vendor: 20,669 bytes, lines 1-80
1/*! 2FullCalendar Standard Bundle v6.1.10 3Docs & License: https://fullcalendar.io/docs/initialize-globals 4(c) 2023 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) { 25 registerStylesRoot(el.getRootNode()); 26 } 27 } 28 function registerStylesRoot(rootNode) { 29 let styleEl = styleEls.get(rootNode); 30 if (!styleEl || !styleEl.isConnected) { 31 styleEl = rootNode.querySelector('style[data-fullcalendar]'); 32 if (!styleEl) { 33 styleEl = document.createElement('style'); 34 styleEl.setAttribute('data-fullcalendar', ''); 35 const nonce = getNonceValue(); 36 if (nonce) { 37 styleEl.nonce = nonce; 38 } 39 const parentEl = rootNode === document ? document.head : rootNode; 40 const insertBefore = rootNode === document 41 ? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style') 42 : parentEl.firstChild; 43 parentEl.insertBefore(styleEl, insertBefore); 44 } 45 styleEls.set(rootNode, styleEl); 46 hydrateStylesRoot(styleEl); 47 } 48 } 49 function hydrateStylesRoot(styleEl) { 50 for (const styleText of styleTexts) { 51 appendStylesTo(styleEl, styleText); 52 } 53 } 54 function appendStylesTo(styleEl, styleText) { 55 const { sheet } = styleEl; 56 const ruleCnt = sheet.cssRules.length; 57 styleText.split('}').forEach((styleStr, i) => { 58 styleStr = styleStr.trim(); 59 if (styleStr) { 60 sheet.insertRule(styleStr + '}', ruleCnt + i); 61 } 62 }); 63 } 64 // nonce 65 // ------------------------------------------------------------------------------------------------- 66 let queriedNonceValue; 67 function getNonceValue() { 68 if (queriedNonceValue === undefined) { 69 queriedNonceValue = queryNonceValue(); 70 } 71 return queriedNonceValue; 72 } 73 /* 74 TODO: discourage meta tag and instead put nonce attribute on placeholder <style> tag 75 */ 76 function queryNonceValue() { 77 const metaWithNonce = document.querySelector('meta[name="csp-nonce"]'); 78 if (metaWithNonce && metaWithNonce.hasAttribute('content')) { 79 return metaWithNonce.getAttribute('content'); 80 }
81 const elWithNonce = document.querySelector('script[nonce]'); 82 if (elWithNonce) { 83 return elWithNonce.nonce || ''; 84 } 85 return ''; 86 } 87 // main 88 // ------------------------------------------------------------------------------------------------- 89 if (typeof document !== 'undefined') { 90 registerStylesRoot(document); 91 } 92 93 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/ARcWMjc2NC8BNzY0JyY
93iDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/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}
93.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)}"; 94 injectStyles(css_248z$4); 95 96 class DelayedRunner { 97 constructor(drainedOption) { 98 this.drainedOption = drainedOption; 99 this.isRunning = false; 100 this.isDirty = false; 101 this.pauseDepths = {}; 102 this.timeoutId = 0; 103 } 104 request(delay) { 105 this.isDirty = true; 106 if (!this.isPaused()) { 107 this.clearTimeout(); 108 if (delay == null) { 109 this.tryDrain(); 110 } 111 else { 112 this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce 113 this.tryDrain.bind(this), delay); 114 } 115 } 116 } 117 pause(scope = '') { 118 let { pauseDepths } = this; 119 pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; 120 this.clearTimeout(); 121 } 122 resume(scope = '', force) { 123 let { pauseDepths } = this; 124 if (scope in pauseDepths) { 125 if (force) { 126 delete pauseDepths[scope]; 127 } 128 else { 129 pauseDepths[scope] -= 1; 130 let depth = pauseDepths[scope]; 131 if (depth <= 0) { 132 delete pauseDepths[scope]; 133 } 134 } 135 this.tryDrain(); 136 } 137 } 138 isPaused() { 139 return Object.keys(this.pauseDepths).length; 140 } 141 tryDrain() { 142 if (!this.isRunning && !this.isPaused()) { 143 this.isRunning = true; 144 while (this.isDirty) { 145 this.isDirty = false; 146 this.drained(); // might set isDirty to true again 147 } 148 this.isRunning = false; 149 } 150 } 151 clear() { 152 this.clearTimeout(); 153 this.isDirty = false; 154 this.pauseDepths = {}; 155 } 156 clearTimeout() { 157 if (this.timeoutId) { 158 clearTimeout(this.timeoutId); 159 this.timeoutId = 0; 160 } 161 } 162 drained() { 163 if (this.drainedOption) { 164 this.drainedOption(); 165 } 166 } 167 } 168 169 function removeElement(el) { 170 if (el.parentNode) { 171 el.parentNode.removeChild(el); 172 } 173 } 174 // Querying 175 // ---------------------------------------------------------------------------------------------------------------- 176 function elementClosest(el, selector) { 177 if (el.closest) { 178 return el.closest(selector); 179 // really bad fallback for IE 180 // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 181 } 182 if (!document.documentElement.contains(el)) { 183 return null; 184 } 185 do { 186 if (elementMatches(el, selector)) { 187 return el; 188 } 189 el = (el.parentElement || el.parentNode); 190 } while (el !== null && el.nodeType === 1); 191 return null; 192 } 193 function elementMatches(el, selector) { 194 let method = el.matches || el.matchesSelector || el.msMatchesSelector; 195 return method.call(el, selector); 196 } 197 // accepts multiple subject els 198 // returns a real array. good for methods like forEach 199 // TODO: accept the document 200 function findElements(container, selector) { 201 let containers = container instanceof HTMLElement ? [container] : container; 202 let allMatches = []; 203 for (let i = 0; i < containers.length; i += 1) { 204 let matches = containers[i].querySelectorAll(selector); 205 for (let j = 0; j < matches.length; j += 1) { 206 allMatches.push(matches[j]); 207 } 208 } 209 return allMatches; 210 } 211 // accepts multiple subject els 212 // only queries direct child elements // TODO: rename to findDirectChildren! 213 function findDirectChildren(parent, selector) { 214 let parents = parent instanceof HTMLElement ? [parent] : parent; 215 let allMatches = []; 216 for (let i = 0; i < parents.length; i += 1) { 217 let childNodes = parents[i].children; // only ever elements 218 for (let j = 0; j < childNodes.length; j += 1) { 219 let childNode = childNodes[j]; 220 if (!selector || elementMatches(childNode, selector)) { 221 allMatches.push(childNode); 222 } 223 } 224 } 225 return allMatches; 226 } 227 // Style 228 // ---------------------------------------------------------------------------------------------------------------- 229 const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; 230 function applyStyle(el, props) { 231 for (let propName in props) { 232 applyStyleProp(el, propName, props[propName]); 233 } 234 } 235 function applyStyleProp(el, name, val) { 236 if (val == null) { 237 el.style[name] = ''; 238 } 239 else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { 240 el.style[name] = `${val}px`; 241 } 242 else { 243 el.style[name] = val; 244 } 245 } 246 // Event Handling 247 // ---------------------------------------------------------------------------------------------------------------- 248 // if intercepting bubbled events at the document/window/body level, 249 // and want to see originating element (the 'target'), use this util instead 250 // of `ev.target` because it goes within web-component boundaries. 251 function getEventTargetViaRoot(ev) { 252 var _a, _b; 253 return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; 254 } 255 // Unique ID for DOM attribute 256 let guid$1 = 0; 257 function getUniqueDomId() { 258 guid$1 += 1; 259 return 'fc-dom-' + guid$1; 260 } 261 262 // Stops a mouse/touch event from doing it's native browser action 263 function preventDefault(ev) { 264 ev.preventDefault(); 265 } 266 // Event Delegation 267 // ---------------------------------------------------------------------------------------------------------------- 268 function buildDelegationHandler(selector, handler) { 269 return (ev) => {
vendor: 47,423 bytes, lines 270-1575
270 let matchedChild = elementClosest(ev.target, selector); 271 if (matchedChild) { 272 handler.call(matchedChild, ev, matchedChild); 273 } 274 }; 275 } 276 function listenBySelector(container, eventType, selector, handler) { 277 let attachedHandler = buildDelegationHandler(selector, handler); 278 container.addEventListener(eventType, attachedHandler); 279 return () => { 280 container.removeEventListener(eventType, attachedHandler); 281 }; 282 } 283 function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { 284 let currentMatchedChild; 285 return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => { 286 if (matchedChild !== currentMatchedChild) { 287 currentMatchedChild = matchedChild; 288 onMouseEnter(mouseOverEv, matchedChild); 289 let realOnMouseLeave = (mouseLeaveEv) => { 290 currentMatchedChild = null; 291 onMouseLeave(mouseLeaveEv, matchedChild); 292 matchedChild.removeEventListener('mouseleave', realOnMouseLeave); 293 }; 294 // listen to the next mouseleave, and then unattach 295 matchedChild.addEventListener('mouseleave', realOnMouseLeave); 296 } 297 }); 298 } 299 // Animation 300 // ---------------------------------------------------------------------------------------------------------------- 301 const transitionEventNames = [ 302 'webkitTransitionEnd', 303 'otransitionend', 304 'oTransitionEnd', 305 'msTransitionEnd', 306 'transitionend', 307 ]; 308 // triggered only when the next single subsequent transition finishes 309 function whenTransitionDone(el, callback) { 310 let realCallback = (ev) => { 311 callback(ev); 312 transitionEventNames.forEach((eventName) => { 313 el.removeEventListener(eventName, realCallback); 314 }); 315 }; 316 transitionEventNames.forEach((eventName) => { 317 el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes 318 }); 319 } 320 // ARIA workarounds 321 // ---------------------------------------------------------------------------------------------------------------- 322 function createAriaClickAttrs(handler) { 323 return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler)); 324 } 325 function createAriaKeyboardAttrs(handler) { 326 return { 327 tabIndex: 0, 328 onKeyDown(ev) { 329 if (ev.key === 'Enter' || ev.key === ' ') { 330 handler(ev); 331 ev.preventDefault(); // if space, don't scroll down page 332 } 333 }, 334 }; 335 } 336 337 let guidNumber = 0; 338 function guid() { 339 guidNumber += 1; 340 return String(guidNumber); 341 } 342 /* FullCalendar-specific DOM Utilities 343 ----------------------------------------------------------------------------------------------------------------------*/ 344 // Make the mouse cursor express that an event is not allowed in the current area 345 function disableCursor() { 346 document.body.classList.add('fc-not-allowed'); 347 } 348 // Returns the mouse cursor to its original look 349 function enableCursor() { 350 document.body.classList.remove('fc-not-allowed'); 351 } 352 /* Selection 353 ----------------------------------------------------------------------------------------------------------------------*/ 354 function preventSelection(el) { 355 el.style.userSelect = 'none'; 356 el.style.webkitUserSelect = 'none'; 357 el.addEventListener('selectstart', preventDefault); 358 } 359 function allowSelection(el) { 360 el.style.userSelect = ''; 361 el.style.webkitUserSelect = ''; 362 el.removeEventListener('selectstart', preventDefault); 363 } 364 /* Context Menu 365 ----------------------------------------------------------------------------------------------------------------------*/ 366 function preventContextMenu(el) { 367 el.addEventListener('contextmenu', preventDefault); 368 } 369 function allowContextMenu(el) { 370 el.removeEventListener('contextmenu', preventDefault); 371 } 372 function parseFieldSpecs(input) { 373 let specs = []; 374 let tokens = []; 375 let i; 376 let token; 377 if (typeof input === 'string') { 378 tokens = input.split(/\s*,\s*/); 379 } 380 else if (typeof input === 'function') { 381 tokens = [input]; 382 } 383 else if (Array.isArray(input)) { 384 tokens = input; 385 } 386 for (i = 0; i < tokens.length; i += 1) { 387 token = tokens[i]; 388 if (typeof token === 'string') { 389 specs.push(token.charAt(0) === '-' ? 390 { field: token.substring(1), order: -1 } : 391 { field: token, order: 1 }); 392 } 393 else if (typeof token === 'function') { 394 specs.push({ func: token }); 395 } 396 } 397 return specs; 398 } 399 function compareByFieldSpecs(obj0, obj1, fieldSpecs) { 400 let i; 401 let cmp; 402 for (i = 0; i < fieldSpecs.length; i += 1) { 403 cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); 404 if (cmp) { 405 return cmp; 406 } 407 } 408 return 0; 409 } 410 function compareByFieldSpec(obj0, obj1, fieldSpec) { 411 if (fieldSpec.func) { 412 return fieldSpec.func(obj0, obj1); 413 } 414 return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) 415 * (fieldSpec.order || 1); 416 } 417 function flexibleCompare(a, b) { 418 if (!a && !b) { 419 return 0; 420 } 421 if (b == null) { 422 return -1; 423 } 424 if (a == null) { 425 return 1; 426 } 427 if (typeof a === 'string' || typeof b === 'string') { 428 return String(a).localeCompare(String(b)); 429 } 430 return a - b; 431 } 432 /* String Utilities 433 ----------------------------------------------------------------------------------------------------------------------*/ 434 function padStart(val, len) { 435 let s = String(val); 436 return '000'.substr(0, len - s.length) + s; 437 } 438 function formatWithOrdinals(formatter, args, fallbackText) { 439 if (typeof formatter === 'function') { 440 return formatter(...args); 441 } 442 if (typeof formatter === 'string') { // non-blank string 443 return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter); 444 } 445 return fallbackText; 446 } 447 /* Number Utilities 448 ----------------------------------------------------------------------------------------------------------------------*/ 449 function compareNumbers(a, b) { 450 return a - b; 451 } 452 function isInt(n) { 453 return n % 1 === 0; 454 } 455 /* FC-specific DOM dimension stuff 456 ----------------------------------------------------------------------------------------------------------------------*/ 457 function computeSmallestCellWidth(cellEl) { 458 let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); 459 let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); 460 if (!allWidthEl) { 461 throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const 462 } 463 if (!contentWidthEl) { 464 throw new Error('needs fc-scrollgrid-shrink-cushion className'); 465 } 466 return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border 467 contentWidthEl.getBoundingClientRect().width; 468 } 469 470 const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; 471 const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; 472 // Parsing and Creation 473 function createDuration(input, unit) { 474 if (typeof input === 'string') { 475 return parseString(input); 476 } 477 if (typeof input === 'object' && input) { // non-null object 478 return parseObject(input); 479 } 480 if (typeof input === 'number') { 481 return parseObject({ [unit || 'milliseconds']: input }); 482 } 483 return null; 484 } 485 function parseString(s) { 486 let m = PARSE_RE.exec(s); 487 if (m) { 488 let sign = m[1] ? -1 : 1; 489 return { 490 years: 0, 491 months: 0, 492 days: sign * (m[2] ? parseInt(m[2], 10) : 0), 493 milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours 494 (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes 495 (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds 496 (m[6] ? parseInt(m[6], 10) : 0) // ms 497 ), 498 }; 499 } 500 return null; 501 } 502 function parseObject(obj) { 503 let duration = { 504 years: obj.years || obj.year || 0, 505 months: obj.months || obj.month || 0, 506 days: obj.days || obj.day || 0, 507 milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours 508 (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes 509 (obj.seconds || obj.second || 0) * 1000 + // seconds 510 (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms 511 }; 512 let weeks = obj.weeks || obj.week; 513 if (weeks) { 514 duration.days += weeks * 7; 515 duration.specifiedWeeks = true; 516 } 517 return duration; 518 } 519 // Equality 520 function durationsEqual(d0, d1) { 521 return d0.years === d1.years && 522 d0.months === d1.months && 523 d0.days === d1.days && 524 d0.milliseconds === d1.milliseconds; 525 } 526 function asCleanDays(dur) { 527 if (!dur.years && !dur.months && !dur.milliseconds) { 528 return dur.days; 529 } 530 return 0; 531 } 532 // Simple Math 533 function addDurations(d0, d1) { 534 return { 535 years: d0.years + d1.years, 536 months: d0.months + d1.months, 537 days: d0.days + d1.days, 538 milliseconds: d0.milliseconds + d1.milliseconds, 539 }; 540 } 541 function subtractDurations(d1, d0) { 542 return { 543 years: d1.years - d0.years, 544 months: d1.months - d0.months, 545 days: d1.days - d0.days, 546 milliseconds: d1.milliseconds - d0.milliseconds, 547 }; 548 } 549 function multiplyDuration(d, n) { 550 return { 551 years: d.years * n, 552 months: d.months * n, 553 days: d.days * n, 554 milliseconds: d.milliseconds * n, 555 }; 556 } 557 // Conversions 558 // "Rough" because they are based on average-case Gregorian months/years 559 function asRoughYears(dur) { 560 return asRoughDays(dur) / 365; 561 } 562 function asRoughMonths(dur) { 563 return asRoughDays(dur) / 30; 564 } 565 function asRoughDays(dur) { 566 return asRoughMs(dur) / 864e5; 567 } 568 function asRoughMinutes(dur) { 569 return asRoughMs(dur) / (1000 * 60); 570 } 571 function asRoughSeconds(dur) { 572 return asRoughMs(dur) / 1000; 573 } 574 function asRoughMs(dur) { 575 return dur.years * (365 * 864e5) + 576 dur.months * (30 * 864e5) + 577 dur.days * 864e5 + 578 dur.milliseconds; 579 } 580 // Advanced Math 581 function wholeDivideDurations(numerator, denominator) { 582 let res = null; 583 for (let i = 0; i < INTERNAL_UNITS.length; i += 1) { 584 let unit = INTERNAL_UNITS[i]; 585 if (denominator[unit]) { 586 let localRes = numerator[unit] / denominator[unit]; 587 if (!isInt(localRes) || (res !== null && res !== localRes)) { 588 return null; 589 } 590 res = localRes; 591 } 592 else if (numerator[unit]) { 593 // needs to divide by something but can't! 594 return null; 595 } 596 } 597 return res; 598 } 599 function greatestDurationDenominator(dur) { 600 let ms = dur.milliseconds; 601 if (ms) { 602 if (ms % 1000 !== 0) { 603 return { unit: 'millisecond', value: ms }; 604 } 605 if (ms % (1000 * 60) !== 0) { 606 return { unit: 'second', value: ms / 1000 }; 607 } 608 if (ms % (1000 * 60 * 60) !== 0) { 609 return { unit: 'minute', value: ms / (1000 * 60) }; 610 } 611 if (ms) { 612 return { unit: 'hour', value: ms / (1000 * 60 * 60) }; 613 } 614 } 615 if (dur.days) { 616 if (dur.specifiedWeeks && dur.days % 7 === 0) { 617 return { unit: 'week', value: dur.days / 7 }; 618 } 619 return { unit: 'day', value: dur.days }; 620 } 621 if (dur.months) { 622 return { unit: 'month', value: dur.months }; 623 } 624 if (dur.years) { 625 return { unit: 'year', value: dur.years }; 626 } 627 return { unit: 'millisecond', value: 0 }; 628 } 629 630 // TODO: new util arrayify? 631 function removeExact(array, exactVal) { 632 let removeCnt = 0; 633 let i = 0; 634 while (i < array.length) { 635 if (array[i] === exactVal) { 636 array.splice(i, 1); 637 removeCnt += 1; 638 } 639 else { 640 i += 1; 641 } 642 } 643 return removeCnt; 644 } 645 function isArraysEqual(a0, a1, equalityFunc) { 646 if (a0 === a1) { 647 return true; 648 } 649 let len = a0.length; 650 let i; 651 if (len !== a1.length) { // not array? or not same length? 652 return false; 653 } 654 for (i = 0; i < len; i += 1) { 655 if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { 656 return false; 657 } 658 } 659 return true; 660 } 661 662 const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; 663 // Adding 664 function addWeeks(m, n) { 665 let a = dateToUtcArray(m); 666 a[2] += n * 7; 667 return arrayToUtcDate(a); 668 } 669 function addDays(m, n) { 670 let a = dateToUtcArray(m); 671 a[2] += n; 672 return arrayToUtcDate(a); 673 } 674 function addMs(m, n) { 675 let a = dateToUtcArray(m); 676 a[6] += n; 677 return arrayToUtcDate(a); 678 } 679 // Diffing (all return floats) 680 // TODO: why not use ranges? 681 function diffWeeks(m0, m1) { 682 return diffDays(m0, m1) / 7; 683 } 684 function diffDays(m0, m1) { 685 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); 686 } 687 function diffHours(m0, m1) { 688 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); 689 } 690 function diffMinutes(m0, m1) { 691 return (m1.valueOf() - m0.valueOf()) / (1000 * 60); 692 } 693 function diffSeconds(m0, m1) { 694 return (m1.valueOf() - m0.valueOf()) / 1000; 695 } 696 function diffDayAndTime(m0, m1) { 697 let m0day = startOfDay(m0); 698 let m1day = startOfDay(m1); 699 return { 700 years: 0, 701 months: 0, 702 days: Math.round(diffDays(m0day, m1day)), 703 milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), 704 }; 705 } 706 // Diffing Whole Units 707 function diffWholeWeeks(m0, m1) { 708 let d = diffWholeDays(m0, m1); 709 if (d !== null && d % 7 === 0) { 710 return d / 7; 711 } 712 return null; 713 } 714 function diffWholeDays(m0, m1) { 715 if (timeAsMs(m0) === timeAsMs(m1)) { 716 return Math.round(diffDays(m0, m1)); 717 } 718 return null; 719 } 720 // Start-Of 721 function startOfDay(m) { 722 return arrayToUtcDate([ 723 m.getUTCFullYear(), 724 m.getUTCMonth(), 725 m.getUTCDate(), 726 ]); 727 } 728 function startOfHour(m) { 729 return arrayToUtcDate([ 730 m.getUTCFullYear(), 731 m.getUTCMonth(), 732 m.getUTCDate(), 733 m.getUTCHours(), 734 ]); 735 } 736 function startOfMinute(m) { 737 return arrayToUtcDate([ 738 m.getUTCFullYear(), 739 m.getUTCMonth(), 740 m.getUTCDate(), 741 m.getUTCHours(), 742 m.getUTCMinutes(), 743 ]); 744 } 745 function startOfSecond(m) { 746 return arrayToUtcDate([ 747 m.getUTCFullYear(), 748 m.getUTCMonth(), 749 m.getUTCDate(), 750 m.getUTCHours(), 751 m.getUTCMinutes(), 752 m.getUTCSeconds(), 753 ]); 754 } 755 // Week Computation 756 function weekOfYear(marker, dow, doy) { 757 let y = marker.getUTCFullYear(); 758 let w = weekOfGivenYear(marker, y, dow, doy); 759 if (w < 1) { 760 return weekOfGivenYear(marker, y - 1, dow, doy); 761 } 762 let nextW = weekOfGivenYear(marker, y + 1, dow, doy); 763 if (nextW >= 1) { 764 return Math.min(w, nextW); 765 } 766 return w; 767 } 768 function weekOfGivenYear(marker, year, dow, doy) { 769 let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); 770 let dayStart = startOfDay(marker); 771 let days = Math.round(diffDays(firstWeekStart, dayStart)); 772 return Math.floor(days / 7) + 1; // zero-indexed 773 } 774 // start-of-first-week - start-of-year 775 function firstWeekOffset(year, dow, doy) { 776 // first-week day -- which january is always in the first week (4 for iso, 1 for other) 777 let fwd = 7 + dow - doy; 778 // first-week day local weekday -- which local weekday is fwd 779 let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; 780 return -fwdlw + fwd - 1; 781 } 782 // Array Conversion 783 function dateToLocalArray(date) { 784 return [ 785 date.getFullYear(), 786 date.getMonth(), 787 date.getDate(), 788 date.getHours(), 789 date.getMinutes(), 790 date.getSeconds(), 791 date.getMilliseconds(), 792 ]; 793 } 794 function arrayToLocalDate(a) { 795 return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month 796 a[3] || 0, a[4] || 0, a[5] || 0); 797 } 798 function dateToUtcArray(date) { 799 return [ 800 date.getUTCFullYear(), 801 date.getUTCMonth(), 802 date.getUTCDate(), 803 date.getUTCHours(), 804 date.getUTCMinutes(), 805 date.getUTCSeconds(), 806 date.getUTCMilliseconds(), 807 ]; 808 } 809 function arrayToUtcDate(a) { 810 // according to web standards (and Safari), a month index is required. 811 // massage if only given a year. 812 if (a.length === 1) { 813 a = a.concat([0]); 814 } 815 return new Date(Date.UTC(...a)); 816 } 817 // Other Utils 818 function isValidDate(m) { 819 return !isNaN(m.valueOf()); 820 } 821 function timeAsMs(m) { 822 return m.getUTCHours() * 1000 * 60 * 60 + 823 m.getUTCMinutes() * 1000 * 60 + 824 m.getUTCSeconds() * 1000 + 825 m.getUTCMilliseconds(); 826 } 827 828 // timeZoneOffset is in minutes 829 function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) { 830 let s = marker.toISOString(); 831 s = s.replace('.000', ''); 832 if (stripZeroTime) { 833 s = s.replace('T00:00:00Z', ''); 834 } 835 if (s.length > 10) { // time part wasn't stripped, can add timezone info 836 if (timeZoneOffset == null) { 837 s = s.replace('Z', ''); 838 } 839 else if (timeZoneOffset !== 0) { 840 s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); 841 } 842 // otherwise, its UTC-0 and we want to keep the Z 843 } 844 return s; 845 } 846 // formats the date, but with no time part 847 // TODO: somehow merge with buildIsoString and stripZeroTime 848 // TODO: rename. omit "string" 849 function formatDayString(marker) { 850 return marker.toISOString().replace(/T.*$/, ''); 851 } 852 function formatIsoMonthStr(marker) { 853 return marker.toISOString().match(/^\d{4}-\d{2}/)[0]; 854 } 855 // TODO: use Date::toISOString and use everything after the T? 856 function formatIsoTimeString(marker) { 857 return padStart(marker.getUTCHours(), 2) + ':' + 858 padStart(marker.getUTCMinutes(), 2) + ':' + 859 padStart(marker.getUTCSeconds(), 2); 860 } 861 function formatTimeZoneOffset(minutes, doIso = false) { 862 let sign = minutes < 0 ? '-' : '+'; 863 let abs = Math.abs(minutes); 864 let hours = Math.floor(abs / 60); 865 let mins = Math.round(abs % 60); 866 if (doIso) { 867 return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`; 868 } 869 return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`; 870 } 871 872 function memoize(workerFunc, resEquality, teardownFunc) { 873 let currentArgs; 874 let currentRes; 875 return function (...newArgs) { 876 if (!currentArgs) { 877 currentRes = workerFunc.apply(this, newArgs); 878 } 879 else if (!isArraysEqual(currentArgs, newArgs)) { 880 if (teardownFunc) { 881 teardownFunc(currentRes); 882 } 883 let res = workerFunc.apply(this, newArgs); 884 if (!resEquality || !resEquality(res, currentRes)) { 885 currentRes = res; 886 } 887 } 888 currentArgs = newArgs; 889 return currentRes; 890 }; 891 } 892 function memoizeObjArg(workerFunc, resEquality, teardownFunc) { 893 let currentArg; 894 let currentRes; 895 return (newArg) => { 896 if (!currentArg) { 897 currentRes = workerFunc.call(this, newArg); 898 } 899 else if (!isPropsEqual(currentArg, newArg)) { 900 if (teardownFunc) { 901 teardownFunc(currentRes); 902 } 903 let res = workerFunc.call(this, newArg); 904 if (!resEquality || !resEquality(res, currentRes)) { 905 currentRes = res; 906 } 907 } 908 currentArg = newArg; 909 return currentRes; 910 }; 911 } 912 function memoizeArraylike(// used at all? 913 workerFunc, resEquality, teardownFunc) { 914 let currentArgSets = []; 915 let currentResults = []; 916 return (newArgSets) => { 917 let currentLen = currentArgSets.length; 918 let newLen = newArgSets.length; 919 let i = 0; 920 for (; i < currentLen; i += 1) { 921 if (!newArgSets[i]) { // one of the old sets no longer exists 922 if (teardownFunc) { 923 teardownFunc(currentResults[i]); 924 } 925 } 926 else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { 927 if (teardownFunc) { 928 teardownFunc(currentResults[i]); 929 } 930 let res = workerFunc.apply(this, newArgSets[i]); 931 if (!resEquality || !resEquality(res, currentResults[i])) { 932 currentResults[i] = res; 933 } 934 } 935 } 936 for (; i < newLen; i += 1) { 937 currentResults[i] = workerFunc.apply(this, newArgSets[i]); 938 } 939 currentArgSets = newArgSets; 940 currentResults.splice(newLen); // remove excess 941 return currentResults; 942 }; 943 } 944 function memoizeHashlike(workerFunc, resEquality, teardownFunc) { 945 let currentArgHash = {}; 946 let currentResHash = {}; 947 return (newArgHash) => { 948 let newResHash = {}; 949 for (let key in newArgHash) { 950 if (!currentResHash[key]) { 951 newResHash[key] = workerFunc.apply(this, newArgHash[key]); 952 } 953 else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { 954 if (teardownFunc) { 955 teardownFunc(currentResHash[key]); 956 } 957 let res = workerFunc.apply(this, newArgHash[key]); 958 newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) 959 ? currentResHash[key] 960 : res; 961 } 962 else { 963 newResHash[key] = currentResHash[key]; 964 } 965 } 966 currentArgHash = newArgHash; 967 currentResHash = newResHash; 968 return newResHash; 969 }; 970 } 971 972 const EXTENDED_SETTINGS_AND_SEVERITIES = { 973 week: 3, 974 separator: 0, 975 omitZeroMinute: 0, 976 meridiem: 0, 977 omitCommas: 0, 978 }; 979 const STANDARD_DATE_PROP_SEVERITIES = { 980 timeZoneName: 7, 981 era: 6, 982 year: 5, 983 month: 4, 984 day: 2, 985 weekday: 2, 986 hour: 1, 987 minute: 1, 988 second: 1, 989 }; 990 const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too 991 const COMMA_RE = /,/g; // we need re for globalness 992 const MULTI_SPACE_RE = /\s+/g; 993 const LTR_RE = /\u200e/g; // control character 994 const UTC_RE = /UTC|GMT/; 995 class NativeFormatter { 996 constructor(formatSettings) { 997 let standardDateProps = {}; 998 let extendedSettings = {}; 999 let severity = 0; 1000 for (let name in formatSettings) { 1001 if (name in EXTENDED_SETTINGS_AND_SEVERITIES) { 1002 extendedSettings[name] = formatSettings[name]; 1003 severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name], severity); 1004 } 1005 else { 1006 standardDateProps[name] = formatSettings[name]; 1007 if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity 1008 severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name], severity); 1009 } 1010 } 1011 } 1012 this.standardDateProps = standardDateProps; 1013 this.extendedSettings = extendedSettings; 1014 this.severity = severity; 1015 this.buildFormattingFunc = memoize(buildFormattingFunc); 1016 } 1017 format(date, context) { 1018 return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); 1019 } 1020 formatRange(start, end, context, betterDefaultSeparator) { 1021 let { standardDateProps, extendedSettings } = this; 1022 let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); 1023 if (!diffSeverity) { 1024 return this.format(start, context); 1025 } 1026 let biggestUnitForPartial = diffSeverity; 1027 if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time 1028 (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && 1029 (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && 1030 (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { 1031 biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time 1032 } 1033 let full0 = this.format(start, context); 1034 let full1 = this.format(end, context); 1035 if (full0 === full1) { 1036 return full0; 1037 } 1038 let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); 1039 let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); 1040 let partial0 = partialFormattingFunc(start); 1041 let partial1 = partialFormattingFunc(end); 1042 let insertion = findCommonInsertion(full0, partial0, full1, partial1); 1043 let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; 1044 if (insertion) { 1045 return insertion.before + partial0 + separator + partial1 + insertion.after; 1046 } 1047 return full0 + separator + full1; 1048 } 1049 getLargestUnit() { 1050 switch (this.severity) { 1051 case 7: 1052 case 6: 1053 case 5: 1054 return 'year'; 1055 case 4: 1056 return 'month'; 1057 case 3: 1058 return 'week'; 1059 case 2: 1060 return 'day'; 1061 default: 1062 return 'time'; // really? 1063 } 1064 } 1065 } 1066 function buildFormattingFunc(standardDateProps, extendedSettings, context) { 1067 let standardDatePropCnt = Object.keys(standardDateProps).length; 1068 if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { 1069 return (date) => (formatTimeZoneOffset(date.timeZoneOffset)); 1070 } 1071 if (standardDatePropCnt === 0 && extendedSettings.week) { 1072 return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week)); 1073 } 1074 return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); 1075 } 1076 function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { 1077 standardDateProps = Object.assign({}, standardDateProps); // copy 1078 extendedSettings = Object.assign({}, extendedSettings); // copy 1079 sanitizeSettings(standardDateProps, extendedSettings); 1080 standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers 1081 let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); 1082 let zeroFormat; // needed? 1083 if (extendedSettings.omitZeroMinute) { 1084 let zeroProps = Object.assign({}, standardDateProps); 1085 delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings 1086 zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); 1087 } 1088 return (date) => { 1089 let { marker } = date; 1090 let format; 1091 if (zeroFormat && !marker.getUTCMinutes()) { 1092 format = zeroFormat; 1093 } 1094 else { 1095 format = normalFormat; 1096 } 1097 let s = format.format(marker); 1098 return postProcess(s, date, standardDateProps, extendedSettings, context); 1099 }; 1100 } 1101 function sanitizeSettings(standardDateProps, extendedSettings) { 1102 // deal with a browser inconsistency where formatting the timezone 1103 // requires that the hour/minute be present. 1104 if (standardDateProps.timeZoneName) { 1105 if (!standardDateProps.hour) { 1106 standardDateProps.hour = '2-digit'; 1107 } 1108 if (!standardDateProps.minute) { 1109 standardDateProps.minute = '2-digit'; 1110 } 1111 } 1112 // only support short timezone names 1113 if (standardDateProps.timeZoneName === 'long') { 1114 standardDateProps.timeZoneName = 'short'; 1115 } 1116 // if requesting to display seconds, MUST display minutes 1117 if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { 1118 delete extendedSettings.omitZeroMinute; 1119 } 1120 } 1121 function postProcess(s, date, standardDateProps, extendedSettings, context) { 1122 s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes 1123 if (standardDateProps.timeZoneName === 'short') { 1124 s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? 1125 'UTC' : // important to normalize for IE, which does "GMT" 1126 formatTimeZoneOffset(date.timeZoneOffset)); 1127 } 1128 if (extendedSettings.omitCommas) { 1129 s = s.replace(COMMA_RE, '').trim(); 1130 } 1131 if (extendedSettings.omitZeroMinute) { 1132 s = s.replace(':00', ''); // zeroFormat doesn't always achieve this 1133 } 1134 // ^ do anything that might create adjacent spaces before this point, 1135 // because MERIDIEM_RE likes to eat up loading spaces 1136 if (extendedSettings.meridiem === false) { 1137 s = s.replace(MERIDIEM_RE, '').trim(); 1138 } 1139 else if (extendedSettings.meridiem === 'narrow') { // a/p 1140 s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase()); 1141 } 1142 else if (extendedSettings.meridiem === 'short') { // am/pm 1143 s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`); 1144 } 1145 else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase 1146 s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase()); 1147 } 1148 s = s.replace(MULTI_SPACE_RE, ' '); 1149 s = s.trim(); 1150 return s; 1151 } 1152 function injectTzoStr(s, tzoStr) { 1153 let replaced = false; 1154 s = s.replace(UTC_RE, () => { 1155 replaced = true; 1156 return tzoStr; 1157 }); 1158 // IE11 doesn't include UTC/GMT in the original string, so append to end 1159 if (!replaced) { 1160 s += ` ${tzoStr}`; 1161 } 1162 return s; 1163 } 1164 function formatWeekNumber(num, weekText, weekTextLong, locale, display) { 1165 let parts = []; 1166 if (display === 'long') { 1167 parts.push(weekTextLong); 1168 } 1169 else if (display === 'short' || display === 'narrow') { 1170 parts.push(weekText); 1171 } 1172 if (display === 'long' || display === 'short') { 1173 parts.push(' '); 1174 } 1175 parts.push(locale.simpleNumberFormat.format(num)); 1176 if (locale.options.direction === 'rtl') { // TODO: use control characters instead? 1177 parts.reverse(); 1178 } 1179 return parts.join(''); 1180 } 1181 // Range Formatting Utils 1182 // 0 = exactly the same 1183 // 1 = different by time 1184 // and bigger 1185 function computeMarkerDiffSeverity(d0, d1, ca) { 1186 if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { 1187 return 5; 1188 } 1189 if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { 1190 return 4; 1191 } 1192 if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { 1193 return 2; 1194 } 1195 if (timeAsMs(d0) !== timeAsMs(d1)) { 1196 return 1; 1197 } 1198 return 0; 1199 } 1200 function computePartialFormattingOptions(options, biggestUnit) { 1201 let partialOptions = {}; 1202 for (let name in options) { 1203 if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) 1204 STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) { 1205 partialOptions[name] = options[name]; 1206 } 1207 } 1208 return partialOptions; 1209 } 1210 function findCommonInsertion(full0, partial0, full1, partial1) { 1211 let i0 = 0; 1212 while (i0 < full0.length) { 1213 let found0 = full0.indexOf(partial0, i0); 1214 if (found0 === -1) { 1215 break; 1216 } 1217 let before0 = full0.substr(0, found0); 1218 i0 = found0 + partial0.length; 1219 let after0 = full0.substr(i0); 1220 let i1 = 0; 1221 while (i1 < full1.length) { 1222 let found1 = full1.indexOf(partial1, i1); 1223 if (found1 === -1) { 1224 break; 1225 } 1226 let before1 = full1.substr(0, found1); 1227 i1 = found1 + partial1.length; 1228 let after1 = full1.substr(i1); 1229 if (before0 === before1 && after0 === after1) { 1230 return { 1231 before: before0, 1232 after: after0, 1233 }; 1234 } 1235 } 1236 } 1237 return null; 1238 } 1239 1240 function expandZonedMarker(dateInfo, calendarSystem) { 1241 let a = calendarSystem.markerToArray(dateInfo.marker); 1242 return { 1243 marker: dateInfo.marker, 1244 timeZoneOffset: dateInfo.timeZoneOffset, 1245 array: a, 1246 year: a[0], 1247 month: a[1], 1248 day: a[2], 1249 hour: a[3], 1250 minute: a[4], 1251 second: a[5], 1252 millisecond: a[6], 1253 }; 1254 } 1255 1256 function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { 1257 let startInfo = expandZonedMarker(start, context.calendarSystem); 1258 let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; 1259 return { 1260 date: startInfo, 1261 start: startInfo, 1262 end: endInfo, 1263 timeZone: context.timeZone, 1264 localeCodes: context.locale.codes, 1265 defaultSeparator: betterDefaultSeparator || context.defaultSeparator, 1266 }; 1267 } 1268 1269 /* 1270 TODO: fix the terminology of "formatter" vs "formatting func" 1271 */ 1272 /* 1273 At the time of instantiation, this object does not know which cmd-formatting system it will use. 1274 It receives this at the time of formatting, as a setting. 1275 */ 1276 class CmdFormatter { 1277 constructor(cmdStr) { 1278 this.cmdStr = cmdStr; 1279 } 1280 format(date, context, betterDefaultSeparator) { 1281 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); 1282 } 1283 formatRange(start, end, context, betterDefaultSeparator) { 1284 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); 1285 } 1286 } 1287 1288 class FuncFormatter { 1289 constructor(func) { 1290 this.func = func; 1291 } 1292 format(date, context, betterDefaultSeparator) { 1293 return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); 1294 } 1295 formatRange(start, end, context, betterDefaultSeparator) { 1296 return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); 1297 } 1298 } 1299 1300 function createFormatter(input) { 1301 if (typeof input === 'object' && input) { // non-null object 1302 return new NativeFormatter(input); 1303 } 1304 if (typeof input === 'string') { 1305 return new CmdFormatter(input); 1306 } 1307 if (typeof input === 'function') { 1308 return new FuncFormatter(input); 1309 } 1310 return null; 1311 } 1312 1313 // base options 1314 // ------------ 1315 const BASE_OPTION_REFINERS = { 1316 navLinkDayClick: identity, 1317 navLinkWeekClick: identity, 1318 duration: createDuration, 1319 bootstrapFontAwesome: identity, 1320 buttonIcons: identity, 1321 customButtons: identity, 1322 defaultAllDayEventDuration: createDuration, 1323 defaultTimedEventDuration: createDuration, 1324 nextDayThreshold: createDuration, 1325 scrollTime: createDuration, 1326 scrollTimeReset: Boolean, 1327 slotMinTime: createDuration, 1328 slotMaxTime: createDuration, 1329 dayPopoverFormat: createFormatter, 1330 slotDuration: createDuration, 1331 snapDuration: createDuration, 1332 headerToolbar: identity, 1333 footerToolbar: identity, 1334 defaultRangeSeparator: String, 1335 titleRangeSeparator: String, 1336 forceEventDuration: Boolean, 1337 dayHeaders: Boolean, 1338 dayHeaderFormat: createFormatter, 1339 dayHeaderClassNames: identity, 1340 dayHeaderContent: identity, 1341 dayHeaderDidMount: identity, 1342 dayHeaderWillUnmount: identity, 1343 dayCellClassNames: identity, 1344 dayCellContent: identity, 1345 dayCellDidMount: identity, 1346 dayCellWillUnmount: identity, 1347 initialView: String, 1348 aspectRatio: Number, 1349 weekends: Boolean, 1350 weekNumberCalculation: identity, 1351 weekNumbers: Boolean, 1352 weekNumberClassNames: identity, 1353 weekNumberContent: identity, 1354 weekNumberDidMount: identity, 1355 weekNumberWillUnmount: identity, 1356 editable: Boolean, 1357 viewClassNames: identity, 1358 viewDidMount: identity, 1359 viewWillUnmount: identity, 1360 nowIndicator: Boolean, 1361 nowIndicatorClassNames: identity, 1362 nowIndicatorContent: identity, 1363 nowIndicatorDidMount: identity, 1364 nowIndicatorWillUnmount: identity, 1365 showNonCurrentDates: Boolean, 1366 lazyFetching: Boolean, 1367 startParam: String, 1368 endParam: String, 1369 timeZoneParam: String, 1370 timeZone: String, 1371 locales: identity, 1372 locale: identity, 1373 themeSystem: String, 1374 dragRevertDuration: Number, 1375 dragScroll: Boolean, 1376 allDayMaintainDuration: Boolean, 1377 unselectAuto: Boolean, 1378 dropAccept: identity, 1379 eventOrder: parseFieldSpecs, 1380 eventOrderStrict: Boolean, 1381 handleWindowResize: Boolean, 1382 windowResizeDelay: Number, 1383 longPressDelay: Number, 1384 eventDragMinDistance: Number, 1385 expandRows: Boolean, 1386 height: identity, 1387 contentHeight: identity, 1388 direction: String, 1389 weekNumberFormat: createFormatter, 1390 eventResizableFromStart: Boolean, 1391 displayEventTime: Boolean, 1392 displayEventEnd: Boolean, 1393 weekText: String, 1394 weekTextLong: String, 1395 progressiveEventRendering: Boolean, 1396 businessHours: identity, 1397 initialDate: identity, 1398 now: identity, 1399 eventDataTransform: identity, 1400 stickyHeaderDates: identity, 1401 stickyFooterScrollbar: identity, 1402 viewHeight: identity, 1403 defaultAllDay: Boolean, 1404 eventSourceFailure: identity, 1405 eventSourceSuccess: identity, 1406 eventDisplay: String, 1407 eventStartEditable: Boolean, 1408 eventDurationEditable: Boolean, 1409 eventOverlap: identity, 1410 eventConstraint: identity, 1411 eventAllow: identity, 1412 eventBackgroundColor: String, 1413 eventBorderColor: String, 1414 eventTextColor: String, 1415 eventColor: String, 1416 eventClassNames: identity, 1417 eventContent: identity, 1418 eventDidMount: identity, 1419 eventWillUnmount: identity, 1420 selectConstraint: identity, 1421 selectOverlap: identity, 1422 selectAllow: identity, 1423 droppable: Boolean, 1424 unselectCancel: String, 1425 slotLabelFormat: identity, 1426 slotLaneClassNames: identity, 1427 slotLaneContent: identity, 1428 slotLaneDidMount: identity, 1429 slotLaneWillUnmount: identity, 1430 slotLabelClassNames: identity, 1431 slotLabelContent: identity, 1432 slotLabelDidMount: identity, 1433 slotLabelWillUnmount: identity, 1434 dayMaxEvents: identity, 1435 dayMaxEventRows: identity, 1436 dayMinWidth: Number, 1437 slotLabelInterval: createDuration, 1438 allDayText: String, 1439 allDayClassNames: identity, 1440 allDayContent: identity, 1441 allDayDidMount: identity, 1442 allDayWillUnmount: identity, 1443 slotMinWidth: Number, 1444 navLinks: Boolean, 1445 eventTimeFormat: createFormatter, 1446 rerenderDelay: Number, 1447 moreLinkText: identity, 1448 moreLinkHint: identity, 1449 selectMinDistance: Number, 1450 selectable: Boolean, 1451 selectLongPressDelay: Number, 1452 eventLongPressDelay: Number, 1453 selectMirror: Boolean, 1454 eventMaxStack: Number, 1455 eventMinHeight: Number, 1456 eventMinWidth: Number, 1457 eventShortHeight: Number, 1458 slotEventOverlap: Boolean, 1459 plugins: identity, 1460 firstDay: Number, 1461 dayCount: Number, 1462 dateAlignment: String, 1463 dateIncrement: createDuration, 1464 hiddenDays: identity, 1465 fixedWeekCount: Boolean, 1466 validRange: identity, 1467 visibleRange: identity, 1468 titleFormat: identity, 1469 eventInteractive: Boolean, 1470 // only used by list-view, but languages define the value, so we need it in base options 1471 noEventsText: String, 1472 viewHint: identity, 1473 navLinkHint: identity, 1474 closeHint: String, 1475 timeHint: String, 1476 eventHint: String, 1477 moreLinkClick: identity, 1478 moreLinkClassNames: identity, 1479 moreLinkContent: identity, 1480 moreLinkDidMount: identity, 1481 moreLinkWillUnmount: identity, 1482 monthStartFormat: createFormatter, 1483 // for connectors 1484 // (can't be part of plugin system b/c must be provided at runtime) 1485 handleCustomRendering: identity, 1486 customRenderingMetaMap: identity, 1487 customRenderingReplaces: Boolean, 1488 }; 1489 // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. 1490 // raw values. 1491 const BASE_OPTION_DEFAULTS = { 1492 eventDisplay: 'auto', 1493 defaultRangeSeparator: ' - ', 1494 titleRangeSeparator: ' \u2013 ', 1495 defaultTimedEventDuration: '01:00:00', 1496 defaultAllDayEventDuration: { day: 1 }, 1497 forceEventDuration: false, 1498 nextDayThreshold: '00:00:00', 1499 dayHeaders: true, 1500 initialView: '', 1501 aspectRatio: 1.35, 1502 headerToolbar: { 1503 start: 'title', 1504 center: '', 1505 end: 'today prev,next', 1506 }, 1507 weekends: true, 1508 weekNumbers: false, 1509 weekNumberCalculation: 'local', 1510 editable: false, 1511 nowIndicator: false, 1512 scrollTime: '06:00:00', 1513 scrollTimeReset: true, 1514 slotMinTime: '00:00:00', 1515 slotMaxTime: '24:00:00', 1516 showNonCurrentDates: true, 1517 lazyFetching: true, 1518 startParam: 'start', 1519 endParam: 'end', 1520 timeZoneParam: 'timeZone', 1521 timeZone: 'local', 1522 locales: [], 1523 locale: '', 1524 themeSystem: 'standard', 1525 dragRevertDuration: 500, 1526 dragScroll: true, 1527 allDayMaintainDuration: false, 1528 unselectAuto: true, 1529 dropAccept: '*', 1530 eventOrder: 'start,-duration,allDay,title', 1531 dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, 1532 handleWindowResize: true, 1533 windowResizeDelay: 100, 1534 longPressDelay: 1000, 1535 eventDragMinDistance: 5, 1536 expandRows: false, 1537 navLinks: false, 1538 selectable: false, 1539 eventMinHeight: 15, 1540 eventMinWidth: 30, 1541 eventShortHeight: 30, 1542 monthStartFormat: { month: 'long', day: 'numeric' }, 1543 }; 1544 // calendar listeners 1545 // ------------------ 1546 const CALENDAR_LISTENER_REFINERS = { 1547 datesSet: identity, 1548 eventsSet: identity, 1549 eventAdd: identity, 1550 eventChange: identity, 1551 eventRemove: identity, 1552 windowResize: identity, 1553 eventClick: identity, 1554 eventMouseEnter: identity, 1555 eventMouseLeave: identity, 1556 select: identity, 1557 unselect: identity, 1558 loading: identity, 1559 // internal 1560 _unmount: identity, 1561 _beforeprint: identity, 1562 _afterprint: identity, 1563 _noEventDrop: identity, 1564 _noEventResize: identity, 1565 _resize: identity, 1566 _scrollRequest: identity, 1567 }; 1568 // calendar-specific options 1569 // ------------------------- 1570 const CALENDAR_OPTION_REFINERS = { 1571 buttonText: identity, 1572 buttonHints: identity, 1573 views: identity, 1574 plugins: identity, 1575 initialEvents: identity,
vendor: 10,012 bytes, lines 1576-1869
1576 events: identity, 1577 eventSources: identity, 1578 }; 1579 const COMPLEX_OPTION_COMPARATORS = { 1580 headerToolbar: isMaybeObjectsEqual, 1581 footerToolbar: isMaybeObjectsEqual, 1582 buttonText: isMaybeObjectsEqual, 1583 buttonHints: isMaybeObjectsEqual, 1584 buttonIcons: isMaybeObjectsEqual, 1585 dateIncrement: isMaybeObjectsEqual, 1586 plugins: isMaybeArraysEqual, 1587 events: isMaybeArraysEqual, 1588 eventSources: isMaybeArraysEqual, 1589 ['resources']: isMaybeArraysEqual, 1590 }; 1591 function isMaybeObjectsEqual(a, b) { 1592 if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects 1593 return isPropsEqual(a, b); 1594 } 1595 return a === b; 1596 } 1597 function isMaybeArraysEqual(a, b) { 1598 if (Array.isArray(a) && Array.isArray(b)) { 1599 return isArraysEqual(a, b); 1600 } 1601 return a === b; 1602 } 1603 // view-specific options 1604 // --------------------- 1605 const VIEW_OPTION_REFINERS = { 1606 type: String, 1607 component: identity, 1608 buttonText: String, 1609 buttonTextKey: String, 1610 dateProfileGeneratorClass: identity, 1611 usesMinMaxTime: Boolean, 1612 classNames: identity, 1613 content: identity, 1614 didMount: identity, 1615 willUnmount: identity, 1616 }; 1617 // util funcs 1618 // ---------------------------------------------------------------------------------------------------- 1619 function mergeRawOptions(optionSets) { 1620 return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); 1621 } 1622 function refineProps(input, refiners) { 1623 let refined = {}; 1624 let extra = {}; 1625 for (let propName in refiners) { 1626 if (propName in input) { 1627 refined[propName] = refiners[propName](input[propName]); 1628 } 1629 } 1630 for (let propName in input) { 1631 if (!(propName in refiners)) { 1632 extra[propName] = input[propName]; 1633 } 1634 } 1635 return { refined, extra }; 1636 } 1637 function identity(raw) { 1638 return raw; 1639 } 1640 1641 const { hasOwnProperty } = Object.prototype; 1642 // Merges an array of objects into a single object. 1643 // The second argument allows for an array of property names who's object values will be merged together. 1644 function mergeProps(propObjs, complexPropsMap) { 1645 let dest = {}; 1646 if (complexPropsMap) { 1647 for (let name in complexPropsMap) { 1648 if (complexPropsMap[name] === isMaybeObjectsEqual) { // implies that it's object-mergeable 1649 let complexObjs = []; 1650 // collect the trailing object values, stopping when a non-object is discovered 1651 for (let i = propObjs.length - 1; i >= 0; i -= 1) { 1652 let val = propObjs[i][name]; 1653 if (typeof val === 'object' && val) { // non-null object 1654 complexObjs.unshift(val); 1655 } 1656 else if (val !== undefined) { 1657 dest[name] = val; // if there were no objects, this value will be used 1658 break; 1659 } 1660 } 1661 // if the trailing values were objects, use the merged value 1662 if (complexObjs.length) { 1663 dest[name] = mergeProps(complexObjs); 1664 } 1665 } 1666 } 1667 } 1668 // copy values into the destination, going from last to first 1669 for (let i = propObjs.length - 1; i >= 0; i -= 1) { 1670 let props = propObjs[i]; 1671 for (let name in props) { 1672 if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign 1673 dest[name] = props[name]; 1674 } 1675 } 1676 } 1677 return dest; 1678 } 1679 function filterHash(hash, func) { 1680 let filtered = {}; 1681 for (let key in hash) { 1682 if (func(hash[key], key)) { 1683 filtered[key] = hash[key]; 1684 } 1685 } 1686 return filtered; 1687 } 1688 function mapHash(hash, func) { 1689 let newHash = {}; 1690 for (let key in hash) { 1691 newHash[key] = func(hash[key], key); 1692 } 1693 return newHash; 1694 } 1695 function arrayToHash(a) { 1696 let hash = {}; 1697 for (let item of a) { 1698 hash[item] = true; 1699 } 1700 return hash; 1701 } 1702 // TODO: reassess browser support 1703 // https://caniuse.com/?search=object.values 1704 function hashValuesToArray(obj) { 1705 let a = []; 1706 for (let key in obj) { 1707 a.push(obj[key]); 1708 } 1709 return a; 1710 } 1711 function isPropsEqual(obj0, obj1) { 1712 if (obj0 === obj1) { 1713 return true; 1714 } 1715 for (let key in obj0) { 1716 if (hasOwnProperty.call(obj0, key)) { 1717 if (!(key in obj1)) { 1718 return false; 1719 } 1720 } 1721 } 1722 for (let key in obj1) { 1723 if (hasOwnProperty.call(obj1, key)) { 1724 if (obj0[key] !== obj1[key]) { 1725 return false; 1726 } 1727 } 1728 } 1729 return true; 1730 } 1731 const HANDLER_RE = /^on[A-Z]/; 1732 function isNonHandlerPropsEqual(obj0, obj1) { 1733 const keys = getUnequalProps(obj0, obj1); 1734 for (let key of keys) { 1735 if (!HANDLER_RE.test(key)) { 1736 return false; 1737 } 1738 } 1739 return true; 1740 } 1741 function getUnequalProps(obj0, obj1) { 1742 let keys = []; 1743 for (let key in obj0) { 1744 if (hasOwnProperty.call(obj0, key)) { 1745 if (!(key in obj1)) { 1746 keys.push(key); 1747 } 1748 } 1749 } 1750 for (let key in obj1) { 1751 if (hasOwnProperty.call(obj1, key)) { 1752 if (obj0[key] !== obj1[key]) { 1753 keys.push(key); 1754 } 1755 } 1756 } 1757 return keys; 1758 } 1759 function compareObjs(oldProps, newProps, equalityFuncs = {}) { 1760 if (oldProps === newProps) { 1761 return true; 1762 } 1763 for (let key in newProps) { 1764 if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; 1765 else { 1766 return false; 1767 } 1768 } 1769 // check for props that were omitted in the new 1770 for (let key in oldProps) { 1771 if (!(key in newProps)) { 1772 return false; 1773 } 1774 } 1775 return true; 1776 } 1777 /* 1778 assumed "true" equality for handler names like "onReceiveSomething" 1779 */ 1780 function isObjValsEqual(val0, val1, comparator) { 1781 if (val0 === val1 || comparator === true) { 1782 return true; 1783 } 1784 if (comparator) { 1785 return comparator(val0, val1); 1786 } 1787 return false; 1788 } 1789 function collectFromHash(hash, startIndex = 0, endIndex, step = 1) { 1790 let res = []; 1791 if (endIndex == null) { 1792 endIndex = Object.keys(hash).length; 1793 } 1794 for (let i = startIndex; i < endIndex; i += step) { 1795 let val = hash[i]; 1796 if (val !== undefined) { // will disregard undefined for sparse arrays 1797 res.push(val); 1798 } 1799 } 1800 return res; 1801 } 1802 1803 let calendarSystemClassMap = {}; 1804 function registerCalendarSystem(name, theClass) { 1805 calendarSystemClassMap[name] = theClass; 1806 } 1807 function createCalendarSystem(name) { 1808 return new calendarSystemClassMap[name](); 1809 } 1810 class GregorianCalendarSystem { 1811 getMarkerYear(d) { 1812 return d.getUTCFullYear(); 1813 } 1814 getMarkerMonth(d) { 1815 return d.getUTCMonth(); 1816 } 1817 getMarkerDay(d) { 1818 return d.getUTCDate(); 1819 } 1820 arrayToMarker(arr) { 1821 return arrayToUtcDate(arr); 1822 } 1823 markerToArray(marker) { 1824 return dateToUtcArray(marker); 1825 } 1826 } 1827 registerCalendarSystem('gregory', GregorianCalendarSystem); 1828 1829 const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; 1830 function parse(str) { 1831 let m = ISO_RE.exec(str); 1832 if (m) { 1833 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)); 1834 if (isValidDate(marker)) { 1835 let timeZoneOffset = null; 1836 if (m[13]) { 1837 timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + 1838 Number(m[18] || 0)); 1839 } 1840 return { 1841 marker, 1842 isTimeUnspecified: !m[6], 1843 timeZoneOffset, 1844 }; 1845 } 1846 } 1847 return null; 1848 } 1849 1850 class DateEnv { 1851 constructor(settings) { 1852 let timeZone = this.timeZone = settings.timeZone; 1853 let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; 1854 if (settings.namedTimeZoneImpl && isNamedTimeZone) { 1855 this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); 1856 } 1857 this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); 1858 this.calendarSystem = createCalendarSystem(settings.calendarSystem); 1859 this.locale = settings.locale; 1860 this.weekDow = settings.locale.week.dow; 1861 this.weekDoy = settings.locale.week.doy; 1862 if (settings.weekNumberCalculation === 'ISO') { 1863 this.weekDow = 1; 1864 this.weekDoy = 4; 1865 } 1866 if (typeof settings.firstDay === 'number') { 1867 this.weekDow = settings.firstDay; 1868 } 1869 if (typeof settings.weekNumberCalculation === 'function') {
vendor: 2,020 bytes, lines 1870-1920
1870 this.weekNumberFunc = settings.weekNumberCalculation; 1871 } 1872 this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; 1873 this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText; 1874 this.cmdFormatter = settings.cmdFormatter; 1875 this.defaultSeparator = settings.defaultSeparator; 1876 } 1877 // Creating / Parsing 1878 createMarker(input) { 1879 let meta = this.createMarkerMeta(input); 1880 if (meta === null) { 1881 return null; 1882 } 1883 return meta.marker; 1884 } 1885 createNowMarker() { 1886 if (this.canComputeOffset) { 1887 return this.timestampToMarker(new Date().valueOf()); 1888 } 1889 // if we can't compute the current date val for a timezone, 1890 // better to give the current local date vals than UTC 1891 return arrayToUtcDate(dateToLocalArray(new Date())); 1892 } 1893 createMarkerMeta(input) { 1894 if (typeof input === 'string') { 1895 return this.parse(input); 1896 } 1897 let marker = null; 1898 if (typeof input === 'number') { 1899 marker = this.timestampToMarker(input); 1900 } 1901 else if (input instanceof Date) { 1902 input = input.valueOf(); 1903 if (!isNaN(input)) { 1904 marker = this.timestampToMarker(input); 1905 } 1906 } 1907 else if (Array.isArray(input)) { 1908 marker = arrayToUtcDate(input); 1909 } 1910 if (marker === null || !isValidDate(marker)) { 1911 return null; 1912 } 1913 return { marker, isTimeUnspecified: false, forcedTzo: null }; 1914 } 1915 parse(s) { 1916 let parts = parse(s); 1917 if (parts === null) { 1918 return null; 1919 } 1920 let { marker } = parts;
vendor: 32,328 bytes, lines 1921-2726
1921 let forcedTzo = null; 1922 if (parts.timeZoneOffset !== null) { 1923 if (this.canComputeOffset) { 1924 marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); 1925 } 1926 else { 1927 forcedTzo = parts.timeZoneOffset; 1928 } 1929 } 1930 return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo }; 1931 } 1932 // Accessors 1933 getYear(marker) { 1934 return this.calendarSystem.getMarkerYear(marker); 1935 } 1936 getMonth(marker) { 1937 return this.calendarSystem.getMarkerMonth(marker); 1938 } 1939 getDay(marker) { 1940 return this.calendarSystem.getMarkerDay(marker); 1941 } 1942 // Adding / Subtracting 1943 add(marker, dur) { 1944 let a = this.calendarSystem.markerToArray(marker); 1945 a[0] += dur.years; 1946 a[1] += dur.months; 1947 a[2] += dur.days; 1948 a[6] += dur.milliseconds; 1949 return this.calendarSystem.arrayToMarker(a); 1950 } 1951 subtract(marker, dur) { 1952 let a = this.calendarSystem.markerToArray(marker); 1953 a[0] -= dur.years; 1954 a[1] -= dur.months; 1955 a[2] -= dur.days; 1956 a[6] -= dur.milliseconds; 1957 return this.calendarSystem.arrayToMarker(a); 1958 } 1959 addYears(marker, n) { 1960 let a = this.calendarSystem.markerToArray(marker); 1961 a[0] += n; 1962 return this.calendarSystem.arrayToMarker(a); 1963 } 1964 addMonths(marker, n) { 1965 let a = this.calendarSystem.markerToArray(marker); 1966 a[1] += n; 1967 return this.calendarSystem.arrayToMarker(a); 1968 } 1969 // Diffing Whole Units 1970 diffWholeYears(m0, m1) { 1971 let { calendarSystem } = this; 1972 if (timeAsMs(m0) === timeAsMs(m1) && 1973 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && 1974 calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { 1975 return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); 1976 } 1977 return null; 1978 } 1979 diffWholeMonths(m0, m1) { 1980 let { calendarSystem } = this; 1981 if (timeAsMs(m0) === timeAsMs(m1) && 1982 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { 1983 return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + 1984 (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; 1985 } 1986 return null; 1987 } 1988 // Range / Duration 1989 greatestWholeUnit(m0, m1) { 1990 let n = this.diffWholeYears(m0, m1); 1991 if (n !== null) { 1992 return { unit: 'year', value: n }; 1993 } 1994 n = this.diffWholeMonths(m0, m1); 1995 if (n !== null) { 1996 return { unit: 'month', value: n }; 1997 } 1998 n = diffWholeWeeks(m0, m1); 1999 if (n !== null) { 2000 return { unit: 'week', value: n }; 2001 } 2002 n = diffWholeDays(m0, m1); 2003 if (n !== null) { 2004 return { unit: 'day', value: n }; 2005 } 2006 n = diffHours(m0, m1); 2007 if (isInt(n)) { 2008 return { unit: 'hour', value: n }; 2009 } 2010 n = diffMinutes(m0, m1); 2011 if (isInt(n)) { 2012 return { unit: 'minute', value: n }; 2013 } 2014 n = diffSeconds(m0, m1); 2015 if (isInt(n)) { 2016 return { unit: 'second', value: n }; 2017 } 2018 return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; 2019 } 2020 countDurationsBetween(m0, m1, d) { 2021 // TODO: can use greatestWholeUnit 2022 let diff; 2023 if (d.years) { 2024 diff = this.diffWholeYears(m0, m1); 2025 if (diff !== null) { 2026 return diff / asRoughYears(d); 2027 } 2028 } 2029 if (d.months) { 2030 diff = this.diffWholeMonths(m0, m1); 2031 if (diff !== null) { 2032 return diff / asRoughMonths(d); 2033 } 2034 } 2035 if (d.days) { 2036 diff = diffWholeDays(m0, m1); 2037 if (diff !== null) { 2038 return diff / asRoughDays(d); 2039 } 2040 } 2041 return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); 2042 } 2043 // Start-Of 2044 // these DON'T return zoned-dates. only UTC start-of dates 2045 startOf(m, unit) { 2046 if (unit === 'year') { 2047 return this.startOfYear(m); 2048 } 2049 if (unit === 'month') { 2050 return this.startOfMonth(m); 2051 } 2052 if (unit === 'week') { 2053 return this.startOfWeek(m); 2054 } 2055 if (unit === 'day') { 2056 return startOfDay(m); 2057 } 2058 if (unit === 'hour') { 2059 return startOfHour(m); 2060 } 2061 if (unit === 'minute') { 2062 return startOfMinute(m); 2063 } 2064 if (unit === 'second') { 2065 return startOfSecond(m); 2066 } 2067 return null; 2068 } 2069 startOfYear(m) { 2070 return this.calendarSystem.arrayToMarker([ 2071 this.calendarSystem.getMarkerYear(m), 2072 ]); 2073 } 2074 startOfMonth(m) { 2075 return this.calendarSystem.arrayToMarker([ 2076 this.calendarSystem.getMarkerYear(m), 2077 this.calendarSystem.getMarkerMonth(m), 2078 ]); 2079 } 2080 startOfWeek(m) { 2081 return this.calendarSystem.arrayToMarker([ 2082 this.calendarSystem.getMarkerYear(m), 2083 this.calendarSystem.getMarkerMonth(m), 2084 m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), 2085 ]); 2086 } 2087 // Week Number 2088 computeWeekNumber(marker) { 2089 if (this.weekNumberFunc) { 2090 return this.weekNumberFunc(this.toDate(marker)); 2091 } 2092 return weekOfYear(marker, this.weekDow, this.weekDoy); 2093 } 2094 // TODO: choke on timeZoneName: long 2095 format(marker, formatter, dateOptions = {}) { 2096 return formatter.format({ 2097 marker, 2098 timeZoneOffset: dateOptions.forcedTzo != null ? 2099 dateOptions.forcedTzo : 2100 this.offsetForMarker(marker), 2101 }, this); 2102 } 2103 formatRange(start, end, formatter, dateOptions = {}) { 2104 if (dateOptions.isEndExclusive) { 2105 end = addMs(end, -1); 2106 } 2107 return formatter.formatRange({ 2108 marker: start, 2109 timeZoneOffset: dateOptions.forcedStartTzo != null ? 2110 dateOptions.forcedStartTzo : 2111 this.offsetForMarker(start), 2112 }, { 2113 marker: end, 2114 timeZoneOffset: dateOptions.forcedEndTzo != null ? 2115 dateOptions.forcedEndTzo : 2116 this.offsetForMarker(end), 2117 }, this, dateOptions.defaultSeparator); 2118 } 2119 /* 2120 DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, 2121 might as well use buildIsoString or some other util directly 2122 */ 2123 formatIso(marker, extraOptions = {}) { 2124 let timeZoneOffset = null; 2125 if (!extraOptions.omitTimeZoneOffset) { 2126 if (extraOptions.forcedTzo != null) { 2127 timeZoneOffset = extraOptions.forcedTzo; 2128 } 2129 else { 2130 timeZoneOffset = this.offsetForMarker(marker); 2131 } 2132 } 2133 return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); 2134 } 2135 // TimeZone 2136 timestampToMarker(ms) { 2137 if (this.timeZone === 'local') { 2138 return arrayToUtcDate(dateToLocalArray(new Date(ms))); 2139 } 2140 if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { 2141 return new Date(ms); 2142 } 2143 return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); 2144 } 2145 offsetForMarker(m) { 2146 if (this.timeZone === 'local') { 2147 return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset 2148 } 2149 if (this.timeZone === 'UTC') { 2150 return 0; 2151 } 2152 if (this.namedTimeZoneImpl) { 2153 return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); 2154 } 2155 return null; 2156 } 2157 // Conversion 2158 toDate(m, forcedTzo) { 2159 if (this.timeZone === 'local') { 2160 return arrayToLocalDate(dateToUtcArray(m)); 2161 } 2162 if (this.timeZone === 'UTC') { 2163 return new Date(m.valueOf()); // make sure it's a copy 2164 } 2165 if (!this.namedTimeZoneImpl) { 2166 return new Date(m.valueOf() - (forcedTzo || 0)); 2167 } 2168 return new Date(m.valueOf() - 2169 this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); 2170 } 2171 } 2172 2173 class Theme { 2174 constructor(calendarOptions) { 2175 if (this.iconOverrideOption) { 2176 this.setIconOverride(calendarOptions[this.iconOverrideOption]); 2177 } 2178 } 2179 setIconOverride(iconOverrideHash) { 2180 let iconClassesCopy; 2181 let buttonName; 2182 if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object 2183 iconClassesCopy = Object.assign({}, this.iconClasses); 2184 for (buttonName in iconOverrideHash) { 2185 iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); 2186 } 2187 this.iconClasses = iconClassesCopy; 2188 } 2189 else if (iconOverrideHash === false) { 2190 this.iconClasses = {}; 2191 } 2192 } 2193 applyIconOverridePrefix(className) { 2194 let prefix = this.iconOverridePrefix; 2195 if (prefix && className.indexOf(prefix) !== 0) { // if not already present 2196 className = prefix + className; 2197 } 2198 return className; 2199 } 2200 getClass(key) { 2201 return this.classes[key] || ''; 2202 } 2203 getIconClass(buttonName, isRtl) { 2204 let className; 2205 if (isRtl && this.rtlIconClasses) { 2206 className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; 2207 } 2208 else { 2209 className = this.iconClasses[buttonName]; 2210 } 2211 if (className) { 2212 return `${this.baseIconClass} ${className}`; 2213 } 2214 return ''; 2215 } 2216 getCustomButtonIconClass(customButtonProps) { 2217 let className; 2218 if (this.iconOverrideCustomButtonOption) { 2219 className = customButtonProps[this.iconOverrideCustomButtonOption]; 2220 if (className) { 2221 return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`; 2222 } 2223 } 2224 return ''; 2225 } 2226 } 2227 Theme.prototype.classes = {}; 2228 Theme.prototype.iconClasses = {}; 2229 Theme.prototype.baseIconClass = ''; 2230 Theme.prototype.iconOverridePrefix = ''; 2231 2232 /* 2233 NOTE: this can be a public API, especially createElement for hooks. 2234 See examples/typescript-scheduler/src/index.ts 2235 */ 2236 function flushSync(runBeforeFlush) { 2237 runBeforeFlush(); 2238 let oldDebounceRendering = l$1.debounceRendering; // orig 2239 let callbackQ = []; 2240 function execCallbackSync(callback) { 2241 callbackQ.push(callback); 2242 } 2243 l$1.debounceRendering = execCallbackSync; 2244 D$1(y(FakeComponent, {}), document.createElement('div')); 2245 while (callbackQ.length) { 2246 callbackQ.shift()(); 2247 } 2248 l$1.debounceRendering = oldDebounceRendering; 2249 } 2250 class FakeComponent extends x$1 { 2251 render() { return y('div', {}); } 2252 componentDidMount() { this.setState({}); } 2253 } 2254 // TODO: use preact/compat instead? 2255 function createContext(defaultValue) { 2256 let ContextType = G$1(defaultValue); 2257 let origProvider = ContextType.Provider; 2258 ContextType.Provider = function () { 2259 let isNew = !this.getChildContext; 2260 let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params 2261 if (isNew) { 2262 let subs = []; 2263 this.shouldComponentUpdate = (_props) => { 2264 if (this.props.value !== _props.value) { 2265 subs.forEach((c) => { 2266 c.context = _props.value; 2267 c.forceUpdate(); 2268 }); 2269 } 2270 }; 2271 this.sub = (c) => { 2272 subs.push(c); 2273 let old = c.componentWillUnmount; 2274 c.componentWillUnmount = () => { 2275 subs.splice(subs.indexOf(c), 1); 2276 old && old.call(c); 2277 }; 2278 }; 2279 } 2280 return children; 2281 }; 2282 return ContextType; 2283 } 2284 2285 class ScrollResponder { 2286 constructor(execFunc, emitter, scrollTime, scrollTimeReset) { 2287 this.execFunc = execFunc; 2288 this.emitter = emitter; 2289 this.scrollTime = scrollTime; 2290 this.scrollTimeReset = scrollTimeReset; 2291 this.handleScrollRequest = (request) => { 2292 this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request); 2293 this.drain(); 2294 }; 2295 emitter.on('_scrollRequest', this.handleScrollRequest); 2296 this.fireInitialScroll(); 2297 } 2298 detach() { 2299 this.emitter.off('_scrollRequest', this.handleScrollRequest); 2300 } 2301 update(isDatesNew) { 2302 if (isDatesNew && this.scrollTimeReset) { 2303 this.fireInitialScroll(); // will drain 2304 } 2305 else { 2306 this.drain(); 2307 } 2308 } 2309 fireInitialScroll() { 2310 this.handleScrollRequest({ 2311 time: this.scrollTime, 2312 }); 2313 } 2314 drain() { 2315 if (this.queuedRequest && this.execFunc(this.queuedRequest)) { 2316 this.queuedRequest = null; 2317 } 2318 } 2319 } 2320 2321 const ViewContextType = createContext({}); // for Components 2322 function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { 2323 return { 2324 dateEnv, 2325 options: viewOptions, 2326 pluginHooks, 2327 emitter, 2328 dispatch, 2329 getCurrentData, 2330 calendarApi, 2331 viewSpec, 2332 viewApi, 2333 dateProfileGenerator, 2334 theme, 2335 isRtl: viewOptions.direction === 'rtl', 2336 addResizeHandler(handler) { 2337 emitter.on('_resize', handler); 2338 }, 2339 removeResizeHandler(handler) { 2340 emitter.off('_resize', handler); 2341 }, 2342 createScrollResponder(execFunc) { 2343 return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); 2344 }, 2345 registerInteractiveComponent, 2346 unregisterInteractiveComponent, 2347 }; 2348 } 2349 2350 /* eslint max-classes-per-file: off */ 2351 class PureComponent extends x$1 { 2352 shouldComponentUpdate(nextProps, nextState) { 2353 if (this.debug) { 2354 // eslint-disable-next-line no-console 2355 console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); 2356 } 2357 return !compareObjs(this.props, nextProps, this.propEquality) || 2358 !compareObjs(this.state, nextState, this.stateEquality); 2359 } 2360 // HACK for freakin' React StrictMode 2361 safeSetState(newState) { 2362 if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) { 2363 this.setState(newState); 2364 } 2365 } 2366 } 2367 PureComponent.addPropsEquality = addPropsEquality; 2368 PureComponent.addStateEquality = addStateEquality; 2369 PureComponent.contextType = ViewContextType; 2370 PureComponent.prototype.propEquality = {}; 2371 PureComponent.prototype.stateEquality = {}; 2372 class BaseComponent extends PureComponent { 2373 } 2374 BaseComponent.contextType = ViewContextType; 2375 function addPropsEquality(propEquality) { 2376 let hash = Object.create(this.prototype.propEquality); 2377 Object.assign(hash, propEquality); 2378 this.prototype.propEquality = hash; 2379 } 2380 function addStateEquality(stateEquality) { 2381 let hash = Object.create(this.prototype.stateEquality); 2382 Object.assign(hash, stateEquality); 2383 this.prototype.stateEquality = hash; 2384 } 2385 // use other one 2386 function setRef(ref, current) { 2387 if (typeof ref === 'function') { 2388 ref(current); 2389 } 2390 else if (ref) { 2391 // see https://github.com/facebook/react/issues/13029 2392 ref.current = current; 2393 } 2394 } 2395 2396 class ContentInjector extends BaseComponent { 2397 constructor() { 2398 super(...arguments); 2399 this.id = guid(); 2400 this.queuedDomNodes = []; 2401 this.currentDomNodes = []; 2402 this.handleEl = (el) => { 2403 const { options } = this.context; 2404 const { generatorName } = this.props; 2405 if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) { 2406 this.updateElRef(el); 2407 } 2408 }; 2409 this.updateElRef = (el) => { 2410 if (this.props.elRef) { 2411 setRef(this.props.elRef, el); 2412 } 2413 }; 2414 } 2415 render() { 2416 const { props, context } = this; 2417 const { options } = context; 2418 const { customGenerator, defaultGenerator, renderProps } = props; 2419 const attrs = buildElAttrs(props, [], this.handleEl); 2420 let useDefault = false; 2421 let innerContent; 2422 let queuedDomNodes = []; 2423 let currentGeneratorMeta; 2424 if (customGenerator != null) { 2425 const customGeneratorRes = typeof customGenerator === 'function' ? 2426 customGenerator(renderProps, y) : 2427 customGenerator; 2428 if (customGeneratorRes === true) { 2429 useDefault = true; 2430 } 2431 else { 2432 const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null 2433 if (isObject && ('html' in customGeneratorRes)) { 2434 attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html }; 2435 } 2436 else if (isObject && ('domNodes' in customGeneratorRes)) { 2437 queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes); 2438 } 2439 else if (isObject 2440 ? i$1(customGeneratorRes) // vdom node 2441 : typeof customGeneratorRes !== 'function' // primitive value (like string or number) 2442 ) { 2443 // use in vdom 2444 innerContent = customGeneratorRes; 2445 } 2446 else { 2447 // an exotic object for handleCustomRendering 2448 currentGeneratorMeta = customGeneratorRes; 2449 } 2450 } 2451 } 2452 else { 2453 useDefault = !hasCustomRenderingHandler(props.generatorName, options); 2454 } 2455 if (useDefault && defaultGenerator) { 2456 innerContent = defaultGenerator(renderProps); 2457 } 2458 this.queuedDomNodes = queuedDomNodes; 2459 this.currentGeneratorMeta = currentGeneratorMeta; 2460 return y(props.elTag, attrs, innerContent); 2461 } 2462 componentDidMount() { 2463 this.applyQueueudDomNodes(); 2464 this.triggerCustomRendering(true); 2465 } 2466 componentDidUpdate() { 2467 this.applyQueueudDomNodes(); 2468 this.triggerCustomRendering(true); 2469 } 2470 componentWillUnmount() { 2471 this.triggerCustomRendering(false); // TODO: different API for removal? 2472 } 2473 triggerCustomRendering(isActive) { 2474 var _a; 2475 const { props, context } = this; 2476 const { handleCustomRendering, customRenderingMetaMap } = context.options; 2477 if (handleCustomRendering) { 2478 const generatorMeta = (_a = this.currentGeneratorMeta) !== null && _a !== void 0 ? _a : customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName]; 2479 if (generatorMeta) { 2480 handleCustomRendering(Object.assign(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els 2481 generatorMeta }, props), { elClasses: (props.elClasses || []).filter(isTruthy) })); 2482 } 2483 } 2484 } 2485 applyQueueudDomNodes() { 2486 const { queuedDomNodes, currentDomNodes } = this; 2487 const el = this.base; 2488 if (!isArraysEqual(queuedDomNodes, currentDomNodes)) { 2489 currentDomNodes.forEach(removeElement); 2490 for (let newNode of queuedDomNodes) { 2491 el.appendChild(newNode); 2492 } 2493 this.currentDomNodes = queuedDomNodes; 2494 } 2495 } 2496 } 2497 ContentInjector.addPropsEquality({ 2498 elClasses: isArraysEqual, 2499 elStyle: isPropsEqual, 2500 elAttrs: isNonHandlerPropsEqual, 2501 renderProps: isPropsEqual, 2502 }); 2503 // Util 2504 /* 2505 Does UI-framework provide custom way of rendering that does not use Preact VDOM 2506 AND does the calendar's options define custom rendering? 2507 AKA. Should we NOT render the default content? 2508 */ 2509 function hasCustomRenderingHandler(generatorName, options) { 2510 var _a; 2511 return Boolean(options.handleCustomRendering && 2512 generatorName && 2513 ((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName])); 2514 } 2515 function buildElAttrs(props, extraClassNames, elRef) { 2516 const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: elRef }); 2517 if (props.elClasses || extraClassNames) { 2518 attrs.className = (props.elClasses || []) 2519 .concat(extraClassNames || []) 2520 .concat(attrs.className || []) 2521 .filter(Boolean) 2522 .join(' '); 2523 } 2524 if (props.elStyle) { 2525 attrs.style = props.elStyle; 2526 } 2527 return attrs; 2528 } 2529 function isTruthy(val) { 2530 return Boolean(val); 2531 } 2532 2533 const RenderId = createContext(0); 2534 2535 class ContentContainer extends x$1 { 2536 constructor() { 2537 super(...arguments); 2538 this.InnerContent = InnerContentInjector.bind(undefined, this); 2539 this.handleEl = (el) => { 2540 this.el = el; 2541 if (this.props.elRef) { 2542 setRef(this.props.elRef, el); 2543 if (el && this.didMountMisfire) { 2544 this.componentDidMount(); 2545 } 2546 } 2547 }; 2548 } 2549 render() { 2550 const { props } = this; 2551 const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps); 2552 if (props.children) { 2553 const elAttrs = buildElAttrs(props, generatedClassNames, this.handleEl); 2554 const children = props.children(this.InnerContent, props.renderProps, elAttrs); 2555 if (props.elTag) { 2556 return y(props.elTag, elAttrs, children); 2557 } 2558 else { 2559 return children; 2560 } 2561 } 2562 else { 2563 return y((ContentInjector), Object.assign(Object.assign({}, props), { elRef: this.handleEl, elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context })); 2564 } 2565 } 2566 componentDidMount() { 2567 var _a, _b; 2568 if (this.el) { 2569 (_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el })); 2570 } 2571 else { 2572 this.didMountMisfire = true; 2573 } 2574 } 2575 componentWillUnmount() { 2576 var _a, _b; 2577 (_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el })); 2578 } 2579 } 2580 ContentContainer.contextType = RenderId; 2581 function InnerContentInjector(containerComponent, props) { 2582 const parentProps = containerComponent.props; 2583 return y((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, customGenerator: parentProps.customGenerator, defaultGenerator: parentProps.defaultGenerator, renderId: containerComponent.context }, props)); 2584 } 2585 // Utils 2586 function generateClassNames(classNameGenerator, renderProps) { 2587 const classNames = typeof classNameGenerator === 'function' ? 2588 classNameGenerator(renderProps) : 2589 classNameGenerator || []; 2590 return typeof classNames === 'string' ? [classNames] : classNames; 2591 } 2592 2593 class ViewContainer extends BaseComponent { 2594 render() { 2595 let { props, context } = this; 2596 let { options } = context; 2597 let renderProps = { view: context.viewApi }; 2598 return (y(ContentContainer, Object.assign({}, props, { elTag: props.elTag || 'div', elClasses: [ 2599 ...buildViewClassNames(props.viewSpec), 2600 ...(props.elClasses || []), 2601 ], renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }), () => props.children)); 2602 } 2603 } 2604 function buildViewClassNames(viewSpec) { 2605 return [ 2606 `fc-${viewSpec.type}-view`, 2607 'fc-view', 2608 ]; 2609 } 2610 2611 function parseRange(input, dateEnv) { 2612 let start = null; 2613 let end = null; 2614 if (input.start) { 2615 start = dateEnv.createMarker(input.start); 2616 } 2617 if (input.end) { 2618 end = dateEnv.createMarker(input.end); 2619 } 2620 if (!start && !end) { 2621 return null; 2622 } 2623 if (start && end && end < start) { 2624 return null; 2625 } 2626 return { start, end }; 2627 } 2628 // SIDE-EFFECT: will mutate ranges. 2629 // Will return a new array result. 2630 function invertRanges(ranges, constraintRange) { 2631 let invertedRanges = []; 2632 let { start } = constraintRange; // the end of the previous range. the start of the new range 2633 let i; 2634 let dateRange; 2635 // ranges need to be in order. required for our date-walking algorithm 2636 ranges.sort(compareRanges); 2637 for (i = 0; i < ranges.length; i += 1) { 2638 dateRange = ranges[i]; 2639 // add the span of time before the event (if there is any) 2640 if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) 2641 invertedRanges.push({ start, end: dateRange.start }); 2642 } 2643 if (dateRange.end > start) { 2644 start = dateRange.end; 2645 } 2646 } 2647 // add the span of time after the last event (if there is any) 2648 if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) 2649 invertedRanges.push({ start, end: constraintRange.end }); 2650 } 2651 return invertedRanges; 2652 } 2653 function compareRanges(range0, range1) { 2654 return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first 2655 } 2656 function intersectRanges(range0, range1) { 2657 let { start, end } = range0; 2658 let newRange = null; 2659 if (range1.start !== null) { 2660 if (start === null) { 2661 start = range1.start; 2662 } 2663 else { 2664 start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); 2665 } 2666 } 2667 if (range1.end != null) { 2668 if (end === null) { 2669 end = range1.end; 2670 } 2671 else { 2672 end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); 2673 } 2674 } 2675 if (start === null || end === null || start < end) { 2676 newRange = { start, end }; 2677 } 2678 return newRange; 2679 } 2680 function rangesEqual(range0, range1) { 2681 return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && 2682 (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); 2683 } 2684 function rangesIntersect(range0, range1) { 2685 return (range0.end === null || range1.start === null || range0.end > range1.start) && 2686 (range0.start === null || range1.end === null || range0.start < range1.end); 2687 } 2688 function rangeContainsRange(outerRange, innerRange) { 2689 return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && 2690 (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); 2691 } 2692 function rangeContainsMarker(range, date) { 2693 return (range.start === null || date >= range.start) && 2694 (range.end === null || date < range.end); 2695 } 2696 // If the given date is not within the given range, move it inside. 2697 // (If it's past the end, make it one millisecond before the end). 2698 function constrainMarkerToRange(date, range) { 2699 if (range.start != null && date < range.start) { 2700 return range.start; 2701 } 2702 if (range.end != null && date >= range.end) { 2703 return new Date(range.end.valueOf() - 1); 2704 } 2705 return date; 2706 } 2707 2708 /* Date stuff that doesn't belong in datelib core 2709 ----------------------------------------------------------------------------------------------------------------------*/ 2710 // given a timed range, computes an all-day range that has the same exact duration, 2711 // but whose start time is aligned with the start of the day. 2712 function computeAlignedDayRange(timedRange) { 2713 let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; 2714 let start = startOfDay(timedRange.start); 2715 let end = addDays(start, dayCnt); 2716 return { start, end }; 2717 } 2718 // given a timed range, computes an all-day range based on how for the end date bleeds into the next day 2719 // TODO: give nextDayThreshold a default arg 2720 function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) { 2721 let startDay = null; 2722 let endDay = null; 2723 if (timedRange.end) { 2724 endDay = startOfDay(timedRange.end); 2725 let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` 2726 // If the end time is actually inclusively part of the next day and is equal to or
vendor: 14,931 bytes, lines 2727-3048
2727 // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. 2728 // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. 2729 if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { 2730 endDay = addDays(endDay, 1); 2731 } 2732 } 2733 if (timedRange.start) { 2734 startDay = startOfDay(timedRange.start); // the beginning of the day the range starts 2735 // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. 2736 if (endDay && endDay <= startDay) { 2737 endDay = addDays(startDay, 1); 2738 } 2739 } 2740 return { start: startDay, end: endDay }; 2741 } 2742 // spans from one day into another? 2743 function isMultiDayRange(range) { 2744 let visibleRange = computeVisibleDayRange(range); 2745 return diffDays(visibleRange.start, visibleRange.end) > 1; 2746 } 2747 function diffDates(date0, date1, dateEnv, largeUnit) { 2748 if (largeUnit === 'year') { 2749 return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); 2750 } 2751 if (largeUnit === 'month') { 2752 return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); 2753 } 2754 return diffDayAndTime(date0, date1); // returns a duration 2755 } 2756 2757 function reduceCurrentDate(currentDate, action) { 2758 switch (action.type) { 2759 case 'CHANGE_DATE': 2760 return action.dateMarker; 2761 default: 2762 return currentDate; 2763 } 2764 } 2765 function getInitialDate(options, dateEnv) { 2766 let initialDateInput = options.initialDate; 2767 // compute the initial ambig-timezone date 2768 if (initialDateInput != null) { 2769 return dateEnv.createMarker(initialDateInput); 2770 } 2771 return getNow(options.now, dateEnv); // getNow already returns unzoned 2772 } 2773 function getNow(nowInput, dateEnv) { 2774 if (typeof nowInput === 'function') { 2775 nowInput = nowInput(); 2776 } 2777 if (nowInput == null) { 2778 return dateEnv.createNowMarker(); 2779 } 2780 return dateEnv.createMarker(nowInput); 2781 } 2782 2783 class DateProfileGenerator { 2784 constructor(props) { 2785 this.props = props; 2786 this.nowDate = getNow(props.nowInput, props.dateEnv); 2787 this.initHiddenDays(); 2788 } 2789 /* Date Range Computation 2790 ------------------------------------------------------------------------------------------------------------------*/ 2791 // Builds a structure with info about what the dates/ranges will be for the "prev" view. 2792 buildPrev(currentDateProfile, currentDate, forceToValid) { 2793 let { dateEnv } = this.props; 2794 let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month 2795 currentDateProfile.dateIncrement); 2796 return this.build(prevDate, -1, forceToValid); 2797 } 2798 // Builds a structure with info about what the dates/ranges will be for the "next" view. 2799 buildNext(currentDateProfile, currentDate, forceToValid) { 2800 let { dateEnv } = this.props; 2801 let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month 2802 currentDateProfile.dateIncrement); 2803 return this.build(nextDate, 1, forceToValid); 2804 } 2805 // Builds a structure holding dates/ranges for rendering around the given date. 2806 // Optional direction param indicates whether the date is being incremented/decremented 2807 // from its previous value. decremented = -1, incremented = 1 (default). 2808 build(currentDate, direction, forceToValid = true) { 2809 let { props } = this; 2810 let validRange; 2811 let currentInfo; 2812 let isRangeAllDay; 2813 let renderRange; 2814 let activeRange; 2815 let isValid; 2816 validRange = this.buildValidRange(); 2817 validRange = this.trimHiddenDays(validRange); 2818 if (forceToValid) { 2819 currentDate = constrainMarkerToRange(currentDate, validRange); 2820 } 2821 currentInfo = this.buildCurrentRangeInfo(currentDate, direction); 2822 isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); 2823 renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); 2824 renderRange = this.trimHiddenDays(renderRange); 2825 activeRange = renderRange; 2826 if (!props.showNonCurrentDates) { 2827 activeRange = intersectRanges(activeRange, currentInfo.range); 2828 } 2829 activeRange = this.adjustActiveRange(activeRange); 2830 activeRange = intersectRanges(activeRange, validRange); // might return null 2831 // it's invalid if the originally requested date is not contained, 2832 // or if the range is completely outside of the valid range. 2833 isValid = rangesIntersect(currentInfo.range, validRange); 2834 // HACK: constrain to render-range so `currentDate` is more useful to view rendering 2835 if (!rangeContainsMarker(renderRange, currentDate)) { 2836 currentDate = renderRange.start; 2837 } 2838 return { 2839 currentDate, 2840 // constraint for where prev/next operations can go and where events can be dragged/resized to. 2841 // an object with optional start and end properties. 2842 validRange, 2843 // range the view is formally responsible for. 2844 // for example, a month view might have 1st-31st, excluding padded dates 2845 currentRange: currentInfo.range, 2846 // name of largest unit being displayed, like "month" or "week" 2847 currentRangeUnit: currentInfo.unit, 2848 isRangeAllDay, 2849 // dates that display events and accept drag-n-drop 2850 // will be `null` if no dates accept events 2851 activeRange, 2852 // date range with a rendered skeleton 2853 // includes not-active days that need some sort of DOM 2854 renderRange, 2855 // Duration object that denotes the first visible time of any given day 2856 slotMinTime: props.slotMinTime, 2857 // Duration object that denotes the exclusive visible end time of any given day 2858 slotMaxTime: props.slotMaxTime, 2859 isValid, 2860 // how far the current date will move for a prev/next operation 2861 dateIncrement: this.buildDateIncrement(currentInfo.duration), 2862 // pass a fallback (might be null) ^ 2863 }; 2864 } 2865 // Builds an object with optional start/end properties. 2866 // Indicates the minimum/maximum dates to display. 2867 // not responsible for trimming hidden days. 2868 buildValidRange() { 2869 let input = this.props.validRangeInput; 2870 let simpleInput = typeof input === 'function' 2871 ? input.call(this.props.calendarApi, this.nowDate) 2872 : input; 2873 return this.refineRange(simpleInput) || 2874 { start: null, end: null }; // completely open-ended 2875 } 2876 // Builds a structure with info about the "current" range, the range that is 2877 // highlighted as being the current month for example. 2878 // See build() for a description of `direction`. 2879 // Guaranteed to have `range` and `unit` properties. `duration` is optional. 2880 buildCurrentRangeInfo(date, direction) { 2881 let { props } = this; 2882 let duration = null; 2883 let unit = null; 2884 let range = null; 2885 let dayCount; 2886 if (props.duration) { 2887 duration = props.duration; 2888 unit = props.durationUnit; 2889 range = this.buildRangeFromDuration(date, direction, duration, unit); 2890 } 2891 else if ((dayCount = this.props.dayCount)) { 2892 unit = 'day'; 2893 range = this.buildRangeFromDayCount(date, direction, dayCount); 2894 } 2895 else if ((range = this.buildCustomVisibleRange(date))) { 2896 unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; 2897 } 2898 else { 2899 duration = this.getFallbackDuration(); 2900 unit = greatestDurationDenominator(duration).unit; 2901 range = this.buildRangeFromDuration(date, direction, duration, unit); 2902 } 2903 return { duration, unit, range }; 2904 } 2905 getFallbackDuration() { 2906 return createDuration({ day: 1 }); 2907 } 2908 // Returns a new activeRange to have time values (un-ambiguate) 2909 // slotMinTime or slotMaxTime causes the range to expand. 2910 adjustActiveRange(range) { 2911 let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props; 2912 let { start, end } = range; 2913 if (usesMinMaxTime) { 2914 // expand active range if slotMinTime is negative (why not when positive?) 2915 if (asRoughDays(slotMinTime) < 0) { 2916 start = startOfDay(start); // necessary? 2917 start = dateEnv.add(start, slotMinTime); 2918 } 2919 // expand active range if slotMaxTime is beyond one day (why not when negative?) 2920 if (asRoughDays(slotMaxTime) > 1) { 2921 end = startOfDay(end); // necessary? 2922 end = addDays(end, -1); 2923 end = dateEnv.add(end, slotMaxTime); 2924 } 2925 } 2926 return { start, end }; 2927 } 2928 // Builds the "current" range when it is specified as an explicit duration. 2929 // `unit` is the already-computed greatestDurationDenominator unit of duration. 2930 buildRangeFromDuration(date, direction, duration, unit) { 2931 let { dateEnv, dateAlignment } = this.props; 2932 let start; 2933 let end; 2934 let res; 2935 // compute what the alignment should be 2936 if (!dateAlignment) { 2937 let { dateIncrement } = this.props; 2938 if (dateIncrement) { 2939 // use the smaller of the two units 2940 if (asRoughMs(dateIncrement) < asRoughMs(duration)) { 2941 dateAlignment = greatestDurationDenominator(dateIncrement).unit; 2942 } 2943 else { 2944 dateAlignment = unit; 2945 } 2946 } 2947 else { 2948 dateAlignment = unit; 2949 } 2950 } 2951 // if the view displays a single day or smaller 2952 if (asRoughDays(duration) <= 1) { 2953 if (this.isHiddenDay(start)) { 2954 start = this.skipHiddenDays(start, direction); 2955 start = startOfDay(start); 2956 } 2957 } 2958 function computeRes() { 2959 start = dateEnv.startOf(date, dateAlignment); 2960 end = dateEnv.add(start, duration); 2961 res = { start, end }; 2962 } 2963 computeRes(); 2964 // if range is completely enveloped by hidden days, go past the hidden days 2965 if (!this.trimHiddenDays(res)) { 2966 date = this.skipHiddenDays(date, direction); 2967 computeRes(); 2968 } 2969 return res; 2970 } 2971 // Builds the "current" range when a dayCount is specified. 2972 buildRangeFromDayCount(date, direction, dayCount) { 2973 let { dateEnv, dateAlignment } = this.props; 2974 let runningCount = 0; 2975 let start = date; 2976 let end; 2977 if (dateAlignment) { 2978 start = dateEnv.startOf(start, dateAlignment); 2979 } 2980 start = startOfDay(start); 2981 start = this.skipHiddenDays(start, direction); 2982 end = start; 2983 do { 2984 end = addDays(end, 1); 2985 if (!this.isHiddenDay(end)) { 2986 runningCount += 1; 2987 } 2988 } while (runningCount < dayCount); 2989 return { start, end }; 2990 } 2991 // Builds a normalized range object for the "visible" range, 2992 // which is a way to define the currentRange and activeRange at the same time. 2993 buildCustomVisibleRange(date) { 2994 let { props } = this; 2995 let input = props.visibleRangeInput; 2996 let simpleInput = typeof input === 'function' 2997 ? input.call(props.calendarApi, props.dateEnv.toDate(date)) 2998 : input; 2999 let range = this.refineRange(simpleInput); 3000 if (range && (range.start == null || range.end == null)) { 3001 return null; 3002 } 3003 return range; 3004 } 3005 // Computes the range that will represent the element/cells for *rendering*, 3006 // but which may have voided days/times. 3007 // not responsible for trimming hidden days. 3008 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { 3009 return currentRange; 3010 } 3011 // Compute the duration value that should be added/substracted to the current date 3012 // when a prev/next operation happens. 3013 buildDateIncrement(fallback) { 3014 let { dateIncrement } = this.props; 3015 let customAlignment; 3016 if (dateIncrement) { 3017 return dateIncrement; 3018 } 3019 if ((customAlignment = this.props.dateAlignment)) { 3020 return createDuration(1, customAlignment); 3021 } 3022 if (fallback) { 3023 return fallback; 3024 } 3025 return createDuration({ days: 1 }); 3026 } 3027 refineRange(rangeInput) { 3028 if (rangeInput) { 3029 let range = parseRange(rangeInput, this.props.dateEnv); 3030 if (range) { 3031 range = computeVisibleDayRange(range); 3032 } 3033 return range; 3034 } 3035 return null; 3036 } 3037 /* Hidden Days 3038 ------------------------------------------------------------------------------------------------------------------*/ 3039 // Initializes internal variables related to calculating hidden days-of-week 3040 initHiddenDays() { 3041 let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden 3042 let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) 3043 let dayCnt = 0; 3044 let i; 3045 if (this.props.weekends === false) { 3046 hiddenDays.push(0, 6); // 0=sunday, 6=saturday 3047 } 3048 for (i = 0; i < 7; i += 1) {
vendor: 13,107 bytes, lines 3049-3368
3049 if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { 3050 dayCnt += 1; 3051 } 3052 } 3053 if (!dayCnt) { 3054 throw new Error('invalid hiddenDays'); // all days were hidden? bad. 3055 } 3056 this.isHiddenDayHash = isHiddenDayHash; 3057 } 3058 // Remove days from the beginning and end of the range that are computed as hidden. 3059 // If the whole range is trimmed off, returns null 3060 trimHiddenDays(range) { 3061 let { start, end } = range; 3062 if (start) { 3063 start = this.skipHiddenDays(start); 3064 } 3065 if (end) { 3066 end = this.skipHiddenDays(end, -1, true); 3067 } 3068 if (start == null || end == null || start < end) { 3069 return { start, end }; 3070 } 3071 return null; 3072 } 3073 // Is the current day hidden? 3074 // `day` is a day-of-week index (0-6), or a Date (used for UTC) 3075 isHiddenDay(day) { 3076 if (day instanceof Date) { 3077 day = day.getUTCDay(); 3078 } 3079 return this.isHiddenDayHash[day]; 3080 } 3081 // Incrementing the current day until it is no longer a hidden day, returning a copy. 3082 // DOES NOT CONSIDER validRange! 3083 // If the initial value of `date` is not a hidden day, don't do anything. 3084 // Pass `isExclusive` as `true` if you are dealing with an end date. 3085 // `inc` defaults to `1` (increment one day forward each time) 3086 skipHiddenDays(date, inc = 1, isExclusive = false) { 3087 while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { 3088 date = addDays(date, inc); 3089 } 3090 return date; 3091 } 3092 } 3093 3094 function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { 3095 return { 3096 instanceId: guid(), 3097 defId, 3098 range, 3099 forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, 3100 forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, 3101 }; 3102 } 3103 3104 function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { 3105 for (let i = 0; i < recurringTypes.length; i += 1) { 3106 let parsed = recurringTypes[i].parse(refined, dateEnv); 3107 if (parsed) { 3108 let { allDay } = refined; 3109 if (allDay == null) { 3110 allDay = defaultAllDay; 3111 if (allDay == null) { 3112 allDay = parsed.allDayGuess; 3113 if (allDay == null) { 3114 allDay = false; 3115 } 3116 } 3117 } 3118 return { 3119 allDay, 3120 duration: parsed.duration, 3121 typeData: parsed.typeData, 3122 typeId: i, 3123 }; 3124 } 3125 } 3126 return null; 3127 } 3128 function expandRecurring(eventStore, framingRange, context) { 3129 let { dateEnv, pluginHooks, options } = context; 3130 let { defs, instances } = eventStore; 3131 // remove existing recurring instances 3132 // TODO: bad. always expand events as a second step 3133 instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef); 3134 for (let defId in defs) { 3135 let def = defs[defId]; 3136 if (def.recurringDef) { 3137 let { duration } = def.recurringDef; 3138 if (!duration) { 3139 duration = def.allDay ? 3140 options.defaultAllDayEventDuration : 3141 options.defaultTimedEventDuration; 3142 } 3143 let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); 3144 for (let start of starts) { 3145 let instance = createEventInstance(defId, { 3146 start, 3147 end: dateEnv.add(start, duration), 3148 }); 3149 instances[instance.instanceId] = instance; 3150 } 3151 } 3152 } 3153 return { defs, instances }; 3154 } 3155 /* 3156 Event MUST have a recurringDef 3157 */ 3158 function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { 3159 let typeDef = recurringTypes[eventDef.recurringDef.typeId]; 3160 let markers = typeDef.expand(eventDef.recurringDef.typeData, { 3161 start: dateEnv.subtract(framingRange.start, duration), 3162 end: framingRange.end, 3163 }, dateEnv); 3164 // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to 3165 if (eventDef.allDay) { 3166 markers = markers.map(startOfDay); 3167 } 3168 return markers; 3169 } 3170 3171 const EVENT_NON_DATE_REFINERS = { 3172 id: String, 3173 groupId: String, 3174 title: String, 3175 url: String, 3176 interactive: Boolean, 3177 }; 3178 const EVENT_DATE_REFINERS = { 3179 start: identity, 3180 end: identity, 3181 date: identity, 3182 allDay: Boolean, 3183 }; 3184 const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); 3185 function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) { 3186 let { refined, extra } = refineEventDef(raw, context, refiners); 3187 let defaultAllDay = computeIsDefaultAllDay(eventSource, context); 3188 let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); 3189 if (recurringRes) { 3190 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap); 3191 def.recurringDef = { 3192 typeId: recurringRes.typeId, 3193 typeData: recurringRes.typeData, 3194 duration: recurringRes.duration, 3195 }; 3196 return { def, instance: null }; 3197 } 3198 let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); 3199 if (singleRes) { 3200 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap); 3201 let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); 3202 if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) { 3203 instance.instanceId = instanceIdMap[def.publicId]; 3204 } 3205 return { def, instance }; 3206 } 3207 return null; 3208 } 3209 function refineEventDef(raw, context, refiners = buildEventRefiners(context)) { 3210 return refineProps(raw, refiners); 3211 } 3212 function buildEventRefiners(context) { 3213 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); 3214 } 3215 /* 3216 Will NOT populate extendedProps with the leftover properties. 3217 Will NOT populate date-related props. 3218 */ 3219 function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) { 3220 let def = { 3221 title: refined.title || '', 3222 groupId: refined.groupId || '', 3223 publicId: refined.id || '', 3224 url: refined.url || '', 3225 recurringDef: null, 3226 defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(), 3227 sourceId, 3228 allDay, 3229 hasEnd, 3230 interactive: refined.interactive, 3231 ui: createEventUi(refined, context), 3232 extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra), 3233 }; 3234 for (let memberAdder of context.pluginHooks.eventDefMemberAdders) { 3235 Object.assign(def, memberAdder(refined)); 3236 } 3237 // help out EventImpl from having user modify props 3238 Object.freeze(def.ui.classNames); 3239 Object.freeze(def.extendedProps); 3240 return def; 3241 } 3242 function parseSingle(refined, defaultAllDay, context, allowOpenRange) { 3243 let { allDay } = refined; 3244 let startMeta; 3245 let startMarker = null; 3246 let hasEnd = false; 3247 let endMeta; 3248 let endMarker = null; 3249 let startInput = refined.start != null ? refined.start : refined.date; 3250 startMeta = context.dateEnv.createMarkerMeta(startInput); 3251 if (startMeta) { 3252 startMarker = startMeta.marker; 3253 } 3254 else if (!allowOpenRange) { 3255 return null; 3256 } 3257 if (refined.end != null) { 3258 endMeta = context.dateEnv.createMarkerMeta(refined.end); 3259 } 3260 if (allDay == null) { 3261 if (defaultAllDay != null) { 3262 allDay = defaultAllDay; 3263 } 3264 else { 3265 // fall back to the date props LAST 3266 allDay = (!startMeta || startMeta.isTimeUnspecified) && 3267 (!endMeta || endMeta.isTimeUnspecified); 3268 } 3269 } 3270 if (allDay && startMarker) { 3271 startMarker = startOfDay(startMarker); 3272 } 3273 if (endMeta) { 3274 endMarker = endMeta.marker; 3275 if (allDay) { 3276 endMarker = startOfDay(endMarker); 3277 } 3278 if (startMarker && endMarker <= startMarker) { 3279 endMarker = null; 3280 } 3281 } 3282 if (endMarker) { 3283 hasEnd = true; 3284 } 3285 else if (!allowOpenRange) { 3286 hasEnd = context.options.forceEventDuration || false; 3287 endMarker = context.dateEnv.add(startMarker, allDay ? 3288 context.options.defaultAllDayEventDuration : 3289 context.options.defaultTimedEventDuration); 3290 } 3291 return { 3292 allDay, 3293 hasEnd, 3294 range: { start: startMarker, end: endMarker }, 3295 forcedStartTzo: startMeta ? startMeta.forcedTzo : null, 3296 forcedEndTzo: endMeta ? endMeta.forcedTzo : null, 3297 }; 3298 } 3299 function computeIsDefaultAllDay(eventSource, context) { 3300 let res = null; 3301 if (eventSource) { 3302 res = eventSource.defaultAllDay; 3303 } 3304 if (res == null) { 3305 res = context.options.defaultAllDay; 3306 } 3307 return res; 3308 } 3309 3310 function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) { 3311 let eventStore = createEmptyEventStore(); 3312 let eventRefiners = buildEventRefiners(context); 3313 for (let rawEvent of rawEvents) { 3314 let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap); 3315 if (tuple) { 3316 eventTupleToStore(tuple, eventStore); 3317 } 3318 } 3319 return eventStore; 3320 } 3321 function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) { 3322 eventStore.defs[tuple.def.defId] = tuple.def; 3323 if (tuple.instance) { 3324 eventStore.instances[tuple.instance.instanceId] = tuple.instance; 3325 } 3326 return eventStore; 3327 } 3328 // retrieves events that have the same groupId as the instance specified by `instanceId` 3329 // or they are the same as the instance. 3330 // why might instanceId not be in the store? an event from another calendar? 3331 function getRelevantEvents(eventStore, instanceId) { 3332 let instance = eventStore.instances[instanceId]; 3333 if (instance) { 3334 let def = eventStore.defs[instance.defId]; 3335 // get events/instances with same group 3336 let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef)); 3337 // add the original 3338 // TODO: wish we could use eventTupleToStore or something like it 3339 newStore.defs[def.defId] = def; 3340 newStore.instances[instance.instanceId] = instance; 3341 return newStore; 3342 } 3343 return createEmptyEventStore(); 3344 } 3345 function isEventDefsGrouped(def0, def1) { 3346 return Boolean(def0.groupId && def0.groupId === def1.groupId); 3347 } 3348 function createEmptyEventStore() { 3349 return { defs: {}, instances: {} }; 3350 } 3351 function mergeEventStores(store0, store1) { 3352 return { 3353 defs: Object.assign(Object.assign({}, store0.defs), store1.defs), 3354 instances: Object.assign(Object.assign({}, store0.instances), store1.instances), 3355 }; 3356 } 3357 function filterEventStoreDefs(eventStore, filterFunc) { 3358 let defs = filterHash(eventStore.defs, filterFunc); 3359 let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists? 3360 )); 3361 return { defs, instances }; 3362 } 3363 function excludeSubEventStore(master, sub) { 3364 let { defs, instances } = master; 3365 let filteredDefs = {}; 3366 let filteredInstances = {}; 3367 for (let defId in defs) { 3368 if (!sub.defs[defId]) { // not explicitly excluded
vendor: 7,507 bytes, lines 3369-3553
3369 filteredDefs[defId] = defs[defId]; 3370 } 3371 } 3372 for (let instanceId in instances) { 3373 if (!sub.instances[instanceId] && // not explicitly excluded 3374 filteredDefs[instances[instanceId].defId] // def wasn't filtered away 3375 ) { 3376 filteredInstances[instanceId] = instances[instanceId]; 3377 } 3378 } 3379 return { 3380 defs: filteredDefs, 3381 instances: filteredInstances, 3382 }; 3383 } 3384 3385 function normalizeConstraint(input, context) { 3386 if (Array.isArray(input)) { 3387 return parseEvents(input, null, context, true); // allowOpenRange=true 3388 } 3389 if (typeof input === 'object' && input) { // non-null object 3390 return parseEvents([input], null, context, true); // allowOpenRange=true 3391 } 3392 if (input != null) { 3393 return String(input); 3394 } 3395 return null; 3396 } 3397 3398 function parseClassNames(raw) { 3399 if (Array.isArray(raw)) { 3400 return raw; 3401 } 3402 if (typeof raw === 'string') { 3403 return raw.split(/\s+/); 3404 } 3405 return []; 3406 } 3407 3408 // TODO: better called "EventSettings" or "EventConfig" 3409 // TODO: move this file into structs 3410 // TODO: separate constraint/overlap/allow, because selection uses only that, not other props 3411 const EVENT_UI_REFINERS = { 3412 display: String, 3413 editable: Boolean, 3414 startEditable: Boolean, 3415 durationEditable: Boolean, 3416 constraint: identity, 3417 overlap: identity, 3418 allow: identity, 3419 className: parseClassNames, 3420 classNames: parseClassNames, 3421 color: String, 3422 backgroundColor: String, 3423 borderColor: String, 3424 textColor: String, 3425 }; 3426 const EMPTY_EVENT_UI = { 3427 display: null, 3428 startEditable: null, 3429 durationEditable: null, 3430 constraints: [], 3431 overlap: null, 3432 allows: [], 3433 backgroundColor: '', 3434 borderColor: '', 3435 textColor: '', 3436 classNames: [], 3437 }; 3438 function createEventUi(refined, context) { 3439 let constraint = normalizeConstraint(refined.constraint, context); 3440 return { 3441 display: refined.display || null, 3442 startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, 3443 durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, 3444 constraints: constraint != null ? [constraint] : [], 3445 overlap: refined.overlap != null ? refined.overlap : null, 3446 allows: refined.allow != null ? [refined.allow] : [], 3447 backgroundColor: refined.backgroundColor || refined.color || '', 3448 borderColor: refined.borderColor || refined.color || '', 3449 textColor: refined.textColor || '', 3450 classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural 3451 }; 3452 } 3453 // TODO: prevent against problems with <2 args! 3454 function combineEventUis(uis) { 3455 return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); 3456 } 3457 function combineTwoEventUis(item0, item1) { 3458 return { 3459 display: item1.display != null ? item1.display : item0.display, 3460 startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, 3461 durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, 3462 constraints: item0.constraints.concat(item1.constraints), 3463 overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, 3464 allows: item0.allows.concat(item1.allows), 3465 backgroundColor: item1.backgroundColor || item0.backgroundColor, 3466 borderColor: item1.borderColor || item0.borderColor, 3467 textColor: item1.textColor || item0.textColor, 3468 classNames: item0.classNames.concat(item1.classNames), 3469 }; 3470 } 3471 3472 const EVENT_SOURCE_REFINERS = { 3473 id: String, 3474 defaultAllDay: Boolean, 3475 url: String, 3476 format: String, 3477 events: identity, 3478 eventDataTransform: identity, 3479 // for any network-related sources 3480 success: identity, 3481 failure: identity, 3482 }; 3483 function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) { 3484 let rawObj; 3485 if (typeof raw === 'string') { 3486 rawObj = { url: raw }; 3487 } 3488 else if (typeof raw === 'function' || Array.isArray(raw)) { 3489 rawObj = { events: raw }; 3490 } 3491 else if (typeof raw === 'object' && raw) { // not null 3492 rawObj = raw; 3493 } 3494 if (rawObj) { 3495 let { refined, extra } = refineProps(rawObj, refiners); 3496 let metaRes = buildEventSourceMeta(refined, context); 3497 if (metaRes) { 3498 return { 3499 _raw: raw, 3500 isFetching: false, 3501 latestFetchId: '', 3502 fetchRange: null, 3503 defaultAllDay: refined.defaultAllDay, 3504 eventDataTransform: refined.eventDataTransform, 3505 success: refined.success, 3506 failure: refined.failure, 3507 publicId: refined.id || '', 3508 sourceId: guid(), 3509 sourceDefId: metaRes.sourceDefId, 3510 meta: metaRes.meta, 3511 ui: createEventUi(refined, context), 3512 extendedProps: extra, 3513 }; 3514 } 3515 } 3516 return null; 3517 } 3518 function buildEventSourceRefiners(context) { 3519 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners); 3520 } 3521 function buildEventSourceMeta(raw, context) { 3522 let defs = context.pluginHooks.eventSourceDefs; 3523 for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence 3524 let def = defs[i]; 3525 let meta = def.parseMeta(raw); 3526 if (meta) { 3527 return { sourceDefId: i, meta }; 3528 } 3529 } 3530 return null; 3531 } 3532 3533 function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { 3534 switch (action.type) { 3535 case 'RECEIVE_EVENTS': // raw 3536 return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); 3537 case 'RESET_RAW_EVENTS': 3538 return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context); 3539 case 'ADD_EVENTS': // already parsed, but not expanded 3540 return addEvent(eventStore, action.eventStore, // new ones 3541 dateProfile ? dateProfile.activeRange : null, context); 3542 case 'RESET_EVENTS': 3543 return action.eventStore; 3544 case 'MERGE_EVENTS': // already parsed and expanded 3545 return mergeEventStores(eventStore, action.eventStore); 3546 case 'PREV': // TODO: how do we track all actions that affect dateProfile :( 3547 case 'NEXT': 3548 case 'CHANGE_DATE': 3549 case 'CHANGE_VIEW_TYPE': 3550 if (dateProfile) { 3551 return expandRecurring(eventStore, dateProfile.activeRange, context); 3552 } 3553 return eventStore;
vendor: 13,249 bytes, lines 3554-3867
3554 case 'REMOVE_EVENTS': 3555 return excludeSubEventStore(eventStore, action.eventStore); 3556 case 'REMOVE_EVENT_SOURCE': 3557 return excludeEventsBySourceId(eventStore, action.sourceId); 3558 case 'REMOVE_ALL_EVENT_SOURCES': 3559 return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id 3560 )); 3561 case 'REMOVE_ALL_EVENTS': 3562 return createEmptyEventStore(); 3563 default: 3564 return eventStore; 3565 } 3566 } 3567 function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { 3568 if (eventSource && // not already removed 3569 fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources 3570 ) { 3571 let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); 3572 if (fetchRange) { 3573 subset = expandRecurring(subset, fetchRange, context); 3574 } 3575 return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); 3576 } 3577 return eventStore; 3578 } 3579 function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) { 3580 const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore); 3581 let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap); 3582 return expandRecurring(newEventStore, activeRange, context); 3583 } 3584 function transformRawEvents(rawEvents, eventSource, context) { 3585 let calEachTransform = context.options.eventDataTransform; 3586 let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; 3587 if (sourceEachTransform) { 3588 rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); 3589 } 3590 if (calEachTransform) { 3591 rawEvents = transformEachRawEvent(rawEvents, calEachTransform); 3592 } 3593 return rawEvents; 3594 } 3595 function transformEachRawEvent(rawEvents, func) { 3596 let refinedEvents; 3597 if (!func) { 3598 refinedEvents = rawEvents; 3599 } 3600 else { 3601 refinedEvents = []; 3602 for (let rawEvent of rawEvents) { 3603 let refinedEvent = func(rawEvent); 3604 if (refinedEvent) { 3605 refinedEvents.push(refinedEvent); 3606 } 3607 else if (refinedEvent == null) { 3608 refinedEvents.push(rawEvent); 3609 } // if a different falsy value, do nothing 3610 } 3611 } 3612 return refinedEvents; 3613 } 3614 function addEvent(eventStore, subset, expandRange, context) { 3615 if (expandRange) { 3616 subset = expandRecurring(subset, expandRange, context); 3617 } 3618 return mergeEventStores(eventStore, subset); 3619 } 3620 function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { 3621 let { defs } = eventStore; 3622 let instances = mapHash(eventStore.instances, (instance) => { 3623 let def = defs[instance.defId]; 3624 if (def.allDay) { 3625 return instance; // isn't dependent on timezone 3626 } 3627 return Object.assign(Object.assign({}, instance), { range: { 3628 start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), 3629 end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), 3630 }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); 3631 }); 3632 return { defs, instances }; 3633 } 3634 function excludeEventsBySourceId(eventStore, sourceId) { 3635 return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId); 3636 } 3637 // QUESTION: why not just return instances? do a general object-property-exclusion util 3638 function excludeInstances(eventStore, removals) { 3639 return { 3640 defs: eventStore.defs, 3641 instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]), 3642 }; 3643 } 3644 function buildPublicIdMaps(eventStore) { 3645 const { defs, instances } = eventStore; 3646 const defIdMap = {}; 3647 const instanceIdMap = {}; 3648 for (let defId in defs) { 3649 const def = defs[defId]; 3650 const { publicId } = def; 3651 if (publicId) { 3652 defIdMap[publicId] = defId; 3653 } 3654 } 3655 for (let instanceId in instances) { 3656 const instance = instances[instanceId]; 3657 const def = defs[instance.defId]; 3658 const { publicId } = def; 3659 if (publicId) { 3660 instanceIdMap[publicId] = instanceId; 3661 } 3662 } 3663 return { defIdMap, instanceIdMap }; 3664 } 3665 3666 class Emitter { 3667 constructor() { 3668 this.handlers = {}; 3669 this.thisContext = null; 3670 } 3671 setThisContext(thisContext) { 3672 this.thisContext = thisContext; 3673 } 3674 setOptions(options) { 3675 this.options = options; 3676 } 3677 on(type, handler) { 3678 addToHash(this.handlers, type, handler); 3679 } 3680 off(type, handler) { 3681 removeFromHash(this.handlers, type, handler); 3682 } 3683 trigger(type, ...args) { 3684 let attachedHandlers = this.handlers[type] || []; 3685 let optionHandler = this.options && this.options[type]; 3686 let handlers = [].concat(optionHandler || [], attachedHandlers); 3687 for (let handler of handlers) { 3688 handler.apply(this.thisContext, args); 3689 } 3690 } 3691 hasHandlers(type) { 3692 return Boolean((this.handlers[type] && this.handlers[type].length) || 3693 (this.options && this.options[type])); 3694 } 3695 } 3696 function addToHash(hash, type, handler) { 3697 (hash[type] || (hash[type] = [])) 3698 .push(handler); 3699 } 3700 function removeFromHash(hash, type, handler) { 3701 if (handler) { 3702 if (hash[type]) { 3703 hash[type] = hash[type].filter((func) => func !== handler); 3704 } 3705 } 3706 else { 3707 delete hash[type]; // remove all handler funcs for this type 3708 } 3709 } 3710 3711 const DEF_DEFAULTS = { 3712 startTime: '09:00', 3713 endTime: '17:00', 3714 daysOfWeek: [1, 2, 3, 4, 5], 3715 display: 'inverse-background', 3716 classNames: 'fc-non-business', 3717 groupId: '_businessHours', // so multiple defs get grouped 3718 }; 3719 /* 3720 TODO: pass around as EventDefHash!!! 3721 */ 3722 function parseBusinessHours(input, context) { 3723 return parseEvents(refineInputs(input), null, context); 3724 } 3725 function refineInputs(input) { 3726 let rawDefs; 3727 if (input === true) { 3728 rawDefs = [{}]; // will get DEF_DEFAULTS verbatim 3729 } 3730 else if (Array.isArray(input)) { 3731 // if specifying an array, every sub-definition NEEDS a day-of-week 3732 rawDefs = input.filter((rawDef) => rawDef.daysOfWeek); 3733 } 3734 else if (typeof input === 'object' && input) { // non-null object 3735 rawDefs = [input]; 3736 } 3737 else { // is probably false 3738 rawDefs = []; 3739 } 3740 rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef))); 3741 return rawDefs; 3742 } 3743 3744 function triggerDateSelect(selection, pev, context) { 3745 context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); 3746 } 3747 function triggerDateUnselect(pev, context) { 3748 context.emitter.trigger('unselect', { 3749 jsEvent: pev ? pev.origEvent : null, 3750 view: context.viewApi || context.calendarApi.view, 3751 }); 3752 } 3753 function buildDateSpanApiWithContext(dateSpan, context) { 3754 let props = {}; 3755 for (let transform of context.pluginHooks.dateSpanTransforms) { 3756 Object.assign(props, transform(dateSpan, context)); 3757 } 3758 Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); 3759 return props; 3760 } 3761 // Given an event's allDay status and start date, return what its fallback end date should be. 3762 // TODO: rename to computeDefaultEventEnd 3763 function getDefaultEventEnd(allDay, marker, context) { 3764 let { dateEnv, options } = context; 3765 let end = marker; 3766 if (allDay) { 3767 end = startOfDay(end); 3768 end = dateEnv.add(end, options.defaultAllDayEventDuration); 3769 } 3770 else { 3771 end = dateEnv.add(end, options.defaultTimedEventDuration); 3772 } 3773 return end; 3774 } 3775 3776 // applies the mutation to ALL defs/instances within the event store 3777 function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { 3778 let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); 3779 let dest = createEmptyEventStore(); 3780 for (let defId in eventStore.defs) { 3781 let def = eventStore.defs[defId]; 3782 dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); 3783 } 3784 for (let instanceId in eventStore.instances) { 3785 let instance = eventStore.instances[instanceId]; 3786 let def = dest.defs[instance.defId]; // important to grab the newly modified def 3787 dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); 3788 } 3789 return dest; 3790 } 3791 function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { 3792 let standardProps = mutation.standardProps || {}; 3793 // if hasEnd has not been specified, guess a good value based on deltas. 3794 // if duration will change, there's no way the default duration will persist, 3795 // and thus, we need to mark the event as having a real end 3796 if (standardProps.hasEnd == null && 3797 eventConfig.durationEditable && 3798 (mutation.startDelta || mutation.endDelta)) { 3799 standardProps.hasEnd = true; // TODO: is this mutation okay? 3800 } 3801 let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) }); 3802 if (mutation.extendedProps) { 3803 copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps); 3804 } 3805 for (let applier of context.pluginHooks.eventDefMutationAppliers) { 3806 applier(copy, mutation, context); 3807 } 3808 if (!copy.hasEnd && context.options.forceEventDuration) { 3809 copy.hasEnd = true; 3810 } 3811 return copy; 3812 } 3813 function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef 3814 eventConfig, mutation, context) { 3815 let { dateEnv } = context; 3816 let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; 3817 let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; 3818 let copy = Object.assign({}, eventInstance); 3819 if (forceAllDay) { 3820 copy.range = computeAlignedDayRange(copy.range); 3821 } 3822 if (mutation.datesDelta && eventConfig.startEditable) { 3823 copy.range = { 3824 start: dateEnv.add(copy.range.start, mutation.datesDelta), 3825 end: dateEnv.add(copy.range.end, mutation.datesDelta), 3826 }; 3827 } 3828 if (mutation.startDelta && eventConfig.durationEditable) { 3829 copy.range = { 3830 start: dateEnv.add(copy.range.start, mutation.startDelta), 3831 end: copy.range.end, 3832 }; 3833 } 3834 if (mutation.endDelta && eventConfig.durationEditable) { 3835 copy.range = { 3836 start: copy.range.start, 3837 end: dateEnv.add(copy.range.end, mutation.endDelta), 3838 }; 3839 } 3840 if (clearEnd) { 3841 copy.range = { 3842 start: copy.range.start, 3843 end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), 3844 }; 3845 } 3846 // in case event was all-day but the supplied deltas were not 3847 // better util for this? 3848 if (eventDef.allDay) { 3849 copy.range = { 3850 start: startOfDay(copy.range.start), 3851 end: startOfDay(copy.range.end), 3852 }; 3853 } 3854 // handle invalid durations 3855 if (copy.range.end < copy.range.start) { 3856 copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); 3857 } 3858 return copy; 3859 } 3860 3861 class EventSourceImpl { 3862 constructor(context, internalEventSource) { 3863 this.context = context; 3864 this.internalEventSource = internalEventSource; 3865 } 3866 remove() { 3867 this.context.dispatch({
vendor: 26,480 bytes, lines 3868-4535
3868 type: 'REMOVE_EVENT_SOURCE', 3869 sourceId: this.internalEventSource.sourceId, 3870 }); 3871 } 3872 refetch() { 3873 this.context.dispatch({ 3874 type: 'FETCH_EVENT_SOURCES', 3875 sourceIds: [this.internalEventSource.sourceId], 3876 isRefetch: true, 3877 }); 3878 } 3879 get id() { 3880 return this.internalEventSource.publicId; 3881 } 3882 get url() { 3883 return this.internalEventSource.meta.url; 3884 } 3885 get format() { 3886 return this.internalEventSource.meta.format; // TODO: bad. not guaranteed 3887 } 3888 } 3889 3890 class EventImpl { 3891 // instance will be null if expressing a recurring event that has no current instances, 3892 // OR if trying to validate an incoming external event that has no dates assigned 3893 constructor(context, def, instance) { 3894 this._context = context; 3895 this._def = def; 3896 this._instance = instance || null; 3897 } 3898 /* 3899 TODO: make event struct more responsible for this 3900 */ 3901 setProp(name, val) { 3902 if (name in EVENT_DATE_REFINERS) { 3903 console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); 3904 // TODO: make proper aliasing system? 3905 } 3906 else if (name === 'id') { 3907 val = EVENT_NON_DATE_REFINERS[name](val); 3908 this.mutate({ 3909 standardProps: { publicId: val }, // hardcoded internal name 3910 }); 3911 } 3912 else if (name in EVENT_NON_DATE_REFINERS) { 3913 val = EVENT_NON_DATE_REFINERS[name](val); 3914 this.mutate({ 3915 standardProps: { [name]: val }, 3916 }); 3917 } 3918 else if (name in EVENT_UI_REFINERS) { 3919 let ui = EVENT_UI_REFINERS[name](val); 3920 if (name === 'color') { 3921 ui = { backgroundColor: val, borderColor: val }; 3922 } 3923 else if (name === 'editable') { 3924 ui = { startEditable: val, durationEditable: val }; 3925 } 3926 else { 3927 ui = { [name]: val }; 3928 } 3929 this.mutate({ 3930 standardProps: { ui }, 3931 }); 3932 } 3933 else { 3934 console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`); 3935 } 3936 } 3937 setExtendedProp(name, val) { 3938 this.mutate({ 3939 extendedProps: { [name]: val }, 3940 }); 3941 } 3942 setStart(startInput, options = {}) { 3943 let { dateEnv } = this._context; 3944 let start = dateEnv.createMarker(startInput); 3945 if (start && this._instance) { // TODO: warning if parsed bad 3946 let instanceRange = this._instance.range; 3947 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? 3948 if (options.maintainDuration) { 3949 this.mutate({ datesDelta: startDelta }); 3950 } 3951 else { 3952 this.mutate({ startDelta }); 3953 } 3954 } 3955 } 3956 setEnd(endInput, options = {}) { 3957 let { dateEnv } = this._context; 3958 let end; 3959 if (endInput != null) { 3960 end = dateEnv.createMarker(endInput); 3961 if (!end) { 3962 return; // TODO: warning if parsed bad 3963 } 3964 } 3965 if (this._instance) { 3966 if (end) { 3967 let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); 3968 this.mutate({ endDelta }); 3969 } 3970 else { 3971 this.mutate({ standardProps: { hasEnd: false } }); 3972 } 3973 } 3974 } 3975 setDates(startInput, endInput, options = {}) { 3976 let { dateEnv } = this._context; 3977 let standardProps = { allDay: options.allDay }; 3978 let start = dateEnv.createMarker(startInput); 3979 let end; 3980 if (!start) { 3981 return; // TODO: warning if parsed bad 3982 } 3983 if (endInput != null) { 3984 end = dateEnv.createMarker(endInput); 3985 if (!end) { // TODO: warning if parsed bad 3986 return; 3987 } 3988 } 3989 if (this._instance) { 3990 let instanceRange = this._instance.range; 3991 // when computing the diff for an event being converted to all-day, 3992 // compute diff off of the all-day values the way event-mutation does. 3993 if (options.allDay === true) { 3994 instanceRange = computeAlignedDayRange(instanceRange); 3995 } 3996 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); 3997 if (end) { 3998 let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); 3999 if (durationsEqual(startDelta, endDelta)) { 4000 this.mutate({ datesDelta: startDelta, standardProps }); 4001 } 4002 else { 4003 this.mutate({ startDelta, endDelta, standardProps }); 4004 } 4005 } 4006 else { // means "clear the end" 4007 standardProps.hasEnd = false; 4008 this.mutate({ datesDelta: startDelta, standardProps }); 4009 } 4010 } 4011 } 4012 moveStart(deltaInput) { 4013 let delta = createDuration(deltaInput); 4014 if (delta) { // TODO: warning if parsed bad 4015 this.mutate({ startDelta: delta }); 4016 } 4017 } 4018 moveEnd(deltaInput) { 4019 let delta = createDuration(deltaInput); 4020 if (delta) { // TODO: warning if parsed bad 4021 this.mutate({ endDelta: delta }); 4022 } 4023 } 4024 moveDates(deltaInput) { 4025 let delta = createDuration(deltaInput); 4026 if (delta) { // TODO: warning if parsed bad 4027 this.mutate({ datesDelta: delta }); 4028 } 4029 } 4030 setAllDay(allDay, options = {}) { 4031 let standardProps = { allDay }; 4032 let { maintainDuration } = options; 4033 if (maintainDuration == null) { 4034 maintainDuration = this._context.options.allDayMaintainDuration; 4035 } 4036 if (this._def.allDay !== allDay) { 4037 standardProps.hasEnd = maintainDuration; 4038 } 4039 this.mutate({ standardProps }); 4040 } 4041 formatRange(formatInput) { 4042 let { dateEnv } = this._context; 4043 let instance = this._instance; 4044 let formatter = createFormatter(formatInput); 4045 if (this._def.hasEnd) { 4046 return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { 4047 forcedStartTzo: instance.forcedStartTzo, 4048 forcedEndTzo: instance.forcedEndTzo, 4049 }); 4050 } 4051 return dateEnv.format(instance.range.start, formatter, { 4052 forcedTzo: instance.forcedStartTzo, 4053 }); 4054 } 4055 mutate(mutation) { 4056 let instance = this._instance; 4057 if (instance) { 4058 let def = this._def; 4059 let context = this._context; 4060 let { eventStore } = context.getCurrentData(); 4061 let relevantEvents = getRelevantEvents(eventStore, instance.instanceId); 4062 let eventConfigBase = { 4063 '': { 4064 display: '', 4065 startEditable: true, 4066 durationEditable: true, 4067 constraints: [], 4068 overlap: null, 4069 allows: [], 4070 backgroundColor: '', 4071 borderColor: '', 4072 textColor: '', 4073 classNames: [], 4074 }, 4075 }; 4076 relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context); 4077 let oldEvent = new EventImpl(context, def, instance); // snapshot 4078 this._def = relevantEvents.defs[def.defId]; 4079 this._instance = relevantEvents.instances[instance.instanceId]; 4080 context.dispatch({ 4081 type: 'MERGE_EVENTS', 4082 eventStore: relevantEvents, 4083 }); 4084 context.emitter.trigger('eventChange', { 4085 oldEvent, 4086 event: this, 4087 relatedEvents: buildEventApis(relevantEvents, context, instance), 4088 revert() { 4089 context.dispatch({ 4090 type: 'RESET_EVENTS', 4091 eventStore, // the ORIGINAL store 4092 }); 4093 }, 4094 }); 4095 } 4096 } 4097 remove() { 4098 let context = this._context; 4099 let asStore = eventApiToStore(this); 4100 context.dispatch({ 4101 type: 'REMOVE_EVENTS', 4102 eventStore: asStore, 4103 }); 4104 context.emitter.trigger('eventRemove', { 4105 event: this, 4106 relatedEvents: [], 4107 revert() { 4108 context.dispatch({ 4109 type: 'MERGE_EVENTS', 4110 eventStore: asStore, 4111 }); 4112 }, 4113 }); 4114 } 4115 get source() { 4116 let { sourceId } = this._def; 4117 if (sourceId) { 4118 return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]); 4119 } 4120 return null; 4121 } 4122 get start() { 4123 return this._instance ? 4124 this._context.dateEnv.toDate(this._instance.range.start) : 4125 null; 4126 } 4127 get end() { 4128 return (this._instance && this._def.hasEnd) ? 4129 this._context.dateEnv.toDate(this._instance.range.end) : 4130 null; 4131 } 4132 get startStr() { 4133 let instance = this._instance; 4134 if (instance) { 4135 return this._context.dateEnv.formatIso(instance.range.start, { 4136 omitTime: this._def.allDay, 4137 forcedTzo: instance.forcedStartTzo, 4138 }); 4139 } 4140 return ''; 4141 } 4142 get endStr() { 4143 let instance = this._instance; 4144 if (instance && this._def.hasEnd) { 4145 return this._context.dateEnv.formatIso(instance.range.end, { 4146 omitTime: this._def.allDay, 4147 forcedTzo: instance.forcedEndTzo, 4148 }); 4149 } 4150 return ''; 4151 } 4152 // computable props that all access the def 4153 // TODO: find a TypeScript-compatible way to do this at scale 4154 get id() { return this._def.publicId; } 4155 get groupId() { return this._def.groupId; } 4156 get allDay() { return this._def.allDay; } 4157 get title() { return this._def.title; } 4158 get url() { return this._def.url; } 4159 get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier 4160 get startEditable() { return this._def.ui.startEditable; } 4161 get durationEditable() { return this._def.ui.durationEditable; } 4162 get constraint() { return this._def.ui.constraints[0] || null; } 4163 get overlap() { return this._def.ui.overlap; } 4164 get allow() { return this._def.ui.allows[0] || null; } 4165 get backgroundColor() { return this._def.ui.backgroundColor; } 4166 get borderColor() { return this._def.ui.borderColor; } 4167 get textColor() { return this._def.ui.textColor; } 4168 // NOTE: user can't modify these because Object.freeze was called in event-def parsing 4169 get classNames() { return this._def.ui.classNames; } 4170 get extendedProps() { return this._def.extendedProps; } 4171 toPlainObject(settings = {}) { 4172 let def = this._def; 4173 let { ui } = def; 4174 let { startStr, endStr } = this; 4175 let res = { 4176 allDay: def.allDay, 4177 }; 4178 if (def.title) { 4179 res.title = def.title; 4180 } 4181 if (startStr) { 4182 res.start = startStr; 4183 } 4184 if (endStr) { 4185 res.end = endStr; 4186 } 4187 if (def.publicId) { 4188 res.id = def.publicId; 4189 } 4190 if (def.groupId) { 4191 res.groupId = def.groupId; 4192 } 4193 if (def.url) { 4194 res.url = def.url; 4195 } 4196 if (ui.display && ui.display !== 'auto') { 4197 res.display = ui.display; 4198 } 4199 // TODO: what about recurring-event properties??? 4200 // TODO: include startEditable/durationEditable/constraint/overlap/allow 4201 if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { 4202 res.color = ui.backgroundColor; 4203 } 4204 else { 4205 if (ui.backgroundColor) { 4206 res.backgroundColor = ui.backgroundColor; 4207 } 4208 if (ui.borderColor) { 4209 res.borderColor = ui.borderColor; 4210 } 4211 } 4212 if (ui.textColor) { 4213 res.textColor = ui.textColor; 4214 } 4215 if (ui.classNames.length) { 4216 res.classNames = ui.classNames; 4217 } 4218 if (Object.keys(def.extendedProps).length) { 4219 if (settings.collapseExtendedProps) { 4220 Object.assign(res, def.extendedProps); 4221 } 4222 else { 4223 res.extendedProps = def.extendedProps; 4224 } 4225 } 4226 return res; 4227 } 4228 toJSON() { 4229 return this.toPlainObject(); 4230 } 4231 } 4232 function eventApiToStore(eventApi) { 4233 let def = eventApi._def; 4234 let instance = eventApi._instance; 4235 return { 4236 defs: { [def.defId]: def }, 4237 instances: instance 4238 ? { [instance.instanceId]: instance } 4239 : {}, 4240 }; 4241 } 4242 function buildEventApis(eventStore, context, excludeInstance) { 4243 let { defs, instances } = eventStore; 4244 let eventApis = []; 4245 let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; 4246 for (let id in instances) { 4247 let instance = instances[id]; 4248 let def = defs[instance.defId]; 4249 if (instance.instanceId !== excludeInstanceId) { 4250 eventApis.push(new EventImpl(context, def, instance)); 4251 } 4252 } 4253 return eventApis; 4254 } 4255 4256 /* 4257 Specifying nextDayThreshold signals that all-day ranges should be sliced. 4258 */ 4259 function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { 4260 let inverseBgByGroupId = {}; 4261 let inverseBgByDefId = {}; 4262 let defByGroupId = {}; 4263 let bgRanges = []; 4264 let fgRanges = []; 4265 let eventUis = compileEventUis(eventStore.defs, eventUiBases); 4266 for (let defId in eventStore.defs) { 4267 let def = eventStore.defs[defId]; 4268 let ui = eventUis[def.defId]; 4269 if (ui.display === 'inverse-background') { 4270 if (def.groupId) { 4271 inverseBgByGroupId[def.groupId] = []; 4272 if (!defByGroupId[def.groupId]) { 4273 defByGroupId[def.groupId] = def; 4274 } 4275 } 4276 else { 4277 inverseBgByDefId[defId] = []; 4278 } 4279 } 4280 } 4281 for (let instanceId in eventStore.instances) { 4282 let instance = eventStore.instances[instanceId]; 4283 let def = eventStore.defs[instance.defId]; 4284 let ui = eventUis[def.defId]; 4285 let origRange = instance.range; 4286 let normalRange = (!def.allDay && nextDayThreshold) ? 4287 computeVisibleDayRange(origRange, nextDayThreshold) : 4288 origRange; 4289 let slicedRange = intersectRanges(normalRange, framingRange); 4290 if (slicedRange) { 4291 if (ui.display === 'inverse-background') { 4292 if (def.groupId) { 4293 inverseBgByGroupId[def.groupId].push(slicedRange); 4294 } 4295 else { 4296 inverseBgByDefId[instance.defId].push(slicedRange); 4297 } 4298 } 4299 else if (ui.display !== 'none') { 4300 (ui.display === 'background' ? bgRanges : fgRanges).push({ 4301 def, 4302 ui, 4303 instance, 4304 range: slicedRange, 4305 isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), 4306 isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), 4307 }); 4308 } 4309 } 4310 } 4311 for (let groupId in inverseBgByGroupId) { // BY GROUP 4312 let ranges = inverseBgByGroupId[groupId]; 4313 let invertedRanges = invertRanges(ranges, framingRange); 4314 for (let invertedRange of invertedRanges) { 4315 let def = defByGroupId[groupId]; 4316 let ui = eventUis[def.defId]; 4317 bgRanges.push({ 4318 def, 4319 ui, 4320 instance: null, 4321 range: invertedRange, 4322 isStart: false, 4323 isEnd: false, 4324 }); 4325 } 4326 } 4327 for (let defId in inverseBgByDefId) { 4328 let ranges = inverseBgByDefId[defId]; 4329 let invertedRanges = invertRanges(ranges, framingRange); 4330 for (let invertedRange of invertedRanges) { 4331 bgRanges.push({ 4332 def: eventStore.defs[defId], 4333 ui: eventUis[defId], 4334 instance: null, 4335 range: invertedRange, 4336 isStart: false, 4337 isEnd: false, 4338 }); 4339 } 4340 } 4341 return { bg: bgRanges, fg: fgRanges }; 4342 } 4343 function hasBgRendering(def) { 4344 return def.ui.display === 'background' || def.ui.display === 'inverse-background'; 4345 } 4346 function setElSeg(el, seg) { 4347 el.fcSeg = seg; 4348 } 4349 function getElSeg(el) { 4350 return el.fcSeg || 4351 el.parentNode.fcSeg || // for the harness 4352 null; 4353 } 4354 // event ui computation 4355 function compileEventUis(eventDefs, eventUiBases) { 4356 return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases)); 4357 } 4358 function compileEventUi(eventDef, eventUiBases) { 4359 let uis = []; 4360 if (eventUiBases['']) { 4361 uis.push(eventUiBases['']); 4362 } 4363 if (eventUiBases[eventDef.defId]) { 4364 uis.push(eventUiBases[eventDef.defId]); 4365 } 4366 uis.push(eventDef.ui); 4367 return combineEventUis(uis); 4368 } 4369 function sortEventSegs(segs, eventOrderSpecs) { 4370 let objs = segs.map(buildSegCompareObj); 4371 objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs)); 4372 return objs.map((c) => c._seg); 4373 } 4374 // returns a object with all primitive props that can be compared 4375 function buildSegCompareObj(seg) { 4376 let { eventRange } = seg; 4377 let eventDef = eventRange.def; 4378 let range = eventRange.instance ? eventRange.instance.range : eventRange.range; 4379 let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events 4380 let end = range.end ? range.end.valueOf() : 0; // " 4381 return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start, 4382 end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); 4383 } 4384 function computeSegDraggable(seg, context) { 4385 let { pluginHooks } = context; 4386 let transformers = pluginHooks.isDraggableTransformers; 4387 let { def, ui } = seg.eventRange; 4388 let val = ui.startEditable; 4389 for (let transformer of transformers) { 4390 val = transformer(val, def, ui, context); 4391 } 4392 return val; 4393 } 4394 function computeSegStartResizable(seg, context) { 4395 return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; 4396 } 4397 function computeSegEndResizable(seg, context) { 4398 return seg.isEnd && seg.eventRange.ui.durationEditable; 4399 } 4400 function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true 4401 defaultDisplayEventEnd, // defaults to true 4402 startOverride, endOverride) { 4403 let { dateEnv, options } = context; 4404 let { displayEventTime, displayEventEnd } = options; 4405 let eventDef = seg.eventRange.def; 4406 let eventInstance = seg.eventRange.instance; 4407 if (displayEventTime == null) { 4408 displayEventTime = defaultDisplayEventTime !== false; 4409 } 4410 if (displayEventEnd == null) { 4411 displayEventEnd = defaultDisplayEventEnd !== false; 4412 } 4413 let wholeEventStart = eventInstance.range.start; 4414 let wholeEventEnd = eventInstance.range.end; 4415 let segStart = startOverride || seg.start || seg.eventRange.range.start; 4416 let segEnd = endOverride || seg.end || seg.eventRange.range.end; 4417 let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); 4418 let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); 4419 if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { 4420 segStart = isStartDay ? wholeEventStart : segStart; 4421 segEnd = isEndDay ? wholeEventEnd : segEnd; 4422 if (displayEventEnd && eventDef.hasEnd) { 4423 return dateEnv.formatRange(segStart, segEnd, timeFormat, { 4424 forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, 4425 forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, 4426 }); 4427 } 4428 return dateEnv.format(segStart, timeFormat, { 4429 forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same 4430 }); 4431 } 4432 return ''; 4433 } 4434 function getSegMeta(seg, todayRange, nowDate) { 4435 let segRange = seg.eventRange.range; 4436 return { 4437 isPast: segRange.end < (nowDate || todayRange.start), 4438 isFuture: segRange.start >= (nowDate || todayRange.end), 4439 isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), 4440 }; 4441 } 4442 function getEventClassNames(props) { 4443 let classNames = ['fc-event']; 4444 if (props.isMirror) { 4445 classNames.push('fc-event-mirror'); 4446 } 4447 if (props.isDraggable) { 4448 classNames.push('fc-event-draggable'); 4449 } 4450 if (props.isStartResizable || props.isEndResizable) { 4451 classNames.push('fc-event-resizable'); 4452 } 4453 if (props.isDragging) { 4454 classNames.push('fc-event-dragging'); 4455 } 4456 if (props.isResizing) { 4457 classNames.push('fc-event-resizing'); 4458 } 4459 if (props.isSelected) { 4460 classNames.push('fc-event-selected'); 4461 } 4462 if (props.isStart) { 4463 classNames.push('fc-event-start'); 4464 } 4465 if (props.isEnd) { 4466 classNames.push('fc-event-end'); 4467 } 4468 if (props.isPast) { 4469 classNames.push('fc-event-past'); 4470 } 4471 if (props.isToday) { 4472 classNames.push('fc-event-today'); 4473 } 4474 if (props.isFuture) { 4475 classNames.push('fc-event-future'); 4476 } 4477 return classNames; 4478 } 4479 function buildEventRangeKey(eventRange) { 4480 return eventRange.instance 4481 ? eventRange.instance.instanceId 4482 : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`; 4483 // inverse-background events don't have specific instances. TODO: better solution 4484 } 4485 function getSegAnchorAttrs(seg, context) { 4486 let { def, instance } = seg.eventRange; 4487 let { url } = def; 4488 if (url) { 4489 return { href: url }; 4490 } 4491 let { emitter, options } = context; 4492 let { eventInteractive } = options; 4493 if (eventInteractive == null) { 4494 eventInteractive = def.interactive; 4495 if (eventInteractive == null) { 4496 eventInteractive = Boolean(emitter.hasHandlers('eventClick')); 4497 } 4498 } 4499 // mock what happens in EventClicking 4500 if (eventInteractive) { 4501 // only attach keyboard-related handlers because click handler is already done in EventClicking 4502 return createAriaKeyboardAttrs((ev) => { 4503 emitter.trigger('eventClick', { 4504 el: ev.target, 4505 event: new EventImpl(context, def, instance), 4506 jsEvent: ev, 4507 view: context.viewApi, 4508 }); 4509 }); 4510 } 4511 return {}; 4512 } 4513 4514 const STANDARD_PROPS = { 4515 start: identity, 4516 end: identity, 4517 allDay: Boolean, 4518 }; 4519 function parseDateSpan(raw, dateEnv, defaultDuration) { 4520 let span = parseOpenDateSpan(raw, dateEnv); 4521 let { range } = span; 4522 if (!range.start) { 4523 return null; 4524 } 4525 if (!range.end) { 4526 if (defaultDuration == null) { 4527 return null; 4528 } 4529 range.end = dateEnv.add(range.start, defaultDuration); 4530 } 4531 return span; 4532 } 4533 /* 4534 TODO: somehow combine with parseRange? 4535 Will return null if the start/end props were present but parsed invali
vendor: 11,466 bytes, lines 4535-4839
4535dly. 4536 */ 4537 function parseOpenDateSpan(raw, dateEnv) { 4538 let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS); 4539 let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; 4540 let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; 4541 let { allDay } = standardProps; 4542 if (allDay == null) { 4543 allDay = (startMeta && startMeta.isTimeUnspecified) && 4544 (!endMeta || endMeta.isTimeUnspecified); 4545 } 4546 return Object.assign({ range: { 4547 start: startMeta ? startMeta.marker : null, 4548 end: endMeta ? endMeta.marker : null, 4549 }, allDay }, extra); 4550 } 4551 function isDateSpansEqual(span0, span1) { 4552 return rangesEqual(span0.range, span1.range) && 4553 span0.allDay === span1.allDay && 4554 isSpanPropsEqual(span0, span1); 4555 } 4556 // the NON-DATE-RELATED props 4557 function isSpanPropsEqual(span0, span1) { 4558 for (let propName in span1) { 4559 if (propName !== 'range' && propName !== 'allDay') { 4560 if (span0[propName] !== span1[propName]) { 4561 return false; 4562 } 4563 } 4564 } 4565 // are there any props that span0 has that span1 DOESN'T have? 4566 // both have range/allDay, so no need to special-case. 4567 for (let propName in span0) { 4568 if (!(propName in span1)) { 4569 return false; 4570 } 4571 } 4572 return true; 4573 } 4574 function buildDateSpanApi(span, dateEnv) { 4575 return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); 4576 } 4577 function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { 4578 return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); 4579 } 4580 function buildRangeApi(range, dateEnv, omitTime) { 4581 return { 4582 start: dateEnv.toDate(range.start), 4583 end: dateEnv.toDate(range.end), 4584 startStr: dateEnv.formatIso(range.start, { omitTime }), 4585 endStr: dateEnv.formatIso(range.end, { omitTime }), 4586 }; 4587 } 4588 function fabricateEventRange(dateSpan, eventUiBases, context) { 4589 let res = refineEventDef({ editable: false }, context); 4590 let def = parseEventDef(res.refined, res.extra, '', // sourceId 4591 dateSpan.allDay, true, // hasEnd 4592 context); 4593 return { 4594 def, 4595 ui: compileEventUi(def, eventUiBases), 4596 instance: createEventInstance(def.defId, dateSpan.range), 4597 range: dateSpan.range, 4598 isStart: true, 4599 isEnd: true, 4600 }; 4601 } 4602 4603 /* 4604 given a function that resolves a result asynchronously. 4605 the function can either call passed-in success and failure callbacks, 4606 or it can return a promise. 4607 if you need to pass additional params to func, bind them first. 4608 */ 4609 function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) { 4610 // guard against success/failure callbacks being called more than once 4611 // and guard against a promise AND callback being used together. 4612 let isResolved = false; 4613 let wrappedSuccess = function (res) { 4614 if (!isResolved) { 4615 isResolved = true; 4616 normalizedSuccessCallback(res); 4617 } 4618 }; 4619 let wrappedFailure = function (error) { 4620 if (!isResolved) { 4621 isResolved = true; 4622 normalizedFailureCallback(error); 4623 } 4624 }; 4625 let res = func(wrappedSuccess, wrappedFailure); 4626 if (res && typeof res.then === 'function') { 4627 res.then(wrappedSuccess, wrappedFailure); 4628 } 4629 } 4630 4631 class JsonRequestError extends Error { 4632 constructor(message, response) { 4633 super(message); 4634 this.response = response; 4635 } 4636 } 4637 function requestJson(method, url, params) { 4638 method = method.toUpperCase(); 4639 const fetchOptions = { 4640 method, 4641 }; 4642 if (method === 'GET') { 4643 url += (url.indexOf('?') === -1 ? '?' : '&') + 4644 new URLSearchParams(params); 4645 } 4646 else { 4647 fetchOptions.body = new URLSearchParams(params); 4648 fetchOptions.headers = { 4649 'Content-Type': 'application/x-www-form-urlencoded', 4650 }; 4651 } 4652 return fetch(url, fetchOptions).then((fetchRes) => { 4653 if (fetchRes.ok) { 4654 return fetchRes.json().then((parsedResponse) => { 4655 return [parsedResponse, fetchRes]; 4656 }, () => { 4657 throw new JsonRequestError('Failure parsing JSON', fetchRes); 4658 }); 4659 } 4660 else { 4661 throw new JsonRequestError('Request failed', fetchRes); 4662 } 4663 }); 4664 } 4665 4666 let canVGrowWithinCell; 4667 function getCanVGrowWithinCell() { 4668 if (canVGrowWithinCell == null) { 4669 canVGrowWithinCell = computeCanVGrowWithinCell(); 4670 } 4671 return canVGrowWithinCell; 4672 } 4673 function computeCanVGrowWithinCell() { 4674 // for SSR, because this function is call immediately at top-level 4675 // TODO: just make this logic execute top-level, immediately, instead of doing lazily 4676 if (typeof document === 'undefined') { 4677 return true; 4678 } 4679 let el = document.createElement('div'); 4680 el.style.position = 'absolute'; 4681 el.style.top = '0px'; 4682 el.style.left = '0px'; 4683 el.innerHTML = '<table><tr><td><div></div></td></tr></table>'; 4684 el.querySelector('table').style.height = '100px'; 4685 el.querySelector('div').style.height = '100%'; 4686 document.body.appendChild(el); 4687 let div = el.querySelector('div'); 4688 let possible = div.offsetHeight > 0; 4689 document.body.removeChild(el); 4690 return possible; 4691 } 4692 4693 class CalendarRoot extends BaseComponent { 4694 constructor() { 4695 super(...arguments); 4696 this.state = { 4697 forPrint: false, 4698 }; 4699 this.handleBeforePrint = () => { 4700 this.setState({ forPrint: true }); 4701 }; 4702 this.handleAfterPrint = () => { 4703 this.setState({ forPrint: false }); 4704 }; 4705 } 4706 render() { 4707 let { props } = this; 4708 let { options } = props; 4709 let { forPrint } = this.state; 4710 let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; 4711 let height = (!isHeightAuto && options.height != null) ? options.height : ''; 4712 let classNames = [ 4713 'fc', 4714 forPrint ? 'fc-media-print' : 'fc-media-screen', 4715 `fc-direction-${options.direction}`, 4716 props.theme.getClass('root'), 4717 ]; 4718 if (!getCanVGrowWithinCell()) { 4719 classNames.push('fc-liquid-hack'); 4720 } 4721 return props.children(classNames, height, isHeightAuto, forPrint); 4722 } 4723 componentDidMount() { 4724 let { emitter } = this.props; 4725 emitter.on('_beforeprint', this.handleBeforePrint); 4726 emitter.on('_afterprint', this.handleAfterPrint); 4727 } 4728 componentWillUnmount() { 4729 let { emitter } = this.props; 4730 emitter.off('_beforeprint', this.handleBeforePrint); 4731 emitter.off('_afterprint', this.handleAfterPrint); 4732 } 4733 } 4734 4735 class Interaction { 4736 constructor(settings) { 4737 this.component = settings.component; 4738 this.isHitComboAllowed = settings.isHitComboAllowed || null; 4739 } 4740 destroy() { 4741 } 4742 } 4743 function parseInteractionSettings(component, input) { 4744 return { 4745 component, 4746 el: input.el, 4747 useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, 4748 isHitComboAllowed: input.isHitComboAllowed || null, 4749 }; 4750 } 4751 function interactionSettingsToStore(settings) { 4752 return { 4753 [settings.component.uid]: settings, 4754 }; 4755 } 4756 // global state 4757 const interactionSettingsStore = {}; 4758 4759 class CalendarImpl { 4760 getCurrentData() { 4761 return this.currentDataManager.getCurrentData(); 4762 } 4763 dispatch(action) { 4764 this.currentDataManager.dispatch(action); 4765 } 4766 get view() { return this.getCurrentData().viewApi; } 4767 batchRendering(callback) { 4768 callback(); 4769 } 4770 updateSize() { 4771 this.trigger('_resize', true); 4772 } 4773 // Options 4774 // ----------------------------------------------------------------------------------------------------------------- 4775 setOption(name, val) { 4776 this.dispatch({ 4777 type: 'SET_OPTION', 4778 optionName: name, 4779 rawOptionValue: val, 4780 }); 4781 } 4782 getOption(name) { 4783 return this.currentDataManager.currentCalendarOptionsInput[name]; 4784 } 4785 getAvailableLocaleCodes() { 4786 return Object.keys(this.getCurrentData().availableRawLocales); 4787 } 4788 // Trigger 4789 // ----------------------------------------------------------------------------------------------------------------- 4790 on(handlerName, handler) { 4791 let { currentDataManager } = this; 4792 if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { 4793 currentDataManager.emitter.on(handlerName, handler); 4794 } 4795 else { 4796 console.warn(`Unknown listener name '${handlerName}'`); 4797 } 4798 } 4799 off(handlerName, handler) { 4800 this.currentDataManager.emitter.off(handlerName, handler); 4801 } 4802 // not meant for public use 4803 trigger(handlerName, ...args) { 4804 this.currentDataManager.emitter.trigger(handlerName, ...args); 4805 } 4806 // View 4807 // ----------------------------------------------------------------------------------------------------------------- 4808 changeView(viewType, dateOrRange) { 4809 this.batchRendering(() => { 4810 this.unselect(); 4811 if (dateOrRange) { 4812 if (dateOrRange.start && dateOrRange.end) { // a range 4813 this.dispatch({ 4814 type: 'CHANGE_VIEW_TYPE', 4815 viewType, 4816 }); 4817 this.dispatch({ 4818 type: 'SET_OPTION', 4819 optionName: 'visibleRange', 4820 rawOptionValue: dateOrRange, 4821 }); 4822 } 4823 else { 4824 let { dateEnv } = this.getCurrentData(); 4825 this.dispatch({ 4826 type: 'CHANGE_VIEW_TYPE', 4827 viewType, 4828 dateMarker: dateEnv.createMarker(dateOrRange), 4829 }); 4830 } 4831 } 4832 else { 4833 this.dispatch({ 4834 type: 'CHANGE_VIEW_TYPE', 4835 viewType, 4836 }); 4837 } 4838 }); 4839 }
vendor: 21,600 bytes, lines 4840-5371
4840 // Forces navigation to a view for the given date. 4841 // `viewType` can be a specific view name or a generic one like "week" or "day". 4842 // needs to change 4843 zoomTo(dateMarker, viewType) { 4844 let state = this.getCurrentData(); 4845 let spec; 4846 viewType = viewType || 'day'; // day is default zoom 4847 spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); 4848 this.unselect(); 4849 if (spec) { 4850 this.dispatch({ 4851 type: 'CHANGE_VIEW_TYPE', 4852 viewType: spec.type, 4853 dateMarker, 4854 }); 4855 } 4856 else { 4857 this.dispatch({ 4858 type: 'CHANGE_DATE', 4859 dateMarker, 4860 }); 4861 } 4862 } 4863 // Given a duration singular unit, like "week" or "day", finds a matching view spec. 4864 // Preference is given to views that have corresponding buttons. 4865 getUnitViewSpec(unit) { 4866 let { viewSpecs, toolbarConfig } = this.getCurrentData(); 4867 let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []); 4868 let i; 4869 let spec; 4870 for (let viewType in viewSpecs) { 4871 viewTypes.push(viewType); 4872 } 4873 for (i = 0; i < viewTypes.length; i += 1) { 4874 spec = viewSpecs[viewTypes[i]]; 4875 if (spec) { 4876 if (spec.singleUnit === unit) { 4877 return spec; 4878 } 4879 } 4880 } 4881 return null; 4882 } 4883 // Current Date 4884 // ----------------------------------------------------------------------------------------------------------------- 4885 prev() { 4886 this.unselect(); 4887 this.dispatch({ type: 'PREV' }); 4888 } 4889 next() { 4890 this.unselect(); 4891 this.dispatch({ type: 'NEXT' }); 4892 } 4893 prevYear() { 4894 let state = this.getCurrentData(); 4895 this.unselect(); 4896 this.dispatch({ 4897 type: 'CHANGE_DATE', 4898 dateMarker: state.dateEnv.addYears(state.currentDate, -1), 4899 }); 4900 } 4901 nextYear() { 4902 let state = this.getCurrentData(); 4903 this.unselect(); 4904 this.dispatch({ 4905 type: 'CHANGE_DATE', 4906 dateMarker: state.dateEnv.addYears(state.currentDate, 1), 4907 }); 4908 } 4909 today() { 4910 let state = this.getCurrentData(); 4911 this.unselect(); 4912 this.dispatch({ 4913 type: 'CHANGE_DATE', 4914 dateMarker: getNow(state.calendarOptions.now, state.dateEnv), 4915 }); 4916 } 4917 gotoDate(zonedDateInput) { 4918 let state = this.getCurrentData(); 4919 this.unselect(); 4920 this.dispatch({ 4921 type: 'CHANGE_DATE', 4922 dateMarker: state.dateEnv.createMarker(zonedDateInput), 4923 }); 4924 } 4925 incrementDate(deltaInput) { 4926 let state = this.getCurrentData(); 4927 let delta = createDuration(deltaInput); 4928 if (delta) { // else, warn about invalid input? 4929 this.unselect(); 4930 this.dispatch({ 4931 type: 'CHANGE_DATE', 4932 dateMarker: state.dateEnv.add(state.currentDate, delta), 4933 }); 4934 } 4935 } 4936 getDate() { 4937 let state = this.getCurrentData(); 4938 return state.dateEnv.toDate(state.currentDate); 4939 } 4940 // Date Formatting Utils 4941 // ----------------------------------------------------------------------------------------------------------------- 4942 formatDate(d, formatter) { 4943 let { dateEnv } = this.getCurrentData(); 4944 return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); 4945 } 4946 // `settings` is for formatter AND isEndExclusive 4947 formatRange(d0, d1, settings) { 4948 let { dateEnv } = this.getCurrentData(); 4949 return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); 4950 } 4951 formatIso(d, omitTime) { 4952 let { dateEnv } = this.getCurrentData(); 4953 return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime }); 4954 } 4955 // Date Selection / Event Selection / DayClick 4956 // ----------------------------------------------------------------------------------------------------------------- 4957 select(dateOrObj, endDate) { 4958 let selectionInput; 4959 if (endDate == null) { 4960 if (dateOrObj.start != null) { 4961 selectionInput = dateOrObj; 4962 } 4963 else { 4964 selectionInput = { 4965 start: dateOrObj, 4966 end: null, 4967 }; 4968 } 4969 } 4970 else { 4971 selectionInput = { 4972 start: dateOrObj, 4973 end: endDate, 4974 }; 4975 } 4976 let state = this.getCurrentData(); 4977 let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); 4978 if (selection) { // throw parse error otherwise? 4979 this.dispatch({ type: 'SELECT_DATES', selection }); 4980 triggerDateSelect(selection, null, state); 4981 } 4982 } 4983 unselect(pev) { 4984 let state = this.getCurrentData(); 4985 if (state.dateSelection) { 4986 this.dispatch({ type: 'UNSELECT_DATES' }); 4987 triggerDateUnselect(pev, state); 4988 } 4989 } 4990 // Public Events API 4991 // ----------------------------------------------------------------------------------------------------------------- 4992 addEvent(eventInput, sourceInput) { 4993 if (eventInput instanceof EventImpl) { 4994 let def = eventInput._def; 4995 let instance = eventInput._instance; 4996 let currentData = this.getCurrentData(); 4997 // not already present? don't want to add an old snapshot 4998 if (!currentData.eventStore.defs[def.defId]) { 4999 this.dispatch({ 5000 type: 'ADD_EVENTS', 5001 eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args? 5002 }); 5003 this.triggerEventAdd(eventInput); 5004 } 5005 return eventInput; 5006 } 5007 let state = this.getCurrentData(); 5008 let eventSource; 5009 if (sourceInput instanceof EventSourceImpl) { 5010 eventSource = sourceInput.internalEventSource; 5011 } 5012 else if (typeof sourceInput === 'boolean') { 5013 if (sourceInput) { // true. part of the first event source 5014 [eventSource] = hashValuesToArray(state.eventSources); 5015 } 5016 } 5017 else if (sourceInput != null) { // an ID. accepts a number too 5018 let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function 5019 if (!sourceApi) { 5020 console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test 5021 return null; 5022 } 5023 eventSource = sourceApi.internalEventSource; 5024 } 5025 let tuple = parseEvent(eventInput, eventSource, state, false); 5026 if (tuple) { 5027 let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); 5028 this.dispatch({ 5029 type: 'ADD_EVENTS', 5030 eventStore: eventTupleToStore(tuple), 5031 }); 5032 this.triggerEventAdd(newEventApi); 5033 return newEventApi; 5034 } 5035 return null; 5036 } 5037 triggerEventAdd(eventApi) { 5038 let { emitter } = this.getCurrentData(); 5039 emitter.trigger('eventAdd', { 5040 event: eventApi, 5041 relatedEvents: [], 5042 revert: () => { 5043 this.dispatch({ 5044 type: 'REMOVE_EVENTS', 5045 eventStore: eventApiToStore(eventApi), 5046 }); 5047 }, 5048 }); 5049 } 5050 // TODO: optimize 5051 getEventById(id) { 5052 let state = this.getCurrentData(); 5053 let { defs, instances } = state.eventStore; 5054 id = String(id); 5055 for (let defId in defs) { 5056 let def = defs[defId]; 5057 if (def.publicId === id) { 5058 if (def.recurringDef) { 5059 return new EventImpl(state, def, null); 5060 } 5061 for (let instanceId in instances) { 5062 let instance = instances[instanceId]; 5063 if (instance.defId === def.defId) { 5064 return new EventImpl(state, def, instance); 5065 } 5066 } 5067 } 5068 } 5069 return null; 5070 } 5071 getEvents() { 5072 let currentData = this.getCurrentData(); 5073 return buildEventApis(currentData.eventStore, currentData); 5074 } 5075 removeAllEvents() { 5076 this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); 5077 } 5078 // Public Event Sources API 5079 // ----------------------------------------------------------------------------------------------------------------- 5080 getEventSources() { 5081 let state = this.getCurrentData(); 5082 let sourceHash = state.eventSources; 5083 let sourceApis = []; 5084 for (let internalId in sourceHash) { 5085 sourceApis.push(new EventSourceImpl(state, sourceHash[internalId])); 5086 } 5087 return sourceApis; 5088 } 5089 getEventSourceById(id) { 5090 let state = this.getCurrentData(); 5091 let sourceHash = state.eventSources; 5092 id = String(id); 5093 for (let sourceId in sourceHash) { 5094 if (sourceHash[sourceId].publicId === id) { 5095 return new EventSourceImpl(state, sourceHash[sourceId]); 5096 } 5097 } 5098 return null; 5099 } 5100 addEventSource(sourceInput) { 5101 let state = this.getCurrentData(); 5102 if (sourceInput instanceof EventSourceImpl) { 5103 // not already present? don't want to add an old snapshot 5104 if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { 5105 this.dispatch({ 5106 type: 'ADD_EVENT_SOURCES', 5107 sources: [sourceInput.internalEventSource], 5108 }); 5109 } 5110 return sourceInput; 5111 } 5112 let eventSource = parseEventSource(sourceInput, state); 5113 if (eventSource) { // TODO: error otherwise? 5114 this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); 5115 return new EventSourceImpl(state, eventSource); 5116 } 5117 return null; 5118 } 5119 removeAllEventSources() { 5120 this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); 5121 } 5122 refetchEvents() { 5123 this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); 5124 } 5125 // Scroll 5126 // ----------------------------------------------------------------------------------------------------------------- 5127 scrollToTime(timeInput) { 5128 let time = createDuration(timeInput); 5129 if (time) { 5130 this.trigger('_scrollRequest', { time }); 5131 } 5132 } 5133 } 5134 5135 function pointInsideRect(point, rect) { 5136 return point.left >= rect.left && 5137 point.left < rect.right && 5138 point.top >= rect.top && 5139 point.top < rect.bottom; 5140 } 5141 // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false 5142 function intersectRects(rect1, rect2) { 5143 let res = { 5144 left: Math.max(rect1.left, rect2.left), 5145 right: Math.min(rect1.right, rect2.right), 5146 top: Math.max(rect1.top, rect2.top), 5147 bottom: Math.min(rect1.bottom, rect2.bottom), 5148 }; 5149 if (res.left < res.right && res.top < res.bottom) { 5150 return res; 5151 } 5152 return false; 5153 } 5154 function translateRect(rect, deltaX, deltaY) { 5155 return { 5156 left: rect.left + deltaX, 5157 right: rect.right + deltaX, 5158 top: rect.top + deltaY, 5159 bottom: rect.bottom + deltaY, 5160 }; 5161 } 5162 // Returns a new point that will have been moved to reside within the given rectangle 5163 function constrainPoint(point, rect) { 5164 return { 5165 left: Math.min(Math.max(point.left, rect.left), rect.right), 5166 top: Math.min(Math.max(point.top, rect.top), rect.bottom), 5167 }; 5168 } 5169 // Returns a point that is the center of the given rectangle 5170 function getRectCenter(rect) { 5171 return { 5172 left: (rect.left + rect.right) / 2, 5173 top: (rect.top + rect.bottom) / 2, 5174 }; 5175 } 5176 // Subtracts point2's coordinates from point1's coordinates, returning a delta 5177 function diffPoints(point1, point2) { 5178 return { 5179 left: point1.left - point2.left, 5180 top: point1.top - point2.top, 5181 }; 5182 } 5183 5184 const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere 5185 class Splitter { 5186 constructor() { 5187 this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); 5188 this.splitDateSelection = memoize(this._splitDateSpan); 5189 this.splitEventStore = memoize(this._splitEventStore); 5190 this.splitIndividualUi = memoize(this._splitIndividualUi); 5191 this.splitEventDrag = memoize(this._splitInteraction); 5192 this.splitEventResize = memoize(this._splitInteraction); 5193 this.eventUiBuilders = {}; // TODO: typescript protection 5194 } 5195 splitProps(props) { 5196 let keyInfos = this.getKeyInfo(props); 5197 let defKeys = this.getKeysForEventDefs(props.eventStore); 5198 let dateSelections = this.splitDateSelection(props.dateSelection); 5199 let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* 5200 let eventStores = this.splitEventStore(props.eventStore, defKeys); 5201 let eventDrags = this.splitEventDrag(props.eventDrag); 5202 let eventResizes = this.splitEventResize(props.eventResize); 5203 let splitProps = {}; 5204 this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey)); 5205 for (let key in keyInfos) { 5206 let keyInfo = keyInfos[key]; 5207 let eventStore = eventStores[key] || EMPTY_EVENT_STORE; 5208 let buildEventUi = this.eventUiBuilders[key]; 5209 splitProps[key] = { 5210 businessHours: keyInfo.businessHours || props.businessHours, 5211 dateSelection: dateSelections[key] || null, 5212 eventStore, 5213 eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), 5214 eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', 5215 eventDrag: eventDrags[key] || null, 5216 eventResize: eventResizes[key] || null, 5217 }; 5218 } 5219 return splitProps; 5220 } 5221 _splitDateSpan(dateSpan) { 5222 let dateSpans = {}; 5223 if (dateSpan) { 5224 let keys = this.getKeysForDateSpan(dateSpan); 5225 for (let key of keys) { 5226 dateSpans[key] = dateSpan; 5227 } 5228 } 5229 return dateSpans; 5230 } 5231 _getKeysForEventDefs(eventStore) { 5232 return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef)); 5233 } 5234 _splitEventStore(eventStore, defKeys) { 5235 let { defs, instances } = eventStore; 5236 let splitStores = {}; 5237 for (let defId in defs) { 5238 for (let key of defKeys[defId]) { 5239 if (!splitStores[key]) { 5240 splitStores[key] = createEmptyEventStore(); 5241 } 5242 splitStores[key].defs[defId] = defs[defId]; 5243 } 5244 } 5245 for (let instanceId in instances) { 5246 let instance = instances[instanceId]; 5247 for (let key of defKeys[instance.defId]) { 5248 if (splitStores[key]) { // must have already been created 5249 splitStores[key].instances[instanceId] = instance; 5250 } 5251 } 5252 } 5253 return splitStores; 5254 } 5255 _splitIndividualUi(eventUiBases, defKeys) { 5256 let splitHashes = {}; 5257 for (let defId in eventUiBases) { 5258 if (defId) { // not the '' key 5259 for (let key of defKeys[defId]) { 5260 if (!splitHashes[key]) { 5261 splitHashes[key] = {}; 5262 } 5263 splitHashes[key][defId] = eventUiBases[defId]; 5264 } 5265 } 5266 } 5267 return splitHashes; 5268 } 5269 _splitInteraction(interaction) { 5270 let splitStates = {}; 5271 if (interaction) { 5272 let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); 5273 // can't rely on defKeys because event data is mutated 5274 let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); 5275 let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); 5276 let populate = (key) => { 5277 if (!splitStates[key]) { 5278 splitStates[key] = { 5279 affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE, 5280 mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE, 5281 isEvent: interaction.isEvent, 5282 }; 5283 } 5284 }; 5285 for (let key in affectedStores) { 5286 populate(key); 5287 } 5288 for (let key in mutatedStores) { 5289 populate(key); 5290 } 5291 } 5292 return splitStates; 5293 } 5294 } 5295 function buildEventUiForKey(allUi, eventUiForKey, individualUi) { 5296 let baseParts = []; 5297 if (allUi) { 5298 baseParts.push(allUi); 5299 } 5300 if (eventUiForKey) { 5301 baseParts.push(eventUiForKey); 5302 } 5303 let stuff = { 5304 '': combineEventUis(baseParts), 5305 }; 5306 if (individualUi) { 5307 Object.assign(stuff, individualUi); 5308 } 5309 return stuff; 5310 } 5311 5312 function getDateMeta(date, todayRange, nowDate, dateProfile) { 5313 return { 5314 dow: date.getUTCDay(), 5315 isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), 5316 isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), 5317 isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), 5318 isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), 5319 isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), 5320 }; 5321 } 5322 function getDayClassNames(meta, theme) { 5323 let classNames = [ 5324 'fc-day', 5325 `fc-day-${DAY_IDS[meta.dow]}`, 5326 ]; 5327 if (meta.isDisabled) { 5328 classNames.push('fc-day-disabled'); 5329 } 5330 else { 5331 if (meta.isToday) { 5332 classNames.push('fc-day-today'); 5333 classNames.push(theme.getClass('today')); 5334 } 5335 if (meta.isPast) { 5336 classNames.push('fc-day-past'); 5337 } 5338 if (meta.isFuture) { 5339 classNames.push('fc-day-future'); 5340 } 5341 if (meta.isOther) { 5342 classNames.push('fc-day-other'); 5343 } 5344 } 5345 return classNames; 5346 } 5347 function getSlotClassNames(meta, theme) { 5348 let classNames = [ 5349 'fc-slot', 5350 `fc-slot-${DAY_IDS[meta.dow]}`, 5351 ]; 5352 if (meta.isDisabled) { 5353 classNames.push('fc-slot-disabled'); 5354 } 5355 else { 5356 if (meta.isToday) { 5357 classNames.push('fc-slot-today'); 5358 classNames.push(theme.getClass('today')); 5359 } 5360 if (meta.isPast) { 5361 classNames.push('fc-slot-past'); 5362 } 5363 if (meta.isFuture) { 5364 classNames.push('fc-slot-future'); 5365 } 5366 } 5367 return classNames; 5368 } 5369 5370 const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' }); 5371 const WEEK_FORMAT = createFormatter({ week: 'long' });
vendor: 7,271 bytes, lines 5372-5552
5372 function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) { 5373 const { dateEnv, options, calendarApi } = context; 5374 let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT); 5375 if (options.navLinks) { 5376 let zonedDate = dateEnv.toDate(dateMarker); 5377 const handleInteraction = (ev) => { 5378 let customAction = viewType === 'day' ? options.navLinkDayClick : 5379 viewType === 'week' ? options.navLinkWeekClick : null; 5380 if (typeof customAction === 'function') { 5381 customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); 5382 } 5383 else { 5384 if (typeof customAction === 'string') { 5385 viewType = customAction; 5386 } 5387 calendarApi.zoomTo(dateMarker, viewType); 5388 } 5389 }; 5390 return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable 5391 ? createAriaClickAttrs(handleInteraction) 5392 : { onClick: handleInteraction })); 5393 } 5394 return { 'aria-label': dateStr }; 5395 } 5396 5397 let _isRtlScrollbarOnLeft = null; 5398 function getIsRtlScrollbarOnLeft() { 5399 if (_isRtlScrollbarOnLeft === null) { 5400 _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); 5401 } 5402 return _isRtlScrollbarOnLeft; 5403 } 5404 function computeIsRtlScrollbarOnLeft() { 5405 let outerEl = document.createElement('div'); 5406 applyStyle(outerEl, { 5407 position: 'absolute', 5408 top: -1000, 5409 left: 0, 5410 border: 0, 5411 padding: 0, 5412 overflow: 'scroll', 5413 direction: 'rtl', 5414 }); 5415 outerEl.innerHTML = '<div></div>'; 5416 document.body.appendChild(outerEl); 5417 let innerEl = outerEl.firstChild; 5418 let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; 5419 removeElement(outerEl); 5420 return res; 5421 } 5422 5423 let _scrollbarWidths; 5424 function getScrollbarWidths() { 5425 if (!_scrollbarWidths) { 5426 _scrollbarWidths = computeScrollbarWidths(); 5427 } 5428 return _scrollbarWidths; 5429 } 5430 function computeScrollbarWidths() { 5431 let el = document.createElement('div'); 5432 el.style.overflow = 'scroll'; 5433 el.style.position = 'absolute'; 5434 el.style.top = '-9999px'; 5435 el.style.left = '-9999px'; 5436 document.body.appendChild(el); 5437 let res = computeScrollbarWidthsForEl(el); 5438 document.body.removeChild(el); 5439 return res; 5440 } 5441 // WARNING: will include border 5442 function computeScrollbarWidthsForEl(el) { 5443 return { 5444 x: el.offsetHeight - el.clientHeight, 5445 y: el.offsetWidth - el.clientWidth, 5446 }; 5447 } 5448 5449 function computeEdges(el, getPadding = false) { 5450 let computedStyle = window.getComputedStyle(el); 5451 let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; 5452 let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; 5453 let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; 5454 let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; 5455 let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! 5456 let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; 5457 let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; 5458 let res = { 5459 borderLeft, 5460 borderRight, 5461 borderTop, 5462 borderBottom, 5463 scrollbarBottom, 5464 scrollbarLeft: 0, 5465 scrollbarRight: 0, 5466 }; 5467 if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? 5468 res.scrollbarLeft = scrollbarLeftRight; 5469 } 5470 else { 5471 res.scrollbarRight = scrollbarLeftRight; 5472 } 5473 if (getPadding) { 5474 res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; 5475 res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; 5476 res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; 5477 res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; 5478 } 5479 return res; 5480 } 5481 function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) { 5482 let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); 5483 let edges = computeEdges(el, goWithinPadding); 5484 let res = { 5485 left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, 5486 right: outerRect.right - edges.borderRight - edges.scrollbarRight, 5487 top: outerRect.top + edges.borderTop, 5488 bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, 5489 }; 5490 if (goWithinPadding) { 5491 res.left += edges.paddingLeft; 5492 res.right -= edges.paddingRight; 5493 res.top += edges.paddingTop; 5494 res.bottom -= edges.paddingBottom; 5495 } 5496 return res; 5497 } 5498 function computeRect(el) { 5499 let rect = el.getBoundingClientRect(); 5500 return { 5501 left: rect.left + window.pageXOffset, 5502 top: rect.top + window.pageYOffset, 5503 right: rect.right + window.pageXOffset, 5504 bottom: rect.bottom + window.pageYOffset, 5505 }; 5506 } 5507 function computeClippedClientRect(el) { 5508 let clippingParents = getClippingParents(el); 5509 let rect = el.getBoundingClientRect(); 5510 for (let clippingParent of clippingParents) { 5511 let intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); 5512 if (intersection) { 5513 rect = intersection; 5514 } 5515 else { 5516 return null; 5517 } 5518 } 5519 return rect; 5520 } 5521 // does not return window 5522 function getClippingParents(el) { 5523 let parents = []; 5524 while (el instanceof HTMLElement) { // will stop when gets to document or null 5525 let computedStyle = window.getComputedStyle(el); 5526 if (computedStyle.position === 'fixed') { 5527 break; 5528 } 5529 if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { 5530 parents.push(el); 5531 } 5532 el = el.parentNode; 5533 } 5534 return parents; 5535 } 5536 5537 /* 5538 Records offset information for a set of elements, relative to an origin element. 5539 Can record the left/right OR the top/bottom OR both. 5540 Provides methods for querying the cache by position. 5541 */ 5542 class PositionCache { 5543 constructor(originEl, els, isHorizontal, isVertical) { 5544 this.els = els; 5545 let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left 5546 if (isHorizontal) { 5547 this.buildElHorizontals(originClientRect.left); 5548 } 5549 if (isVertical) { 5550 this.buildElVerticals(originClientRect.top); 5551 } 5552 }
vendor: 4,074 bytes, lines 5553-5662
5553 // Populates the left/right internal coordinate arrays 5554 buildElHorizontals(originClientLeft) { 5555 let lefts = []; 5556 let rights = []; 5557 for (let el of this.els) { 5558 let rect = el.getBoundingClientRect(); 5559 lefts.push(rect.left - originClientLeft); 5560 rights.push(rect.right - originClientLeft); 5561 } 5562 this.lefts = lefts; 5563 this.rights = rights; 5564 } 5565 // Populates the top/bottom internal coordinate arrays 5566 buildElVerticals(originClientTop) { 5567 let tops = []; 5568 let bottoms = []; 5569 for (let el of this.els) { 5570 let rect = el.getBoundingClientRect(); 5571 tops.push(rect.top - originClientTop); 5572 bottoms.push(rect.bottom - originClientTop); 5573 } 5574 this.tops = tops; 5575 this.bottoms = bottoms; 5576 } 5577 // Given a left offset (from document left), returns the index of the el that it horizontally intersects. 5578 // If no intersection is made, returns undefined. 5579 leftToIndex(leftPosition) { 5580 let { lefts, rights } = this; 5581 let len = lefts.length; 5582 let i; 5583 for (i = 0; i < len; i += 1) { 5584 if (leftPosition >= lefts[i] && leftPosition < rights[i]) { 5585 return i; 5586 } 5587 } 5588 return undefined; // TODO: better 5589 } 5590 // Given a top offset (from document top), returns the index of the el that it vertically intersects. 5591 // If no intersection is made, returns undefined. 5592 topToIndex(topPosition) { 5593 let { tops, bottoms } = this; 5594 let len = tops.length; 5595 let i; 5596 for (i = 0; i < len; i += 1) { 5597 if (topPosition >= tops[i] && topPosition < bottoms[i]) { 5598 return i; 5599 } 5600 } 5601 return undefined; // TODO: better 5602 } 5603 // Gets the width of the element at the given index 5604 getWidth(leftIndex) { 5605 return this.rights[leftIndex] - this.lefts[leftIndex]; 5606 } 5607 // Gets the height of the element at the given index 5608 getHeight(topIndex) { 5609 return this.bottoms[topIndex] - this.tops[topIndex]; 5610 } 5611 similarTo(otherCache) { 5612 return similarNumArrays(this.tops || [], otherCache.tops || []) && 5613 similarNumArrays(this.bottoms || [], otherCache.bottoms || []) && 5614 similarNumArrays(this.lefts || [], otherCache.lefts || []) && 5615 similarNumArrays(this.rights || [], otherCache.rights || []); 5616 } 5617 } 5618 function similarNumArrays(a, b) { 5619 const len = a.length; 5620 if (len !== b.length) { 5621 return false; 5622 } 5623 for (let i = 0; i < len; i++) { 5624 if (Math.round(a[i]) !== Math.round(b[i])) { 5625 return false; 5626 } 5627 } 5628 return true; 5629 } 5630 5631 /* eslint max-classes-per-file: "off" */ 5632 /* 5633 An object for getting/setting scroll-related information for an element. 5634 Internally, this is done very differently for window versus DOM element, 5635 so this object serves as a common interface. 5636 */ 5637 class ScrollController { 5638 getMaxScrollTop() { 5639 return this.getScrollHeight() - this.getClientHeight(); 5640 } 5641 getMaxScrollLeft() { 5642 return this.getScrollWidth() - this.getClientWidth(); 5643 } 5644 canScrollVertically() { 5645 return this.getMaxScrollTop() > 0; 5646 } 5647 canScrollHorizontally() { 5648 return this.getMaxScrollLeft() > 0; 5649 } 5650 canScrollUp() { 5651 return this.getScrollTop() > 0; 5652 } 5653 canScrollDown() { 5654 return this.getScrollTop() < this.getMaxScrollTop(); 5655 } 5656 canScrollLeft() { 5657 return this.getScrollLeft() > 0; 5658 } 5659 canScrollRight() { 5660 return this.getScrollLeft() < this.getMaxScrollLeft(); 5661 } 5662 }
vendor: 4,519 bytes, lines 5663-5795
5663 class ElementScrollController extends ScrollController { 5664 constructor(el) { 5665 super(); 5666 this.el = el; 5667 } 5668 getScrollTop() { 5669 return this.el.scrollTop; 5670 } 5671 getScrollLeft() { 5672 return this.el.scrollLeft; 5673 } 5674 setScrollTop(top) { 5675 this.el.scrollTop = top; 5676 } 5677 setScrollLeft(left) { 5678 this.el.scrollLeft = left; 5679 } 5680 getScrollWidth() { 5681 return this.el.scrollWidth; 5682 } 5683 getScrollHeight() { 5684 return this.el.scrollHeight; 5685 } 5686 getClientHeight() { 5687 return this.el.clientHeight; 5688 } 5689 getClientWidth() { 5690 return this.el.clientWidth; 5691 } 5692 } 5693 class WindowScrollController extends ScrollController { 5694 getScrollTop() { 5695 return window.pageYOffset; 5696 } 5697 getScrollLeft() { 5698 return window.pageXOffset; 5699 } 5700 setScrollTop(n) { 5701 window.scroll(window.pageXOffset, n); 5702 } 5703 setScrollLeft(n) { 5704 window.scroll(n, window.pageYOffset); 5705 } 5706 getScrollWidth() { 5707 return document.documentElement.scrollWidth; 5708 } 5709 getScrollHeight() { 5710 return document.documentElement.scrollHeight; 5711 } 5712 getClientHeight() { 5713 return document.documentElement.clientHeight; 5714 } 5715 getClientWidth() { 5716 return document.documentElement.clientWidth; 5717 } 5718 } 5719 5720 /* 5721 an INTERACTABLE date component 5722 5723 PURPOSES: 5724 - hook up to fg, fill, and mirror renderers 5725 - interface for dragging and hits 5726 */ 5727 class DateComponent extends BaseComponent { 5728 constructor() { 5729 super(...arguments); 5730 this.uid = guid(); 5731 } 5732 // Hit System 5733 // ----------------------------------------------------------------------------------------------------------------- 5734 prepareHits() { 5735 } 5736 queryHit(positionLeft, positionTop, elWidth, elHeight) { 5737 return null; // this should be abstract 5738 } 5739 // Pointer Interaction Utils 5740 // ----------------------------------------------------------------------------------------------------------------- 5741 isValidSegDownEl(el) { 5742 return !this.props.eventDrag && // HACK 5743 !this.props.eventResize && // HACK 5744 !elementClosest(el, '.fc-event-mirror'); 5745 } 5746 isValidDateDownEl(el) { 5747 return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && 5748 !elementClosest(el, '.fc-more-link') && // a "more.." link 5749 !elementClosest(el, 'a[data-navlink]') && // a clickable nav link 5750 !elementClosest(el, '.fc-popover'); // hack 5751 } 5752 } 5753 5754 class NamedTimeZoneImpl { 5755 constructor(timeZoneName) { 5756 this.timeZoneName = timeZoneName; 5757 } 5758 } 5759 5760 class SegHierarchy { 5761 constructor(getEntryThickness = (entry) => { 5762 // should return an integer 5763 return entry.thickness; 5764 }) { 5765 this.getEntryThickness = getEntryThickness; 5766 // settings 5767 this.strictOrder = false; 5768 this.allowReslicing = false; 5769 this.maxCoord = -1; // -1 means no max 5770 this.maxStackCnt = -1; // -1 means no max 5771 this.levelCoords = []; // ordered 5772 this.entriesByLevel = []; // parallel with levelCoords 5773 this.stackCnts = {}; // TODO: use better technique!? 5774 } 5775 addSegs(inputs) { 5776 let hiddenEntries = []; 5777 for (let input of inputs) { 5778 this.insertEntry(input, hiddenEntries); 5779 } 5780 return hiddenEntries; 5781 } 5782 insertEntry(entry, hiddenEntries) { 5783 let insertion = this.findInsertion(entry); 5784 if (this.isInsertionValid(insertion, entry)) { 5785 this.insertEntryAt(entry, insertion); 5786 return 1; 5787 } 5788 return this.handleInvalidInsertion(insertion, entry, hiddenEntries); 5789 } 5790 isInsertionValid(insertion, entry) { 5791 return (this.maxCoord === -1 || insertion.levelCoord + this.getEntryThickness(entry) <= this.maxCoord) && 5792 (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); 5793 } 5794 // returns number of new entries inserted 5795 handleInvalidInsertion(insertion, entry, hiddenEntries) {
vendor: 8,288 bytes, lines 5796-5982
5796 if (this.allowReslicing && insertion.touchingEntry) { 5797 return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); 5798 } 5799 hiddenEntries.push(entry); 5800 return 0; 5801 } 5802 splitEntry(entry, barrier, hiddenEntries) { 5803 let partCnt = 0; 5804 let splitHiddenEntries = []; 5805 let entrySpan = entry.span; 5806 let barrierSpan = barrier.span; 5807 if (entrySpan.start < barrierSpan.start) { 5808 partCnt += this.insertEntry({ 5809 index: entry.index, 5810 thickness: entry.thickness, 5811 span: { start: entrySpan.start, end: barrierSpan.start }, 5812 }, splitHiddenEntries); 5813 } 5814 if (entrySpan.end > barrierSpan.end) { 5815 partCnt += this.insertEntry({ 5816 index: entry.index, 5817 thickness: entry.thickness, 5818 span: { start: barrierSpan.end, end: entrySpan.end }, 5819 }, splitHiddenEntries); 5820 } 5821 if (partCnt) { 5822 hiddenEntries.push({ 5823 index: entry.index, 5824 thickness: entry.thickness, 5825 span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect 5826 }, ...splitHiddenEntries); 5827 return partCnt; 5828 } 5829 hiddenEntries.push(entry); 5830 return 0; 5831 } 5832 insertEntryAt(entry, insertion) { 5833 let { entriesByLevel, levelCoords } = this; 5834 if (insertion.lateral === -1) { 5835 // create a new level 5836 insertAt(levelCoords, insertion.level, insertion.levelCoord); 5837 insertAt(entriesByLevel, insertion.level, [entry]); 5838 } 5839 else { 5840 // insert into existing level 5841 insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); 5842 } 5843 this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; 5844 } 5845 findInsertion(newEntry) { 5846 let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this; 5847 let levelCnt = levelCoords.length; 5848 let candidateCoord = 0; 5849 let touchingLevel = -1; 5850 let touchingLateral = -1; 5851 let touchingEntry = null; 5852 let stackCnt = 0; 5853 for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { 5854 let trackingCoord = levelCoords[trackingLevel]; 5855 // if the current level is past the placed entry, we have found a good empty space and can stop. 5856 // if strictOrder, keep finding more lateral intersections. 5857 if (!strictOrder && trackingCoord >= candidateCoord + this.getEntryThickness(newEntry)) { 5858 break; 5859 } 5860 let trackingEntries = entriesByLevel[trackingLevel]; 5861 let trackingEntry; 5862 let searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end 5863 let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one 5864 while ( // loop through entries that horizontally intersect 5865 (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list 5866 trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry 5867 ) { 5868 let trackingEntryBottom = trackingCoord + this.getEntryThickness(trackingEntry); 5869 // intersects into the top of the candidate? 5870 if (trackingEntryBottom > candidateCoord) { 5871 candidateCoord = trackingEntryBottom; 5872 touchingEntry = trackingEntry; 5873 touchingLevel = trackingLevel; 5874 touchingLateral = lateralIndex; 5875 } 5876 // butts up against top of candidate? (will happen if just intersected as well) 5877 if (trackingEntryBottom === candidateCoord) { 5878 // accumulate the highest possible stackCnt of the trackingEntries that butt up 5879 stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); 5880 } 5881 lateralIndex += 1; 5882 } 5883 } 5884 // the destination level will be after touchingEntry's level. find it 5885 let destLevel = 0; 5886 if (touchingEntry) { 5887 destLevel = touchingLevel + 1; 5888 while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { 5889 destLevel += 1; 5890 } 5891 } 5892 // if adding to an existing level, find where to insert 5893 let destLateral = -1; 5894 if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { 5895 destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; 5896 } 5897 return { 5898 touchingLevel, 5899 touchingLateral, 5900 touchingEntry, 5901 stackCnt, 5902 levelCoord: candidateCoord, 5903 level: destLevel, 5904 lateral: destLateral, 5905 }; 5906 } 5907 // sorted by levelCoord (lowest to highest) 5908 toRects() { 5909 let { entriesByLevel, levelCoords } = this; 5910 let levelCnt = entriesByLevel.length; 5911 let rects = []; 5912 for (let level = 0; level < levelCnt; level += 1) { 5913 let entries = entriesByLevel[level]; 5914 let levelCoord = levelCoords[level]; 5915 for (let entry of entries) { 5916 rects.push(Object.assign(Object.assign({}, entry), { thickness: this.getEntryThickness(entry), levelCoord })); 5917 } 5918 } 5919 return rects; 5920 } 5921 } 5922 function getEntrySpanEnd(entry) { 5923 return entry.span.end; 5924 } 5925 function buildEntryKey(entry) { 5926 return entry.index + ':' + entry.span.start; 5927 } 5928 // returns groups with entries sorted by input order 5929 function groupIntersectingEntries(entries) { 5930 let merges = []; 5931 for (let entry of entries) { 5932 let filteredMerges = []; 5933 let hungryMerge = { 5934 span: entry.span, 5935 entries: [entry], 5936 }; 5937 for (let merge of merges) { 5938 if (intersectSpans(merge.span, hungryMerge.span)) { 5939 hungryMerge = { 5940 entries: merge.entries.concat(hungryMerge.entries), 5941 span: joinSpans(merge.span, hungryMerge.span), 5942 }; 5943 } 5944 else { 5945 filteredMerges.push(merge); 5946 } 5947 } 5948 filteredMerges.push(hungryMerge); 5949 merges = filteredMerges; 5950 } 5951 return merges; 5952 } 5953 function joinSpans(span0, span1) { 5954 return { 5955 start: Math.min(span0.start, span1.start), 5956 end: Math.max(span0.end, span1.end), 5957 }; 5958 } 5959 function intersectSpans(span0, span1) { 5960 let start = Math.max(span0.start, span1.start); 5961 let end = Math.min(span0.end, span1.end); 5962 if (start < end) { 5963 return { start, end }; 5964 } 5965 return null; 5966 } 5967 // general util 5968 // --------------------------------------------------------------------------------------------------------------------- 5969 function insertAt(arr, index, item) { 5970 arr.splice(index, 0, item); 5971 } 5972 function binarySearch(a, searchVal, getItemVal) { 5973 let startIndex = 0; 5974 let endIndex = a.length; // exclusive 5975 if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item 5976 return [0, 0]; 5977 } 5978 if (searchVal > getItemVal(a[endIndex - 1])) { // after last item 5979 return [endIndex, 0]; 5980 } 5981 while (startIndex < endIndex) { 5982 let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2);
vendor: 9,969 bytes, lines 5983-6198
5983 let middleVal = getItemVal(a[middleIndex]); 5984 if (searchVal < middleVal) { 5985 endIndex = middleIndex; 5986 } 5987 else if (searchVal > middleVal) { 5988 startIndex = middleIndex + 1; 5989 } 5990 else { // equal! 5991 return [middleIndex, 1]; 5992 } 5993 } 5994 return [startIndex, 0]; 5995 } 5996 5997 /* 5998 An abstraction for a dragging interaction originating on an event. 5999 Does higher-level things than PointerDragger, such as possibly: 6000 - a "mirror" that moves with the pointer 6001 - a minimum number of pixels or other criteria for a true drag to begin 6002 6003 subclasses must emit: 6004 - pointerdown 6005 - dragstart 6006 - dragmove 6007 - pointerup 6008 - dragend 6009 */ 6010 class ElementDragging { 6011 constructor(el, selector) { 6012 this.emitter = new Emitter(); 6013 } 6014 destroy() { 6015 } 6016 setMirrorIsVisible(bool) { 6017 // optional if subclass doesn't want to support a mirror 6018 } 6019 setMirrorNeedsRevert(bool) { 6020 // optional if subclass doesn't want to support a mirror 6021 } 6022 setAutoScrollEnabled(bool) { 6023 // optional 6024 } 6025 } 6026 6027 // TODO: get rid of this in favor of options system, 6028 // tho it's really easy to access this globally rather than pass thru options. 6029 const config = {}; 6030 6031 /* 6032 Information about what will happen when an external element is dragged-and-dropped 6033 onto a calendar. Contains information for creating an event. 6034 */ 6035 const DRAG_META_REFINERS = { 6036 startTime: createDuration, 6037 duration: createDuration, 6038 create: Boolean, 6039 sourceId: String, 6040 }; 6041 function parseDragMeta(raw) { 6042 let { refined, extra } = refineProps(raw, DRAG_META_REFINERS); 6043 return { 6044 startTime: refined.startTime || null, 6045 duration: refined.duration || null, 6046 create: refined.create != null ? refined.create : true, 6047 sourceId: refined.sourceId, 6048 leftoverProps: extra, 6049 }; 6050 } 6051 6052 // Computes a default column header formatting string if `colFormat` is not explicitly defined 6053 function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { 6054 // if more than one week row, or if there are a lot of columns with not much space, 6055 // put just the day numbers will be in each cell 6056 if (!datesRepDistinctDays || dayCnt > 10) { 6057 return createFormatter({ weekday: 'short' }); // "Sat" 6058 } 6059 if (dayCnt > 1) { 6060 return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" 6061 } 6062 return createFormatter({ weekday: 'long' }); // "Saturday" 6063 } 6064 6065 const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no 6066 function renderInner$1(renderProps) { 6067 return renderProps.text; 6068 } 6069 6070 // BAD name for this class now. used in the Header 6071 class TableDateCell extends BaseComponent { 6072 render() { 6073 let { dateEnv, options, theme, viewApi } = this.context; 6074 let { props } = this; 6075 let { date, dateProfile } = props; 6076 let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); 6077 let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); 6078 let text = dateEnv.format(date, props.dayHeaderFormat); 6079 // if colCnt is 1, we are already in a day-view and don't need a navlink 6080 let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1) 6081 ? buildNavLinkAttrs(this.context, date) 6082 : {}; 6083 let renderProps = Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraRenderProps), { text }), dayMeta); 6084 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: [ 6085 'fc-col-header-cell-cushion', 6086 props.isSticky && 'fc-sticky', 6087 ] })))))); 6088 } 6089 } 6090 6091 const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' }); 6092 class TableDowCell extends BaseComponent { 6093 render() { 6094 let { props } = this; 6095 let { dateEnv, theme, viewApi, options } = this.context; 6096 let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT 6097 let dateMeta = { 6098 dow: props.dow, 6099 isDisabled: false, 6100 isFuture: false, 6101 isPast: false, 6102 isToday: false, 6103 isOther: false, 6104 }; 6105 let text = dateEnv.format(date, props.dayHeaderFormat); 6106 let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public? 6107 date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text }); 6108 return (y(ContentContainer, { elTag: "th", elClasses: [ 6109 CLASS_NAME, 6110 ...getDayClassNames(dateMeta, theme), 6111 ...(props.extraClassNames || []), 6112 ], 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" }, 6113 y(InnerContent, { elTag: "a", elClasses: [ 6114 'fc-col-header-cell-cushion', 6115 props.isSticky && 'fc-sticky', 6116 ], elAttrs: { 6117 'aria-label': dateEnv.format(date, WEEKDAY_FORMAT), 6118 } }))))); 6119 } 6120 } 6121 6122 class NowTimer extends x$1 { 6123 constructor(props, context) { 6124 super(props, context); 6125 this.initialNowDate = getNow(context.options.now, context.dateEnv); 6126 this.initialNowQueriedMs = new Date().valueOf(); 6127 this.state = this.computeTiming().currentState; 6128 } 6129 render() { 6130 let { props, state } = this; 6131 return props.children(state.nowDate, state.todayRange); 6132 } 6133 componentDidMount() { 6134 this.setTimeout(); 6135 } 6136 componentDidUpdate(prevProps) { 6137 if (prevProps.unit !== this.props.unit) { 6138 this.clearTimeout(); 6139 this.setTimeout(); 6140 } 6141 } 6142 componentWillUnmount() { 6143 this.clearTimeout(); 6144 } 6145 computeTiming() { 6146 let { props, context } = this; 6147 let unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); 6148 let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); 6149 let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); 6150 let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); 6151 // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) 6152 // ensure no longer than a day 6153 waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); 6154 return { 6155 currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, 6156 nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, 6157 waitMs, 6158 }; 6159 } 6160 setTimeout() { 6161 let { nextState, waitMs } = this.computeTiming(); 6162 this.timeoutId = setTimeout(() => { 6163 this.setState(nextState, () => { 6164 this.setTimeout(); 6165 }); 6166 }, waitMs); 6167 } 6168 clearTimeout() { 6169 if (this.timeoutId) { 6170 clearTimeout(this.timeoutId); 6171 } 6172 } 6173 } 6174 NowTimer.contextType = ViewContextType; 6175 function buildDayRange(date) { 6176 let start = startOfDay(date); 6177 let end = addDays(start, 1); 6178 return { start, end }; 6179 } 6180 6181 class DayHeader extends BaseComponent { 6182 constructor() { 6183 super(...arguments); 6184 this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); 6185 } 6186 render() { 6187 let { context } = this; 6188 let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props; 6189 let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); 6190 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y("tr", { role: "row" }, 6191 renderIntro && renderIntro('day'), 6192 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 })))))))); 6193 } 6194 } 6195 function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { 6196 return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); 6197 } 6198
vendor: 16,771 bytes, lines 6199-6544
6199 class DaySeriesModel { 6200 constructor(range, dateProfileGenerator) { 6201 let date = range.start; 6202 let { end } = range; 6203 let indices = []; 6204 let dates = []; 6205 let dayIndex = -1; 6206 while (date < end) { // loop each day from start to end 6207 if (dateProfileGenerator.isHiddenDay(date)) { 6208 indices.push(dayIndex + 0.5); // mark that it's between indices 6209 } 6210 else { 6211 dayIndex += 1; 6212 indices.push(dayIndex); 6213 dates.push(date); 6214 } 6215 date = addDays(date, 1); 6216 } 6217 this.dates = dates; 6218 this.indices = indices; 6219 this.cnt = dates.length; 6220 } 6221 sliceRange(range) { 6222 let firstIndex = this.getDateDayIndex(range.start); // inclusive first index 6223 let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index 6224 let clippedFirstIndex = Math.max(0, firstIndex); 6225 let clippedLastIndex = Math.min(this.cnt - 1, lastIndex); 6226 // deal with in-between indices 6227 clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell 6228 clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell 6229 if (clippedFirstIndex <= clippedLastIndex) { 6230 return { 6231 firstIndex: clippedFirstIndex, 6232 lastIndex: clippedLastIndex, 6233 isStart: firstIndex === clippedFirstIndex, 6234 isEnd: lastIndex === clippedLastIndex, 6235 }; 6236 } 6237 return null; 6238 } 6239 // Given a date, returns its chronolocial cell-index from the first cell of the grid. 6240 // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. 6241 // If before the first offset, returns a negative number. 6242 // If after the last offset, returns an offset past the last cell offset. 6243 // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. 6244 getDateDayIndex(date) { 6245 let { indices } = this; 6246 let dayOffset = Math.floor(diffDays(this.dates[0], date)); 6247 if (dayOffset < 0) { 6248 return indices[0] - 1; 6249 } 6250 if (dayOffset >= indices.length) { 6251 return indices[indices.length - 1] + 1; 6252 } 6253 return indices[dayOffset]; 6254 } 6255 } 6256 6257 class DayTableModel { 6258 constructor(daySeries, breakOnWeeks) { 6259 let { dates } = daySeries; 6260 let daysPerRow; 6261 let firstDay; 6262 let rowCnt; 6263 if (breakOnWeeks) { 6264 // count columns until the day-of-week repeats 6265 firstDay = dates[0].getUTCDay(); 6266 for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { 6267 if (dates[daysPerRow].getUTCDay() === firstDay) { 6268 break; 6269 } 6270 } 6271 rowCnt = Math.ceil(dates.length / daysPerRow); 6272 } 6273 else { 6274 rowCnt = 1; 6275 daysPerRow = dates.length; 6276 } 6277 this.rowCnt = rowCnt; 6278 this.colCnt = daysPerRow; 6279 this.daySeries = daySeries; 6280 this.cells = this.buildCells(); 6281 this.headerDates = this.buildHeaderDates(); 6282 } 6283 buildCells() { 6284 let rows = []; 6285 for (let row = 0; row < this.rowCnt; row += 1) { 6286 let cells = []; 6287 for (let col = 0; col < this.colCnt; col += 1) { 6288 cells.push(this.buildCell(row, col)); 6289 } 6290 rows.push(cells); 6291 } 6292 return rows; 6293 } 6294 buildCell(row, col) { 6295 let date = this.daySeries.dates[row * this.colCnt + col]; 6296 return { 6297 key: date.toISOString(), 6298 date, 6299 }; 6300 } 6301 buildHeaderDates() { 6302 let dates = []; 6303 for (let col = 0; col < this.colCnt; col += 1) { 6304 dates.push(this.cells[0][col].date); 6305 } 6306 return dates; 6307 } 6308 sliceRange(range) { 6309 let { colCnt } = this; 6310 let seriesSeg = this.daySeries.sliceRange(range); 6311 let segs = []; 6312 if (seriesSeg) { 6313 let { firstIndex, lastIndex } = seriesSeg; 6314 let index = firstIndex; 6315 while (index <= lastIndex) { 6316 let row = Math.floor(index / colCnt); 6317 let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); 6318 segs.push({ 6319 row, 6320 firstCol: index % colCnt, 6321 lastCol: (nextIndex - 1) % colCnt, 6322 isStart: seriesSeg.isStart && index === firstIndex, 6323 isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, 6324 }); 6325 index = nextIndex; 6326 } 6327 } 6328 return segs; 6329 } 6330 } 6331 6332 class Slicer { 6333 constructor() { 6334 this.sliceBusinessHours = memoize(this._sliceBusinessHours); 6335 this.sliceDateSelection = memoize(this._sliceDateSpan); 6336 this.sliceEventStore = memoize(this._sliceEventStore); 6337 this.sliceEventDrag = memoize(this._sliceInteraction); 6338 this.sliceEventResize = memoize(this._sliceInteraction); 6339 this.forceDayIfListItem = false; // hack 6340 } 6341 sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) { 6342 let { eventUiBases } = props; 6343 let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs); 6344 return { 6345 dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs), 6346 businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs), 6347 fgEventSegs: eventSegs.fg, 6348 bgEventSegs: eventSegs.bg, 6349 eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), 6350 eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), 6351 eventSelection: props.eventSelection, 6352 }; // TODO: give interactionSegs? 6353 } 6354 sliceNowDate(// does not memoize 6355 date, dateProfile, nextDayThreshold, context, ...extraArgs) { 6356 return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range 6357 dateProfile, nextDayThreshold, {}, context, ...extraArgs); 6358 } 6359 _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) { 6360 if (!businessHours) { 6361 return []; 6362 } 6363 return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg; 6364 } 6365 _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { 6366 if (eventStore) { 6367 let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); 6368 return { 6369 bg: this.sliceEventRanges(rangeRes.bg, extraArgs), 6370 fg: this.sliceEventRanges(rangeRes.fg, extraArgs), 6371 }; 6372 } 6373 return { bg: [], fg: [] }; 6374 } 6375 _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { 6376 if (!interaction) { 6377 return null; 6378 } 6379 let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); 6380 return { 6381 segs: this.sliceEventRanges(rangeRes.fg, extraArgs), 6382 affectedInstances: interaction.affectedEvents.instances, 6383 isEvent: interaction.isEvent, 6384 }; 6385 } 6386 _sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) { 6387 if (!dateSpan) { 6388 return []; 6389 } 6390 let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold)); 6391 let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange); 6392 if (activeDateSpanRange) { 6393 dateSpan = Object.assign(Object.assign({}, dateSpan), { range: activeDateSpanRange }); 6394 let eventRange = fabricateEventRange(dateSpan, eventUiBases, context); 6395 let segs = this.sliceRange(dateSpan.range, ...extraArgs); 6396 for (let seg of segs) { 6397 seg.eventRange = eventRange; 6398 } 6399 return segs; 6400 } 6401 return []; 6402 } 6403 /* 6404 "complete" seg means it has component and eventRange 6405 */ 6406 sliceEventRanges(eventRanges, extraArgs) { 6407 let segs = []; 6408 for (let eventRange of eventRanges) { 6409 segs.push(...this.sliceEventRange(eventRange, extraArgs)); 6410 } 6411 return segs; 6412 } 6413 /* 6414 "complete" seg means it has component and eventRange 6415 */ 6416 sliceEventRange(eventRange, extraArgs) { 6417 let dateRange = eventRange.range; 6418 // hack to make multi-day events that are being force-displayed as list-items to take up only one day 6419 if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { 6420 dateRange = { 6421 start: dateRange.start, 6422 end: addDays(dateRange.start, 1), 6423 }; 6424 } 6425 let segs = this.sliceRange(dateRange, ...extraArgs); 6426 for (let seg of segs) { 6427 seg.eventRange = eventRange; 6428 seg.isStart = eventRange.isStart && seg.isStart; 6429 seg.isEnd = eventRange.isEnd && seg.isEnd; 6430 } 6431 return segs; 6432 } 6433 } 6434 /* 6435 for incorporating slotMinTime/slotMaxTime if appropriate 6436 TODO: should be part of DateProfile! 6437 TimelineDateProfile already does this btw 6438 */ 6439 function computeActiveRange(dateProfile, isComponentAllDay) { 6440 let range = dateProfile.activeRange; 6441 if (isComponentAllDay) { 6442 return range; 6443 } 6444 return { 6445 start: addMs(range.start, dateProfile.slotMinTime.milliseconds), 6446 end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day 6447 }; 6448 } 6449 6450 // high-level segmenting-aware tester functions 6451 // ------------------------------------------------------------------------------------------------------------------------ 6452 function isInteractionValid(interaction, dateProfile, context) { 6453 let { instances } = interaction.mutatedEvents; 6454 for (let instanceId in instances) { 6455 if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { 6456 return false; 6457 } 6458 } 6459 return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions 6460 } 6461 function isDateSelectionValid(dateSelection, dateProfile, context) { 6462 if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { 6463 return false; 6464 } 6465 return isNewPropsValid({ dateSelection }, context); 6466 } 6467 function isNewPropsValid(newProps, context) { 6468 let calendarState = context.getCurrentData(); 6469 let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); 6470 return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); 6471 } 6472 function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) { 6473 if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { 6474 return false; 6475 } 6476 if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { 6477 return false; 6478 } 6479 return true; 6480 } 6481 // Moving Event Validation 6482 // ------------------------------------------------------------------------------------------------------------------------ 6483 function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { 6484 let currentState = context.getCurrentData(); 6485 let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions 6486 let subjectEventStore = interaction.mutatedEvents; 6487 let subjectDefs = subjectEventStore.defs; 6488 let subjectInstances = subjectEventStore.instances; 6489 let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? 6490 state.eventUiBases : 6491 { '': currentState.selectionConfig }); 6492 if (filterConfig) { 6493 subjectConfigs = mapHash(subjectConfigs, filterConfig); 6494 } 6495 // exclude the subject events. TODO: exclude defs too? 6496 let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); 6497 let otherDefs = otherEventStore.defs; 6498 let otherInstances = otherEventStore.instances; 6499 let otherConfigs = compileEventUis(otherDefs, state.eventUiBases); 6500 for (let subjectInstanceId in subjectInstances) { 6501 let subjectInstance = subjectInstances[subjectInstanceId]; 6502 let subjectRange = subjectInstance.range; 6503 let subjectConfig = subjectConfigs[subjectInstance.defId]; 6504 let subjectDef = subjectDefs[subjectInstance.defId]; 6505 // constraint 6506 if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { 6507 return false; 6508 } 6509 // overlap 6510 let { eventOverlap } = context.options; 6511 let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; 6512 for (let otherInstanceId in otherInstances) { 6513 let otherInstance = otherInstances[otherInstanceId]; 6514 // intersect! evaluate 6515 if (rangesIntersect(subjectRange, otherInstance.range)) { 6516 let otherOverlap = otherConfigs[otherInstance.defId].overlap; 6517 // consider the other event's overlap. only do this if the subject event is a "real" event 6518 if (otherOverlap === false && interaction.isEvent) { 6519 return false; 6520 } 6521 if (subjectConfig.overlap === false) { 6522 return false; 6523 } 6524 if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event 6525 new EventImpl(context, subjectDef, subjectInstance))) { 6526 return false; 6527 } 6528 } 6529 } 6530 // allow (a function) 6531 let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state 6532 for (let subjectAllow of subjectConfig.allows) { 6533 let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); 6534 let origDef = calendarEventStore.defs[subjectDef.defId]; 6535 let origInstance = calendarEventStore.instances[subjectInstanceId]; 6536 let eventApi; 6537 if (origDef) { // was previously in the calendar 6538 eventApi = new EventImpl(context, origDef, origInstance); 6539 } 6540 else { // was an external event 6541 eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates 6542 } 6543 if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { 6544 return false;
vendor: 41,014 bytes, lines 6545-7374
6545 } 6546 } 6547 } 6548 return true; 6549 } 6550 // Date Selection Validation 6551 // ------------------------------------------------------------------------------------------------------------------------ 6552 function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { 6553 let relevantEventStore = state.eventStore; 6554 let relevantDefs = relevantEventStore.defs; 6555 let relevantInstances = relevantEventStore.instances; 6556 let selection = state.dateSelection; 6557 let selectionRange = selection.range; 6558 let { selectionConfig } = context.getCurrentData(); 6559 if (filterConfig) { 6560 selectionConfig = filterConfig(selectionConfig); 6561 } 6562 // constraint 6563 if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { 6564 return false; 6565 } 6566 // overlap 6567 let { selectOverlap } = context.options; 6568 let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; 6569 for (let relevantInstanceId in relevantInstances) { 6570 let relevantInstance = relevantInstances[relevantInstanceId]; 6571 // intersect! evaluate 6572 if (rangesIntersect(selectionRange, relevantInstance.range)) { 6573 if (selectionConfig.overlap === false) { 6574 return false; 6575 } 6576 if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { 6577 return false; 6578 } 6579 } 6580 } 6581 // allow (a function) 6582 for (let selectionAllow of selectionConfig.allows) { 6583 let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection); 6584 if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { 6585 return false; 6586 } 6587 } 6588 return true; 6589 } 6590 // Constraint Utils 6591 // ------------------------------------------------------------------------------------------------------------------------ 6592 function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { 6593 for (let constraint of constraints) { 6594 if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { 6595 return false; 6596 } 6597 } 6598 return true; 6599 } 6600 function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours 6601 otherEventStore, // for if constraint is an even group ID 6602 businessHoursUnexpanded, // for if constraint is 'businessHours' 6603 context) { 6604 if (constraint === 'businessHours') { 6605 return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); 6606 } 6607 if (typeof constraint === 'string') { // an group ID 6608 return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint)); 6609 } 6610 if (typeof constraint === 'object' && constraint) { // non-null object 6611 return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); 6612 } 6613 return []; // if it's false 6614 } 6615 // TODO: move to event-store file? 6616 function eventStoreToRanges(eventStore) { 6617 let { instances } = eventStore; 6618 let ranges = []; 6619 for (let instanceId in instances) { 6620 ranges.push(instances[instanceId].range); 6621 } 6622 return ranges; 6623 } 6624 // TODO: move to geom file? 6625 function anyRangesContainRange(outerRanges, innerRange) { 6626 for (let outerRange of outerRanges) { 6627 if (rangeContainsRange(outerRange, innerRange)) { 6628 return true; 6629 } 6630 } 6631 return false; 6632 } 6633 6634 const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; 6635 class Scroller extends BaseComponent { 6636 constructor() { 6637 super(...arguments); 6638 this.handleEl = (el) => { 6639 this.el = el; 6640 setRef(this.props.elRef, el); 6641 }; 6642 } 6643 render() { 6644 let { props } = this; 6645 let { liquid, liquidIsAbsolute } = props; 6646 let isAbsolute = liquid && liquidIsAbsolute; 6647 let className = ['fc-scroller']; 6648 if (liquid) { 6649 if (liquidIsAbsolute) { 6650 className.push('fc-scroller-liquid-absolute'); 6651 } 6652 else { 6653 className.push('fc-scroller-liquid'); 6654 } 6655 } 6656 return (y("div", { ref: this.handleEl, className: className.join(' '), style: { 6657 overflowX: props.overflowX, 6658 overflowY: props.overflowY, 6659 left: (isAbsolute && -(props.overcomeLeft || 0)) || '', 6660 right: (isAbsolute && -(props.overcomeRight || 0)) || '', 6661 bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', 6662 marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', 6663 marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', 6664 marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', 6665 maxHeight: props.maxHeight || '', 6666 } }, props.children)); 6667 } 6668 needsXScrolling() { 6669 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { 6670 return false; 6671 } 6672 // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. 6673 // much more reliable to see if children are taller than the scroller, even tho doesn't account for 6674 // inner-child margins and absolute positioning 6675 let { el } = this; 6676 let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); 6677 let { children } = el; 6678 for (let i = 0; i < children.length; i += 1) { 6679 let childEl = children[i]; 6680 if (childEl.getBoundingClientRect().width > realClientWidth) { 6681 return true; 6682 } 6683 } 6684 return false; 6685 } 6686 needsYScrolling() { 6687 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { 6688 return false; 6689 } 6690 // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. 6691 // much more reliable to see if children are taller than the scroller, even tho doesn't account for 6692 // inner-child margins and absolute positioning 6693 let { el } = this; 6694 let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); 6695 let { children } = el; 6696 for (let i = 0; i < children.length; i += 1) { 6697 let childEl = children[i]; 6698 if (childEl.getBoundingClientRect().height > realClientHeight) { 6699 return true; 6700 } 6701 } 6702 return false; 6703 } 6704 getXScrollbarWidth() { 6705 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { 6706 return 0; 6707 } 6708 return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? 6709 } 6710 getYScrollbarWidth() { 6711 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { 6712 return 0; 6713 } 6714 return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? 6715 } 6716 } 6717 6718 /* 6719 TODO: somehow infer OtherArgs from masterCallback? 6720 TODO: infer RefType from masterCallback if provided 6721 */ 6722 class RefMap { 6723 constructor(masterCallback) { 6724 this.masterCallback = masterCallback; 6725 this.currentMap = {}; 6726 this.depths = {}; 6727 this.callbackMap = {}; 6728 this.handleValue = (val, key) => { 6729 let { depths, currentMap } = this; 6730 let removed = false; 6731 let added = false; 6732 if (val !== null) { 6733 // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore 6734 removed = (key in currentMap); 6735 currentMap[key] = val; 6736 depths[key] = (depths[key] || 0) + 1; 6737 added = true; 6738 } 6739 else { 6740 depths[key] -= 1; 6741 if (!depths[key]) { 6742 delete currentMap[key]; 6743 delete this.callbackMap[key]; 6744 removed = true; 6745 } 6746 } 6747 if (this.masterCallback) { 6748 if (removed) { 6749 this.masterCallback(null, String(key)); 6750 } 6751 if (added) { 6752 this.masterCallback(val, String(key)); 6753 } 6754 } 6755 }; 6756 } 6757 createRef(key) { 6758 let refCallback = this.callbackMap[key]; 6759 if (!refCallback) { 6760 refCallback = this.callbackMap[key] = (val) => { 6761 this.handleValue(val, String(key)); 6762 }; 6763 } 6764 return refCallback; 6765 } 6766 // TODO: check callers that don't care about order. should use getAll instead 6767 // NOTE: this method has become less valuable now that we are encouraged to map order by some other index 6768 // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" 6769 collect(startIndex, endIndex, step) { 6770 return collectFromHash(this.currentMap, startIndex, endIndex, step); 6771 } 6772 getAll() { 6773 return hashValuesToArray(this.currentMap); 6774 } 6775 } 6776 6777 function computeShrinkWidth(chunkEls) { 6778 let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); 6779 let largestWidth = 0; 6780 for (let shrinkCell of shrinkCells) { 6781 largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); 6782 } 6783 return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits 6784 } 6785 function getSectionHasLiquidHeight(props, sectionConfig) { 6786 return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well) 6787 } 6788 function getAllowYScrolling(props, sectionConfig) { 6789 return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars 6790 getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars 6791 } 6792 // TODO: ONLY use `arg`. force out internal function to use same API 6793 function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) { 6794 let { expandRows } = arg; 6795 let content = typeof chunkConfig.content === 'function' ? 6796 chunkConfig.content(arg) : 6797 y('table', { 6798 role: 'presentation', 6799 className: [ 6800 chunkConfig.tableClassName, 6801 sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '', 6802 ].join(' '), 6803 style: { 6804 minWidth: arg.tableMinWidth, 6805 width: arg.clientWidth, 6806 height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height 6807 }, 6808 }, arg.tableColGroupNode, y(isHeader ? 'thead' : 'tbody', { 6809 role: 'presentation', 6810 }, typeof chunkConfig.rowContent === 'function' 6811 ? chunkConfig.rowContent(arg) 6812 : chunkConfig.rowContent)); 6813 return content; 6814 } 6815 function isColPropsEqual(cols0, cols1) { 6816 return isArraysEqual(cols0, cols1, isPropsEqual); 6817 } 6818 function renderMicroColGroup(cols, shrinkWidth) { 6819 let colNodes = []; 6820 /* 6821 for ColProps with spans, it would have been great to make a single <col span=""> 6822 HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans. 6823 SOLUTION: making individual <col> elements makes Chrome behave. 6824 */ 6825 for (let colProps of cols) { 6826 let span = colProps.span || 1; 6827 for (let i = 0; i < span; i += 1) { 6828 colNodes.push(y("col", { style: { 6829 width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''), 6830 minWidth: colProps.minWidth || '', 6831 } })); 6832 } 6833 } 6834 return y('colgroup', {}, ...colNodes); 6835 } 6836 function sanitizeShrinkWidth(shrinkWidth) { 6837 /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth 6838 4 accounts for 2 2-pixel borders. TODO: better solution? */ 6839 return shrinkWidth == null ? 4 : shrinkWidth; 6840 } 6841 function hasShrinkWidth(cols) { 6842 for (let col of cols) { 6843 if (col.width === 'shrink') { 6844 return true; 6845 } 6846 } 6847 return false; 6848 } 6849 function getScrollGridClassNames(liquid, context) { 6850 let classNames = [ 6851 'fc-scrollgrid', 6852 context.theme.getClass('table'), 6853 ]; 6854 if (liquid) { 6855 classNames.push('fc-scrollgrid-liquid'); 6856 } 6857 return classNames; 6858 } 6859 function getSectionClassNames(sectionConfig, wholeTableVGrow) { 6860 let classNames = [ 6861 'fc-scrollgrid-section', 6862 `fc-scrollgrid-section-${sectionConfig.type}`, 6863 sectionConfig.className, // used? 6864 ]; 6865 if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) { 6866 classNames.push('fc-scrollgrid-section-liquid'); 6867 } 6868 if (sectionConfig.isSticky) { 6869 classNames.push('fc-scrollgrid-section-sticky'); 6870 } 6871 return classNames; 6872 } 6873 function renderScrollShim(arg) { 6874 return (y("div", { className: "fc-scrollgrid-sticky-shim", style: { 6875 width: arg.clientWidth, 6876 minWidth: arg.tableMinWidth, 6877 } })); 6878 } 6879 function getStickyHeaderDates(options) { 6880 let { stickyHeaderDates } = options; 6881 if (stickyHeaderDates == null || stickyHeaderDates === 'auto') { 6882 stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto'; 6883 } 6884 return stickyHeaderDates; 6885 } 6886 function getStickyFooterScrollbar(options) { 6887 let { stickyFooterScrollbar } = options; 6888 if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') { 6889 stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto'; 6890 } 6891 return stickyFooterScrollbar; 6892 } 6893 6894 class SimpleScrollGrid extends BaseComponent { 6895 constructor() { 6896 super(...arguments); 6897 this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time 6898 // yucky to memoize VNodes, but much more efficient for consumers 6899 this.renderMicroColGroup = memoize(renderMicroColGroup); 6900 this.scrollerRefs = new RefMap(); 6901 this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this)); 6902 this.state = { 6903 shrinkWidth: null, 6904 forceYScrollbars: false, 6905 scrollerClientWidths: {}, 6906 scrollerClientHeights: {}, 6907 }; 6908 // TODO: can do a really simple print-view. dont need to join rows 6909 this.handleSizing = () => { 6910 this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims())); 6911 }; 6912 } 6913 render() { 6914 let { props, state, context } = this; 6915 let sectionConfigs = props.sections || []; 6916 let cols = this.processCols(props.cols); 6917 let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth); 6918 let classNames = getScrollGridClassNames(props.liquid, context); 6919 if (props.collapsibleWidth) { 6920 classNames.push('fc-scrollgrid-collapsible'); 6921 } 6922 // TODO: make DRY 6923 let configCnt = sectionConfigs.length; 6924 let configI = 0; 6925 let currentConfig; 6926 let headSectionNodes = []; 6927 let bodySectionNodes = []; 6928 let footSectionNodes = []; 6929 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { 6930 headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true)); 6931 configI += 1; 6932 } 6933 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { 6934 bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false)); 6935 configI += 1; 6936 } 6937 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { 6938 footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true)); 6939 configI += 1; 6940 } 6941 // firefox bug: when setting height on table and there is a thead or tfoot, 6942 // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524) 6943 // use getCanVGrowWithinCell as a way to detect table-stupid firefox. 6944 // if so, use a simpler dom structure, jam everything into a lone tbody. 6945 let isBuggy = !getCanVGrowWithinCell(); 6946 const roleAttrs = { role: 'rowgroup' }; 6947 return y('table', { 6948 role: 'grid', 6949 className: classNames.join(' '), 6950 style: { height: props.height }, 6951 }, 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)); 6952 } 6953 renderSection(sectionConfig, microColGroupNode, isHeader) { 6954 if ('outerContent' in sectionConfig) { 6955 return (y(_, { key: sectionConfig.key }, sectionConfig.outerContent)); 6956 } 6957 return (y("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader))); 6958 } 6959 renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) { 6960 if ('outerContent' in chunkConfig) { 6961 return chunkConfig.outerContent; 6962 } 6963 let { props } = this; 6964 let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state; 6965 let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config? 6966 let isLiquid = getSectionHasLiquidHeight(props, sectionConfig); 6967 // for `!props.liquid` - is WHOLE scrollgrid natural height? 6968 // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars 6969 let overflowY = !props.liquid ? 'visible' : 6970 forceYScrollbars ? 'scroll' : 6971 !needsYScrolling ? 'hidden' : 6972 'auto'; 6973 let sectionKey = sectionConfig.key; 6974 let content = renderChunkContent(sectionConfig, chunkConfig, { 6975 tableColGroupNode: microColGroupNode, 6976 tableMinWidth: '', 6977 clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null, 6978 clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null, 6979 expandRows: sectionConfig.expandRows, 6980 syncRowHeights: false, 6981 rowSyncHeights: [], 6982 reportRowHeightChange: () => { }, 6983 }, isHeader); 6984 return y(isHeader ? 'th' : 'td', { 6985 ref: chunkConfig.elRef, 6986 role: 'presentation', 6987 }, y("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` }, 6988 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 6989 : true }, content))); 6990 } 6991 _handleScrollerEl(scrollerEl, key) { 6992 let section = getSectionByKey(this.props.sections, key); 6993 if (section) { 6994 setRef(section.chunk.scrollerElRef, scrollerEl); 6995 } 6996 } 6997 componentDidMount() { 6998 this.handleSizing(); 6999 this.context.addResizeHandler(this.handleSizing); 7000 } 7001 componentDidUpdate() { 7002 // TODO: need better solution when state contains non-sizing things 7003 this.handleSizing(); 7004 } 7005 componentWillUnmount() { 7006 this.context.removeResizeHandler(this.handleSizing); 7007 } 7008 computeShrinkWidth() { 7009 return hasShrinkWidth(this.props.cols) 7010 ? computeShrinkWidth(this.scrollerElRefs.getAll()) 7011 : 0; 7012 } 7013 computeScrollerDims() { 7014 let scrollbarWidth = getScrollbarWidths(); 7015 let { scrollerRefs, scrollerElRefs } = this; 7016 let forceYScrollbars = false; 7017 let scrollerClientWidths = {}; 7018 let scrollerClientHeights = {}; 7019 for (let sectionKey in scrollerRefs.currentMap) { 7020 let scroller = scrollerRefs.currentMap[sectionKey]; 7021 if (scroller && scroller.needsYScrolling()) { 7022 forceYScrollbars = true; 7023 break; 7024 } 7025 } 7026 for (let section of this.props.sections) { 7027 let sectionKey = section.key; 7028 let scrollerEl = scrollerElRefs.currentMap[sectionKey]; 7029 if (scrollerEl) { 7030 let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders 7031 scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars 7032 ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future 7033 : 0)); 7034 scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height); 7035 } 7036 } 7037 return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights }; 7038 } 7039 } 7040 SimpleScrollGrid.addStateEquality({ 7041 scrollerClientWidths: isPropsEqual, 7042 scrollerClientHeights: isPropsEqual, 7043 }); 7044 function getSectionByKey(sections, key) { 7045 for (let section of sections) { 7046 if (section.key === key) { 7047 return section; 7048 } 7049 } 7050 return null; 7051 } 7052 7053 class EventContainer extends BaseComponent { 7054 constructor() { 7055 super(...arguments); 7056 this.handleEl = (el) => { 7057 this.el = el; 7058 if (el) { 7059 setElSeg(el, this.props.seg); 7060 } 7061 }; 7062 } 7063 render() { 7064 const { props, context } = this; 7065 const { options } = context; 7066 const { seg } = props; 7067 const { eventRange } = seg; 7068 const { ui } = eventRange; 7069 const renderProps = { 7070 event: new EventImpl(context, eventRange.def, eventRange.instance), 7071 view: context.viewApi, 7072 timeText: props.timeText, 7073 textColor: ui.textColor, 7074 backgroundColor: ui.backgroundColor, 7075 borderColor: ui.borderColor, 7076 isDraggable: !props.disableDragging && computeSegDraggable(seg, context), 7077 isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context), 7078 isEndResizable: !props.disableResizing && computeSegEndResizable(seg), 7079 isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting), 7080 isStart: Boolean(seg.isStart), 7081 isEnd: Boolean(seg.isEnd), 7082 isPast: Boolean(props.isPast), 7083 isFuture: Boolean(props.isFuture), 7084 isToday: Boolean(props.isToday), 7085 isSelected: Boolean(props.isSelected), 7086 isDragging: Boolean(props.isDragging), 7087 isResizing: Boolean(props.isResizing), 7088 }; 7089 return (y(ContentContainer, Object.assign({}, props /* contains children */, { elRef: this.handleEl, elClasses: [ 7090 ...getEventClassNames(renderProps), 7091 ...seg.eventRange.ui.classNames, 7092 ...(props.elClasses || []), 7093 ], renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount }))); 7094 } 7095 componentDidUpdate(prevProps) { 7096 if (this.el && this.props.seg !== prevProps.seg) { 7097 setElSeg(this.el, this.props.seg); 7098 } 7099 } 7100 } 7101 7102 // should not be a purecomponent 7103 class StandardEvent extends BaseComponent { 7104 render() { 7105 let { props, context } = this; 7106 let { options } = context; 7107 let { seg } = props; 7108 let { ui } = seg.eventRange; 7109 let timeFormat = options.eventTimeFormat || props.defaultTimeFormat; 7110 let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd); 7111 return (y(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: { 7112 borderColor: ui.borderColor, 7113 backgroundColor: ui.backgroundColor, 7114 }, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1$1, timeText: timeText }), (InnerContent, eventContentArg) => (y(_, null, 7115 y(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }), 7116 Boolean(eventContentArg.isStartResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-start" })), 7117 Boolean(eventContentArg.isEndResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-end" })))))); 7118 } 7119 } 7120 function renderInnerContent$1$1(innerProps) { 7121 return (y("div", { className: "fc-event-main-frame" }, 7122 innerProps.timeText && (y("div", { className: "fc-event-time" }, innerProps.timeText)), 7123 y("div", { className: "fc-event-title-container" }, 7124 y("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || y(_, null, "\u00A0"))))); 7125 } 7126 7127 const NowIndicatorContainer = (props) => (y(ViewContextType.Consumer, null, (context) => { 7128 let { options } = context; 7129 let renderProps = { 7130 isAxis: props.isAxis, 7131 date: context.dateEnv.toDate(props.date), 7132 view: context.viewApi, 7133 }; 7134 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 }))); 7135 })); 7136 7137 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' }); 7138 class DayCellContainer extends BaseComponent { 7139 constructor() { 7140 super(...arguments); 7141 this.refineRenderProps = memoizeObjArg(refineRenderProps); 7142 } 7143 render() { 7144 let { props, context } = this; 7145 let { options } = context; 7146 let renderProps = this.refineRenderProps({ 7147 date: props.date, 7148 dateProfile: props.dateProfile, 7149 todayRange: props.todayRange, 7150 isMonthStart: props.isMonthStart || false, 7151 showDayNumber: props.showDayNumber, 7152 extraRenderProps: props.extraRenderProps, 7153 viewApi: context.viewApi, 7154 dateEnv: context.dateEnv, 7155 monthStartFormat: options.monthStartFormat, 7156 }); 7157 return (y(ContentContainer, Object.assign({}, props /* includes children */, { elClasses: [ 7158 ...getDayClassNames(renderProps, context.theme), 7159 ...(props.elClasses || []), 7160 ], 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: 7161 // don't use custom classNames if disabled 7162 renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount }))); 7163 } 7164 } 7165 function hasCustomDayCellContent(options) { 7166 return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options)); 7167 } 7168 function refineRenderProps(raw) { 7169 let { date, dateEnv, dateProfile, isMonthStart } = raw; 7170 let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile); 7171 let dayNumberText = raw.showDayNumber ? (dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)) : ''; 7172 return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { isMonthStart, 7173 dayNumberText }), raw.extraRenderProps); 7174 } 7175 7176 class BgEvent extends BaseComponent { 7177 render() { 7178 let { props } = this; 7179 let { seg } = props; 7180 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 })); 7181 } 7182 } 7183 function renderInnerContent$3(props) { 7184 let { title } = props.event; 7185 return title && (y("div", { className: "fc-event-title" }, props.event.title)); 7186 } 7187 function renderFill(fillType) { 7188 return (y("div", { className: `fc-${fillType}` })); 7189 } 7190 7191 const WeekNumberContainer = (props) => (y(ViewContextType.Consumer, null, (context) => { 7192 let { dateEnv, options } = context; 7193 let { date } = props; 7194 let format = options.weekNumberFormat || props.defaultFormat; 7195 let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well? 7196 let text = dateEnv.format(date, format); 7197 let renderProps = { num, text, date }; 7198 return (y(ContentContainer // why isn't WeekNumberContentArg being auto-detected? 7199 , Object.assign({}, props /* includes children */, { renderProps: renderProps, generatorName: "weekNumberContent", customGenerator: options.weekNumberContent, defaultGenerator: renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }))); 7200 })); 7201 function renderInner(innerProps) { 7202 return innerProps.text; 7203 } 7204 7205 const PADDING_FROM_VIEWPORT = 10; 7206 class Popover extends BaseComponent { 7207 constructor() { 7208 super(...arguments); 7209 this.state = { 7210 titleId: getUniqueDomId(), 7211 }; 7212 this.handleRootEl = (el) => { 7213 this.rootEl = el; 7214 if (this.props.elRef) { 7215 setRef(this.props.elRef, el); 7216 } 7217 }; 7218 // Triggered when the user clicks *anywhere* in the document, for the autoHide feature 7219 this.handleDocumentMouseDown = (ev) => { 7220 // only hide the popover if the click happened outside the popover 7221 const target = getEventTargetViaRoot(ev); 7222 if (!this.rootEl.contains(target)) { 7223 this.handleCloseClick(); 7224 } 7225 }; 7226 this.handleDocumentKeyDown = (ev) => { 7227 if (ev.key === 'Escape') { 7228 this.handleCloseClick(); 7229 } 7230 }; 7231 this.handleCloseClick = () => { 7232 let { onClose } = this.props; 7233 if (onClose) { 7234 onClose(); 7235 } 7236 }; 7237 } 7238 render() { 7239 let { theme, options } = this.context; 7240 let { props, state } = this; 7241 let classNames = [ 7242 'fc-popover', 7243 theme.getClass('popover'), 7244 ].concat(props.extraClassNames || []); 7245 return j(y("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }), 7246 y("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') }, 7247 y("span", { className: "fc-popover-title", id: state.titleId }, props.title), 7248 y("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })), 7249 y("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl); 7250 } 7251 componentDidMount() { 7252 document.addEventListener('mousedown', this.handleDocumentMouseDown); 7253 document.addEventListener('keydown', this.handleDocumentKeyDown); 7254 this.updateSize(); 7255 } 7256 componentWillUnmount() { 7257 document.removeEventListener('mousedown', this.handleDocumentMouseDown); 7258 document.removeEventListener('keydown', this.handleDocumentKeyDown); 7259 } 7260 updateSize() { 7261 let { isRtl } = this.context; 7262 let { alignmentEl, alignGridTop } = this.props; 7263 let { rootEl } = this; 7264 let alignmentRect = computeClippedClientRect(alignmentEl); 7265 if (alignmentRect) { 7266 let popoverDims = rootEl.getBoundingClientRect(); 7267 // position relative to viewport 7268 let popoverTop = alignGridTop 7269 ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top 7270 : alignmentRect.top; 7271 let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left; 7272 // constrain 7273 popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT); 7274 popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width); 7275 popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT); 7276 let origin = rootEl.offsetParent.getBoundingClientRect(); 7277 applyStyle(rootEl, { 7278 top: popoverTop - origin.top, 7279 left: popoverLeft - origin.left, 7280 }); 7281 } 7282 } 7283 } 7284 7285 class MorePopover extends DateComponent { 7286 constructor() { 7287 super(...arguments); 7288 this.handleRootEl = (rootEl) => { 7289 this.rootEl = rootEl; 7290 if (rootEl) { 7291 this.context.registerInteractiveComponent(this, { 7292 el: rootEl, 7293 useEventCenter: false, 7294 }); 7295 } 7296 else { 7297 this.context.unregisterInteractiveComponent(this); 7298 } 7299 }; 7300 } 7301 render() { 7302 let { options, dateEnv } = this.context; 7303 let { props } = this; 7304 let { startDate, todayRange, dateProfile } = props; 7305 let title = dateEnv.format(startDate, options.dayPopoverFormat); 7306 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 }, 7307 hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })), 7308 props.children)))); 7309 } 7310 queryHit(positionLeft, positionTop, elWidth, elHeight) { 7311 let { rootEl, props } = this; 7312 if (positionLeft >= 0 && positionLeft < elWidth && 7313 positionTop >= 0 && positionTop < elHeight) { 7314 return { 7315 dateProfile: props.dateProfile, 7316 dateSpan: Object.assign({ allDay: !props.forceTimed, range: { 7317 start: props.startDate, 7318 end: props.endDate, 7319 } }, props.extraDateSpan), 7320 dayEl: rootEl, 7321 rect: { 7322 left: 0, 7323 top: 0, 7324 right: elWidth, 7325 bottom: elHeight, 7326 }, 7327 layer: 1, // important when comparing with hits from other components 7328 }; 7329 } 7330 return null; 7331 } 7332 } 7333 7334 class MoreLinkContainer extends BaseComponent { 7335 constructor() { 7336 super(...arguments); 7337 this.state = { 7338 isPopoverOpen: false, 7339 popoverId: getUniqueDomId(), 7340 }; 7341 this.handleLinkEl = (linkEl) => { 7342 this.linkEl = linkEl; 7343 if (this.props.elRef) { 7344 setRef(this.props.elRef, linkEl); 7345 } 7346 }; 7347 this.handleClick = (ev) => { 7348 let { props, context } = this; 7349 let { moreLinkClick } = context.options; 7350 let date = computeRange(props).start; 7351 function buildPublicSeg(seg) { 7352 let { def, instance, range } = seg.eventRange; 7353 return { 7354 event: new EventImpl(context, def, instance), 7355 start: context.dateEnv.toDate(range.start), 7356 end: context.dateEnv.toDate(range.end), 7357 isStart: seg.isStart, 7358 isEnd: seg.isEnd, 7359 }; 7360 } 7361 if (typeof moreLinkClick === 'function') { 7362 moreLinkClick = moreLinkClick({ 7363 date, 7364 allDay: Boolean(props.allDayDate), 7365 allSegs: props.allSegs.map(buildPublicSeg), 7366 hiddenSegs: props.hiddenSegs.map(buildPublicSeg), 7367 jsEvent: ev, 7368 view: context.viewApi, 7369 }); 7370 } 7371 if (!moreLinkClick || moreLinkClick === 'popover') { 7372 this.setState({ isPopoverOpen: true }); 7373 } 7374 else if (typeof moreLinkClick === 'string') { // a view n
vendor: 4,546 bytes, lines 7374-7479
7374ame 7375 context.calendarApi.zoomTo(date, moreLinkClick); 7376 } 7377 }; 7378 this.handlePopoverClose = () => { 7379 this.setState({ isPopoverOpen: false }); 7380 }; 7381 } 7382 render() { 7383 let { props, state } = this; 7384 return (y(ViewContextType.Consumer, null, (context) => { 7385 let { viewApi, options, calendarApi } = context; 7386 let { moreLinkText } = options; 7387 let { moreCnt } = props; 7388 let range = computeRange(props); 7389 let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals 7390 ? moreLinkText.call(calendarApi, moreCnt) 7391 : `+${moreCnt} ${moreLinkText}`; 7392 let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text); 7393 let renderProps = { 7394 num: moreCnt, 7395 shortText: `+${moreCnt}`, 7396 text, 7397 view: viewApi, 7398 }; 7399 return (y(_, null, 7400 Boolean(props.moreCnt) && (y(ContentContainer, { elTag: props.elTag || 'a', elRef: this.handleLinkEl, elClasses: [ 7401 ...(props.elClasses || []), 7402 'fc-more-link', 7403 ], 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)), 7404 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 ? 7405 props.alignmentElRef.current : 7406 this.linkEl, alignGridTop: props.alignGridTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose }, props.popoverContent())))); 7407 })); 7408 } 7409 componentDidMount() { 7410 this.updateParentEl(); 7411 } 7412 componentDidUpdate() { 7413 this.updateParentEl(); 7414 } 7415 updateParentEl() { 7416 if (this.linkEl) { 7417 this.parentEl = elementClosest(this.linkEl, '.fc-view-harness'); 7418 } 7419 } 7420 } 7421 function renderMoreLinkInner$1(props) { 7422 return props.text; 7423 } 7424 function computeRange(props) { 7425 if (props.allDayDate) { 7426 return { 7427 start: props.allDayDate, 7428 end: addDays(props.allDayDate, 1), 7429 }; 7430 } 7431 let { hiddenSegs } = props; 7432 return { 7433 start: computeEarliestSegStart(hiddenSegs), 7434 end: computeLatestSegEnd(hiddenSegs), 7435 }; 7436 } 7437 function computeEarliestSegStart(segs) { 7438 return segs.reduce(pickEarliestStart).eventRange.range.start; 7439 } 7440 function pickEarliestStart(seg0, seg1) { 7441 return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1; 7442 } 7443 function computeLatestSegEnd(segs) { 7444 return segs.reduce(pickLatestEnd).eventRange.range.end; 7445 } 7446 function pickLatestEnd(seg0, seg1) { 7447 return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1; 7448 } 7449 7450 class Store { 7451 constructor() { 7452 this.handlers = []; 7453 } 7454 set(value) { 7455 this.currentValue = value; 7456 for (let handler of this.handlers) { 7457 handler(value); 7458 } 7459 } 7460 subscribe(handler) { 7461 this.handlers.push(handler); 7462 if (this.currentValue !== undefined) { 7463 handler(this.currentValue); 7464 } 7465 } 7466 } 7467 7468 /* 7469 Subscribers will get a LIST of CustomRenderings 7470 */ 7471 class CustomRenderingStore extends Store { 7472 constructor() { 7473 super(...arguments); 7474 this.map = new Map(); 7475 } 7476 // for consistent order 7477 handle(customRendering) { 7478 const { map } = this; 7479 let updated = false;
7480 if (customRendering.isActive) { 7481 map.set(customRendering.id, customRendering); 7482 updated = true; 7483 } 7484 else if (map.has(customRendering.id)) { 7485 map.delete(customRendering.id); 7486 updated = true; 7487 } 7488 if (updated) { 7489 this.set(map); 7490 } 7491 } 7492 } 7493 7494 var internal = { 7495 __proto__: null, 7496 BASE_OPTION_DEFAULTS: BASE_OPTION_DEFAULTS, 7497 BaseComponent: BaseComponent, 7498 BgEvent: BgEvent, 7499 CalendarImpl: CalendarImpl, 7500 CalendarRoot: CalendarRoot, 7501 ContentContainer: ContentContainer, 7502 CustomRenderingStore: CustomRenderingStore, 7503 DateComponent: DateComponent, 7504 DateEnv: DateEnv, 7505 DateProfileGenerator: DateProfileGenerator, 7506 DayCellContainer: DayCellContainer, 7507 DayHeader: DayHeader, 7508 DaySeriesModel: DaySeriesModel, 7509 DayTableModel: DayTableModel, 7510 DelayedRunner: DelayedRunner, 7511 ElementDragging: ElementDragging, 7512 ElementScrollController: ElementScrollController, 7513 Emitter: Emitter, 7514 EventContainer: EventContainer, 7515 EventImpl: EventImpl, 7516 Interaction: Interaction, 7517 MoreLinkContainer: MoreLinkContainer, 7518 NamedTimeZoneImpl: NamedTimeZoneImpl, 7519 NowIndicatorContainer: NowIndicatorContainer, 7520 NowTimer: NowTimer, 7521 PositionCache: PositionCache, 7522 RefMap: RefMap, 7523 ScrollController: ScrollController, 7524 ScrollResponder: ScrollResponder, 7525 Scroller: Scroller, 7526 SegHierarchy: SegHierarchy, 7527 SimpleScrollGrid: SimpleScrollGrid, 7528 Slicer: Slicer, 7529 Splitter: Splitter, 7530 StandardEvent: StandardEvent, 7531 TableDateCell: TableDateCell, 7532 TableDowCell: TableDowCell, 7533 Theme: Theme, 7534 ViewContainer: ViewContainer, 7535 ViewContextType: ViewContextType, 7536 WeekNumberContainer: WeekNumberContainer, 7537 WindowScrollController: WindowScrollController, 7538 addDays: addDays, 7539 addDurations: addDurations, 7540 addMs: addMs, 7541 addWeeks: addWeeks, 7542 allowContextMenu: allowContextMenu, 7543 allowSelection: allowSelection, 7544 applyMutationToEventStore: applyMutationToEventStore, 7545 applyStyle: applyStyle, 7546 asCleanDays: asCleanDays, 7547 asRoughMinutes: asRoughMinutes, 7548 asRoughMs: asRoughMs, 7549 asRoughSeconds: asRoughSeconds, 7550 binarySearch: binarySearch, 7551 buildElAttrs: buildElAttrs, 7552 buildEntryKey: buildEntryKey, 7553 buildEventApis: buildEventApis, 7554 buildEventRangeKey: buildEventRangeKey, 7555 buildIsoString: buildIsoString, 7556 buildNavLinkAttrs: buildNavLinkAttrs, 7557 buildSegTimeText: buildSegTimeText, 7558 collectFromHash: collectFromHash, 7559 combineEventUis: combineEventUis, 7560 compareByFieldSpecs: compareByFieldSpecs, 7561 compareNumbers: compareNumbers, 7562 compareObjs: compareObjs, 7563 computeEarliestSegStart: computeEarliestSegStart, 7564 computeEdges: computeEdges, 7565 computeFallbackHeaderFormat: computeFallbackHeaderFormat, 7566 computeInnerRect: computeInnerRect, 7567 computeRect: computeRect, 7568 computeShrinkWidth: computeShrinkWidth, 7569 computeVisibleDayRange: computeVisibleDayRange, 7570 config: config, 7571 constrainPoint: constrainPoint, 7572 createDuration: createDuration, 7573 createEmptyEventStore: createEmptyEventStore, 7574 createEventInstance: createEventInstance, 7575 createEventUi: createEventUi, 7576 createFormatter: createFormatter, 7577 diffDates: diffDates, 7578 diffDayAndTime: diffDayAndTime, 7579 diffDays: diffDays, 7580 diffPoints: diffPoints, 7581 diffWeeks: diffWeeks, 7582 diffWholeDays: diffWholeDays, 7583 diffWholeWeeks: diffWholeWeeks, 7584 disableCursor: disableCursor, 7585 elementClosest: elementClosest, 7586 elementMatches: elementMatches, 7587 enableCursor: enableCursor, 7588 eventTupleToStore: eventTupleToStore, 7589 filterHash: filterHash, 7590 findDirectChildren: findDirectChildren, 7591 findElements: findElements, 7592 flexibleCompare: flexibleCompare, 7593 formatDayString: formatDayString, 7594 formatIsoMonthStr: formatIsoMonthStr, 7595 formatIsoTimeString: formatIsoTimeString, 7596 getAllowYScrolling: getAllowYScrolling, 7597 getCanVGrowWithinCell: getCanVGrowWithinCell, 7598 getClippingParents: getClippingParents, 7599 getDateMeta: getDateMeta, 7600 getDayClassNames: getDayClassNames, 7601 getDefaultEventEnd: getDefaultEventEnd, 7602 getElSeg: getElSeg, 7603 getEntrySpanEnd: getEntrySpanEnd, 7604 getEventTargetViaRoot: getEventTargetViaRoot, 7605 getIsRtlScrollbarOnLeft: getIsRtlScrollbarOnLeft, 7606 getRectCenter: getRectCenter, 7607 getRelevantEvents: getRelevantEvents, 7608 getScrollGridClassNames: getScrollGridClassNames, 7609 getScrollbarWidths: getScrollbarWidths, 7610 getSectionClassNames: getSectionClassNames, 7611 getSectionHasLiquidHeight: getSectionHasLiquidHeight, 7612 getSegAnchorAttrs: getSegAnchorAttrs, 7613 getSegMeta: getSegMeta, 7614 getSlotClassNames: getSlotClassNames, 7615 getStickyFooterScrollbar: getStickyFooterScrollbar, 7616 getStickyHeaderDates: getStickyHeaderDates, 7617 getUniqueDomId: getUniqueDomId, 7618 greatestDurationDenominator: greatestDurationDenominator, 7619 groupIntersectingEntries: groupIntersectingEntries, 7620 guid: guid, 7621 hasBgRendering: hasBgRendering, 7622 hasCustomDayCellContent: hasCustomDayCellContent, 7623 hasShrinkWidth: hasShrinkWidth, 7624 identity: identity, 7625 injectStyles: injectStyles, 7626 interactionSettingsStore: interactionSettingsStore, 7627 interactionSettingsToStore: interactionSettingsToStore, 7628 intersectRanges: intersectRanges,
vendor: 7,522 bytes, lines 7629-7823
7629 intersectRects: intersectRects, 7630 intersectSpans: intersectSpans, 7631 isArraysEqual: isArraysEqual, 7632 isColPropsEqual: isColPropsEqual, 7633 isDateSelectionValid: isDateSelectionValid, 7634 isDateSpansEqual: isDateSpansEqual, 7635 isInt: isInt, 7636 isInteractionValid: isInteractionValid, 7637 isMultiDayRange: isMultiDayRange, 7638 isPropsEqual: isPropsEqual, 7639 isPropsValid: isPropsValid, 7640 isValidDate: isValidDate, 7641 mapHash: mapHash, 7642 memoize: memoize, 7643 memoizeArraylike: memoizeArraylike, 7644 memoizeHashlike: memoizeHashlike, 7645 memoizeObjArg: memoizeObjArg, 7646 mergeEventStores: mergeEventStores, 7647 multiplyDuration: multiplyDuration, 7648 padStart: padStart, 7649 parseBusinessHours: parseBusinessHours, 7650 parseClassNames: parseClassNames, 7651 parseDragMeta: parseDragMeta, 7652 parseEventDef: parseEventDef, 7653 parseFieldSpecs: parseFieldSpecs, 7654 parseMarker: parse, 7655 pointInsideRect: pointInsideRect, 7656 preventContextMenu: preventContextMenu, 7657 preventDefault: preventDefault, 7658 preventSelection: preventSelection, 7659 rangeContainsMarker: rangeContainsMarker, 7660 rangeContainsRange: rangeContainsRange, 7661 rangesEqual: rangesEqual, 7662 rangesIntersect: rangesIntersect, 7663 refineEventDef: refineEventDef, 7664 refineProps: refineProps, 7665 removeElement: removeElement, 7666 removeExact: removeExact, 7667 renderChunkContent: renderChunkContent, 7668 renderFill: renderFill, 7669 renderMicroColGroup: renderMicroColGroup, 7670 renderScrollShim: renderScrollShim, 7671 requestJson: requestJson, 7672 sanitizeShrinkWidth: sanitizeShrinkWidth, 7673 setRef: setRef, 7674 sliceEventStore: sliceEventStore, 7675 sortEventSegs: sortEventSegs, 7676 startOfDay: startOfDay, 7677 translateRect: translateRect, 7678 triggerDateSelect: triggerDateSelect, 7679 unpromisify: unpromisify, 7680 whenTransitionDone: whenTransitionDone, 7681 wholeDivideDurations: wholeDivideDurations 7682 }; 7683 7684 var preact = { 7685 __proto__: null, 7686 createPortal: j, 7687 createContext: createContext, 7688 flushSync: flushSync, 7689 Component: x$1, 7690 Fragment: _, 7691 cloneElement: F$1, 7692 createElement: y, 7693 createRef: d, 7694 h: y, 7695 hydrate: E, 7696 get isValidElement () { return i$1; }, 7697 get options () { return l$1; }, 7698 render: D$1, 7699 toChildArray: j$2 7700 }; 7701 7702 const globalLocales = []; 7703 7704 const MINIMAL_RAW_EN_LOCALE = { 7705 code: 'en', 7706 week: { 7707 dow: 0, 7708 doy: 4, // 4 days need to be within the year to be considered the first week 7709 }, 7710 direction: 'ltr', 7711 buttonText: { 7712 prev: 'prev', 7713 next: 'next', 7714 prevYear: 'prev year', 7715 nextYear: 'next year', 7716 year: 'year', 7717 today: 'today', 7718 month: 'month', 7719 week: 'week', 7720 day: 'day', 7721 list: 'list', 7722 }, 7723 weekText: 'W', 7724 weekTextLong: 'Week', 7725 closeHint: 'Close', 7726 timeHint: 'Time', 7727 eventHint: 'Event', 7728 allDayText: 'all-day', 7729 moreLinkText: 'more', 7730 noEventsText: 'No events to display', 7731 }; 7732 const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), { 7733 // Includes things we don't want other locales to inherit, 7734 // things that derive from other translatable strings. 7735 buttonHints: { 7736 prev: 'Previous $0', 7737 next: 'Next $0', 7738 today(buttonText, unit) { 7739 return (unit === 'day') 7740 ? 'Today' 7741 : `This ${buttonText}`; 7742 }, 7743 }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) { 7744 return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; 7745 } }); 7746 function organizeRawLocales(explicitRawLocales) { 7747 let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; 7748 let allRawLocales = globalLocales.concat(explicitRawLocales); 7749 let rawLocaleMap = { 7750 en: RAW_EN_LOCALE, 7751 }; 7752 for (let rawLocale of allRawLocales) { 7753 rawLocaleMap[rawLocale.code] = rawLocale; 7754 } 7755 return { 7756 map: rawLocaleMap, 7757 defaultCode, 7758 }; 7759 } 7760 function buildLocale(inputSingular, available) { 7761 if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { 7762 return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); 7763 } 7764 return queryLocale(inputSingular, available); 7765 } 7766 function queryLocale(codeArg, available) { 7767 let codes = [].concat(codeArg || []); // will convert to array 7768 let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; 7769 return parseLocale(codeArg, codes, raw); 7770 } 7771 function queryRawLocale(codes, available) { 7772 for (let i = 0; i < codes.length; i += 1) { 7773 let parts = codes[i].toLocaleLowerCase().split('-'); 7774 for (let j = parts.length; j > 0; j -= 1) { 7775 let simpleId = parts.slice(0, j).join('-'); 7776 if (available[simpleId]) { 7777 return available[simpleId]; 7778 } 7779 } 7780 } 7781 return null; 7782 } 7783 function parseLocale(codeArg, codes, raw) { 7784 let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']); 7785 delete merged.code; // don't want this part of the options 7786 let { week } = merged; 7787 delete merged.week; 7788 return { 7789 codeArg, 7790 codes, 7791 week, 7792 simpleNumberFormat: new Intl.NumberFormat(codeArg), 7793 options: merged, 7794 }; 7795 } 7796 7797 // TODO: easier way to add new hooks? need to update a million things 7798 function createPlugin(input) { 7799 return { 7800 id: guid(), 7801 name: input.name, 7802 premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined, 7803 deps: input.deps || [], 7804 reducers: input.reducers || [], 7805 isLoadingFuncs: input.isLoadingFuncs || [], 7806 contextInit: [].concat(input.contextInit || []), 7807 eventRefiners: input.eventRefiners || {}, 7808 eventDefMemberAdders: input.eventDefMemberAdders || [], 7809 eventSourceRefiners: input.eventSourceRefiners || {}, 7810 isDraggableTransformers: input.isDraggableTransformers || [], 7811 eventDragMutationMassagers: input.eventDragMutationMassagers || [], 7812 eventDefMutationAppliers: input.eventDefMutationAppliers || [], 7813 dateSelectionTransformers: input.dateSelectionTransformers || [], 7814 datePointTransforms: input.datePointTransforms || [], 7815 dateSpanTransforms: input.dateSpanTransforms || [], 7816 views: input.views || {}, 7817 viewPropsTransformers: input.viewPropsTransformers || [], 7818 isPropsValid: input.isPropsValid || null, 7819 externalDefTransforms: input.externalDefTransforms || [], 7820 viewContainerAppends: input.viewContainerAppends || [], 7821 eventDropTransformers: input.eventDropTransformers || [], 7822 componentInteractions: input.componentInteractions || [], 7823 calendarInteractions: input.calendarInteractions || [],
vendor: 21,948 bytes, lines 7824-8298
7824 themeClasses: input.themeClasses || {}, 7825 eventSourceDefs: input.eventSourceDefs || [], 7826 cmdFormatter: input.cmdFormatter, 7827 recurringTypes: input.recurringTypes || [], 7828 namedTimeZonedImpl: input.namedTimeZonedImpl, 7829 initialView: input.initialView || '', 7830 elementDraggingImpl: input.elementDraggingImpl, 7831 optionChangeHandlers: input.optionChangeHandlers || {}, 7832 scrollGridImpl: input.scrollGridImpl || null, 7833 listenerRefiners: input.listenerRefiners || {}, 7834 optionRefiners: input.optionRefiners || {}, 7835 propSetHandlers: input.propSetHandlers || {}, 7836 }; 7837 } 7838 function buildPluginHooks(pluginDefs, globalDefs) { 7839 let currentPluginIds = {}; 7840 let hooks = { 7841 premiumReleaseDate: undefined, 7842 reducers: [], 7843 isLoadingFuncs: [], 7844 contextInit: [], 7845 eventRefiners: {}, 7846 eventDefMemberAdders: [], 7847 eventSourceRefiners: {}, 7848 isDraggableTransformers: [], 7849 eventDragMutationMassagers: [], 7850 eventDefMutationAppliers: [], 7851 dateSelectionTransformers: [], 7852 datePointTransforms: [], 7853 dateSpanTransforms: [], 7854 views: {}, 7855 viewPropsTransformers: [], 7856 isPropsValid: null, 7857 externalDefTransforms: [], 7858 viewContainerAppends: [], 7859 eventDropTransformers: [], 7860 componentInteractions: [], 7861 calendarInteractions: [], 7862 themeClasses: {}, 7863 eventSourceDefs: [], 7864 cmdFormatter: null, 7865 recurringTypes: [], 7866 namedTimeZonedImpl: null, 7867 initialView: '', 7868 elementDraggingImpl: null, 7869 optionChangeHandlers: {}, 7870 scrollGridImpl: null, 7871 listenerRefiners: {}, 7872 optionRefiners: {}, 7873 propSetHandlers: {}, 7874 }; 7875 function addDefs(defs) { 7876 for (let def of defs) { 7877 const pluginName = def.name; 7878 const currentId = currentPluginIds[pluginName]; 7879 if (currentId === undefined) { 7880 currentPluginIds[pluginName] = def.id; 7881 addDefs(def.deps); 7882 hooks = combineHooks(hooks, def); 7883 } 7884 else if (currentId !== def.id) { 7885 // different ID than the one already added 7886 console.warn(`Duplicate plugin '${pluginName}'`); 7887 } 7888 } 7889 } 7890 if (pluginDefs) { 7891 addDefs(pluginDefs); 7892 } 7893 addDefs(globalDefs); 7894 return hooks; 7895 } 7896 function buildBuildPluginHooks() { 7897 let currentOverrideDefs = []; 7898 let currentGlobalDefs = []; 7899 let currentHooks; 7900 return (overrideDefs, globalDefs) => { 7901 if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { 7902 currentHooks = buildPluginHooks(overrideDefs, globalDefs); 7903 } 7904 currentOverrideDefs = overrideDefs; 7905 currentGlobalDefs = globalDefs; 7906 return currentHooks; 7907 }; 7908 } 7909 function combineHooks(hooks0, hooks1) { 7910 return { 7911 premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate), 7912 reducers: hooks0.reducers.concat(hooks1.reducers), 7913 isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), 7914 contextInit: hooks0.contextInit.concat(hooks1.contextInit), 7915 eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners), 7916 eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), 7917 eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), 7918 isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), 7919 eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), 7920 eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), 7921 dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), 7922 datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), 7923 dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), 7924 views: Object.assign(Object.assign({}, hooks0.views), hooks1.views), 7925 viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), 7926 isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, 7927 externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), 7928 viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), 7929 eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), 7930 calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), 7931 componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), 7932 themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses), 7933 eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), 7934 cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, 7935 recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), 7936 namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, 7937 initialView: hooks0.initialView || hooks1.initialView, 7938 elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, 7939 optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), 7940 scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, 7941 listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), 7942 optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners), 7943 propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), 7944 }; 7945 } 7946 function compareOptionalDates(date0, date1) { 7947 if (date0 === undefined) { 7948 return date1; 7949 } 7950 if (date1 === undefined) { 7951 return date0; 7952 } 7953 return new Date(Math.max(date0.valueOf(), date1.valueOf())); 7954 } 7955 7956 class StandardTheme extends Theme { 7957 } 7958 StandardTheme.prototype.classes = { 7959 root: 'fc-theme-standard', 7960 tableCellShaded: 'fc-cell-shaded', 7961 buttonGroup: 'fc-button-group', 7962 button: 'fc-button fc-button-primary', 7963 buttonActive: 'fc-button-active', 7964 }; 7965 StandardTheme.prototype.baseIconClass = 'fc-icon'; 7966 StandardTheme.prototype.iconClasses = { 7967 close: 'fc-icon-x', 7968 prev: 'fc-icon-chevron-left', 7969 next: 'fc-icon-chevron-right', 7970 prevYear: 'fc-icon-chevrons-left', 7971 nextYear: 'fc-icon-chevrons-right', 7972 }; 7973 StandardTheme.prototype.rtlIconClasses = { 7974 prev: 'fc-icon-chevron-right', 7975 next: 'fc-icon-chevron-left', 7976 prevYear: 'fc-icon-chevrons-right', 7977 nextYear: 'fc-icon-chevrons-left', 7978 }; 7979 StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly 7980 StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; 7981 StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; 7982 7983 function compileViewDefs(defaultConfigs, overrideConfigs) { 7984 let hash = {}; 7985 let viewType; 7986 for (viewType in defaultConfigs) { 7987 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); 7988 } 7989 for (viewType in overrideConfigs) { 7990 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); 7991 } 7992 return hash; 7993 } 7994 function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { 7995 if (hash[viewType]) { 7996 return hash[viewType]; 7997 } 7998 let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); 7999 if (viewDef) { 8000 hash[viewType] = viewDef; 8001 } 8002 return viewDef; 8003 } 8004 function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { 8005 let defaultConfig = defaultConfigs[viewType]; 8006 let overrideConfig = overrideConfigs[viewType]; 8007 let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : 8008 ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); 8009 let theComponent = queryProp('component'); 8010 let superType = queryProp('superType'); 8011 let superDef = null; 8012 if (superType) { 8013 if (superType === viewType) { 8014 throw new Error('Can\'t have a custom view type that references itself'); 8015 } 8016 superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); 8017 } 8018 if (!theComponent && superDef) { 8019 theComponent = superDef.component; 8020 } 8021 if (!theComponent) { 8022 return null; // don't throw a warning, might be settings for a single-unit view 8023 } 8024 return { 8025 type: viewType, 8026 component: theComponent, 8027 defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), 8028 overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), 8029 }; 8030 } 8031 8032 function parseViewConfigs(inputs) { 8033 return mapHash(inputs, parseViewConfig); 8034 } 8035 function parseViewConfig(input) { 8036 let rawOptions = typeof input === 'function' ? 8037 { component: input } : 8038 input; 8039 let { component } = rawOptions; 8040 if (rawOptions.content) { 8041 // TODO: remove content/classNames/didMount/etc from options? 8042 component = createViewHookComponent(rawOptions); 8043 } 8044 else if (component && !(component.prototype instanceof BaseComponent)) { 8045 // WHY?: people were using `component` property for `content` 8046 // TODO: converge on one setting name 8047 component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component })); 8048 } 8049 return { 8050 superType: rawOptions.type, 8051 component: component, 8052 rawOptions, // includes type and component too :( 8053 }; 8054 } 8055 function createViewHookComponent(options) { 8056 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 })))); 8057 } 8058 8059 function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { 8060 let defaultConfigs = parseViewConfigs(defaultInputs); 8061 let overrideConfigs = parseViewConfigs(optionOverrides.views); 8062 let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); 8063 return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults)); 8064 } 8065 function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { 8066 let durationInput = viewDef.overrides.duration || 8067 viewDef.defaults.duration || 8068 dynamicOptionOverrides.duration || 8069 optionOverrides.duration; 8070 let duration = null; 8071 let durationUnit = ''; 8072 let singleUnit = ''; 8073 let singleUnitOverrides = {}; 8074 if (durationInput) { 8075 duration = createDurationCached(durationInput); 8076 if (duration) { // valid? 8077 let denom = greatestDurationDenominator(duration); 8078 durationUnit = denom.unit; 8079 if (denom.value === 1) { 8080 singleUnit = durationUnit; 8081 singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; 8082 } 8083 } 8084 } 8085 let queryButtonText = (optionsSubset) => { 8086 let buttonTextMap = optionsSubset.buttonText || {}; 8087 let buttonTextKey = viewDef.defaults.buttonTextKey; 8088 if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { 8089 return buttonTextMap[buttonTextKey]; 8090 } 8091 if (buttonTextMap[viewDef.type] != null) { 8092 return buttonTextMap[viewDef.type]; 8093 } 8094 if (buttonTextMap[singleUnit] != null) { 8095 return buttonTextMap[singleUnit]; 8096 } 8097 return null; 8098 }; 8099 let queryButtonTitle = (optionsSubset) => { 8100 let buttonHints = optionsSubset.buttonHints || {}; 8101 let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text 8102 if (buttonKey != null && buttonHints[buttonKey] != null) { 8103 return buttonHints[buttonKey]; 8104 } 8105 if (buttonHints[viewDef.type] != null) { 8106 return buttonHints[viewDef.type]; 8107 } 8108 if (buttonHints[singleUnit] != null) { 8109 return buttonHints[singleUnit]; 8110 } 8111 return null; 8112 }; 8113 return { 8114 type: viewDef.type, 8115 component: viewDef.component, 8116 duration, 8117 durationUnit, 8118 singleUnit, 8119 optionDefaults: viewDef.defaults, 8120 optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides), 8121 buttonTextOverride: queryButtonText(dynamicOptionOverrides) || 8122 queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence 8123 viewDef.overrides.buttonText, 8124 buttonTextDefault: queryButtonText(localeDefaults) || 8125 viewDef.defaults.buttonText || 8126 queryButtonText(BASE_OPTION_DEFAULTS) || 8127 viewDef.type, 8128 // not DRY 8129 buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) || 8130 queryButtonTitle(optionOverrides) || 8131 viewDef.overrides.buttonHint, 8132 buttonTitleDefault: queryButtonTitle(localeDefaults) || 8133 viewDef.defaults.buttonHint || 8134 queryButtonTitle(BASE_OPTION_DEFAULTS), 8135 // will eventually fall back to buttonText 8136 }; 8137 } 8138 // hack to get memoization working 8139 let durationInputMap = {}; 8140 function createDurationCached(durationInput) { 8141 let json = JSON.stringify(durationInput); 8142 let res = durationInputMap[json]; 8143 if (res === undefined) { 8144 res = createDuration(durationInput); 8145 durationInputMap[json] = res; 8146 } 8147 return res; 8148 } 8149 8150 function reduceViewType(viewType, action) { 8151 switch (action.type) { 8152 case 'CHANGE_VIEW_TYPE': 8153 viewType = action.viewType; 8154 } 8155 return viewType; 8156 } 8157 8158 function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { 8159 switch (action.type) { 8160 case 'SET_OPTION': 8161 return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue }); 8162 default: 8163 return dynamicOptionOverrides; 8164 } 8165 } 8166 8167 function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { 8168 let dp; 8169 switch (action.type) { 8170 case 'CHANGE_VIEW_TYPE': 8171 return dateProfileGenerator.build(action.dateMarker || currentDate); 8172 case 'CHANGE_DATE': 8173 return dateProfileGenerator.build(action.dateMarker); 8174 case 'PREV': 8175 dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); 8176 if (dp.isValid) { 8177 return dp; 8178 } 8179 break; 8180 case 'NEXT': 8181 dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); 8182 if (dp.isValid) { 8183 return dp; 8184 } 8185 break; 8186 } 8187 return currentDateProfile; 8188 } 8189 8190 function initEventSources(calendarOptions, dateProfile, context) { 8191 let activeRange = dateProfile ? dateProfile.activeRange : null; 8192 return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); 8193 } 8194 function reduceEventSources(eventSources, action, dateProfile, context) { 8195 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? 8196 switch (action.type) { 8197 case 'ADD_EVENT_SOURCES': // already parsed 8198 return addSources(eventSources, action.sources, activeRange, context); 8199 case 'REMOVE_EVENT_SOURCE': 8200 return removeSource(eventSources, action.sourceId); 8201 case 'PREV': // TODO: how do we track all actions that affect dateProfile :( 8202 case 'NEXT': 8203 case 'CHANGE_DATE': 8204 case 'CHANGE_VIEW_TYPE': 8205 if (dateProfile) { 8206 return fetchDirtySources(eventSources, activeRange, context); 8207 } 8208 return eventSources; 8209 case 'FETCH_EVENT_SOURCES': 8210 return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? 8211 arrayToHash(action.sourceIds) : 8212 excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); 8213 case 'RECEIVE_EVENTS': 8214 case 'RECEIVE_EVENT_ERROR': 8215 return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); 8216 case 'REMOVE_ALL_EVENT_SOURCES': 8217 return {}; 8218 default: 8219 return eventSources; 8220 } 8221 } 8222 function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { 8223 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? 8224 return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); 8225 } 8226 function computeEventSourcesLoading(eventSources) { 8227 for (let sourceId in eventSources) { 8228 if (eventSources[sourceId].isFetching) { 8229 return true; 8230 } 8231 } 8232 return false; 8233 } 8234 function addSources(eventSourceHash, sources, fetchRange, context) { 8235 let hash = {}; 8236 for (let source of sources) { 8237 hash[source.sourceId] = source; 8238 } 8239 if (fetchRange) { 8240 hash = fetchDirtySources(hash, fetchRange, context); 8241 } 8242 return Object.assign(Object.assign({}, eventSourceHash), hash); 8243 } 8244 function removeSource(eventSourceHash, sourceId) { 8245 return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId); 8246 } 8247 function fetchDirtySources(sourceHash, fetchRange, context) { 8248 return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context); 8249 } 8250 function isSourceDirty(eventSource, fetchRange, context) { 8251 if (!doesSourceNeedRange(eventSource, context)) { 8252 return !eventSource.latestFetchId; 8253 } 8254 return !context.options.lazyFetching || 8255 !eventSource.fetchRange || 8256 eventSource.isFetching || // always cancel outdated in-progress fetches 8257 fetchRange.start < eventSource.fetchRange.start || 8258 fetchRange.end > eventSource.fetchRange.end; 8259 } 8260 function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { 8261 let nextSources = {}; 8262 for (let sourceId in prevSources) { 8263 let source = prevSources[sourceId]; 8264 if (sourceIdHash[sourceId]) { 8265 nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); 8266 } 8267 else { 8268 nextSources[sourceId] = source; 8269 } 8270 } 8271 return nextSources; 8272 } 8273 function fetchSource(eventSource, fetchRange, isRefetch, context) { 8274 let { options, calendarApi } = context; 8275 let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; 8276 let fetchId = guid(); 8277 sourceDef.fetch({ 8278 eventSource, 8279 range: fetchRange, 8280 isRefetch, 8281 context, 8282 }, (res) => { 8283 let { rawEvents } = res; 8284 if (options.eventSourceSuccess) { 8285 rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents; 8286 } 8287 if (eventSource.success) { 8288 rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents; 8289 } 8290 context.dispatch({ 8291 type: 'RECEIVE_EVENTS', 8292 sourceId: eventSource.sourceId, 8293 fetchId, 8294 fetchRange, 8295 rawEvents, 8296 }); 8297 }, (error) => { 8298 let errorHandled = false;
vendor: 4,583 bytes, lines 8299-8418
8299 if (options.eventSourceFailure) { 8300 options.eventSourceFailure.call(calendarApi, error); 8301 errorHandled = true; 8302 } 8303 if (eventSource.failure) { 8304 eventSource.failure(error); 8305 errorHandled = true; 8306 } 8307 if (!errorHandled) { 8308 console.warn(error.message, error); 8309 } 8310 context.dispatch({ 8311 type: 'RECEIVE_EVENT_ERROR', 8312 sourceId: eventSource.sourceId, 8313 fetchId, 8314 fetchRange, 8315 error, 8316 }); 8317 }); 8318 return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); 8319 } 8320 function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { 8321 let eventSource = sourceHash[sourceId]; 8322 if (eventSource && // not already removed 8323 fetchId === eventSource.latestFetchId) { 8324 return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) }); 8325 } 8326 return sourceHash; 8327 } 8328 function excludeStaticSources(eventSources, context) { 8329 return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context)); 8330 } 8331 function parseInitialSources(rawOptions, context) { 8332 let refiners = buildEventSourceRefiners(context); 8333 let rawSources = [].concat(rawOptions.eventSources || []); 8334 let sources = []; // parsed 8335 if (rawOptions.initialEvents) { 8336 rawSources.unshift(rawOptions.initialEvents); 8337 } 8338 if (rawOptions.events) { 8339 rawSources.unshift(rawOptions.events); 8340 } 8341 for (let rawSource of rawSources) { 8342 let source = parseEventSource(rawSource, context, refiners); 8343 if (source) { 8344 sources.push(source); 8345 } 8346 } 8347 return sources; 8348 } 8349 function doesSourceNeedRange(eventSource, context) { 8350 let defs = context.pluginHooks.eventSourceDefs; 8351 return !defs[eventSource.sourceDefId].ignoreRange; 8352 } 8353 8354 function reduceDateSelection(currentSelection, action) { 8355 switch (action.type) { 8356 case 'UNSELECT_DATES': 8357 return null; 8358 case 'SELECT_DATES': 8359 return action.selection; 8360 default: 8361 return currentSelection; 8362 } 8363 } 8364 8365 function reduceSelectedEvent(currentInstanceId, action) { 8366 switch (action.type) { 8367 case 'UNSELECT_EVENT': 8368 return ''; 8369 case 'SELECT_EVENT': 8370 return action.eventInstanceId; 8371 default: 8372 return currentInstanceId; 8373 } 8374 } 8375 8376 function reduceEventDrag(currentDrag, action) { 8377 let newDrag; 8378 switch (action.type) { 8379 case 'UNSET_EVENT_DRAG': 8380 return null; 8381 case 'SET_EVENT_DRAG': 8382 newDrag = action.state; 8383 return { 8384 affectedEvents: newDrag.affectedEvents, 8385 mutatedEvents: newDrag.mutatedEvents, 8386 isEvent: newDrag.isEvent, 8387 }; 8388 default: 8389 return currentDrag; 8390 } 8391 } 8392 8393 function reduceEventResize(currentResize, action) { 8394 let newResize; 8395 switch (action.type) { 8396 case 'UNSET_EVENT_RESIZE': 8397 return null; 8398 case 'SET_EVENT_RESIZE': 8399 newResize = action.state; 8400 return { 8401 affectedEvents: newResize.affectedEvents, 8402 mutatedEvents: newResize.mutatedEvents, 8403 isEvent: newResize.isEvent, 8404 }; 8405 default: 8406 return currentResize; 8407 } 8408 } 8409 8410 function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { 8411 let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null; 8412 let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null; 8413 return { header, footer }; 8414 } 8415 function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { 8416 let sectionWidgets = {}; 8417 let viewsWithButtons = []; 8418 let hasTitle = false;
vendor: 60,628 bytes, lines 8419-9693
8419 for (let sectionName in sectionStrHash) { 8420 let sectionStr = sectionStrHash[sectionName]; 8421 let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi); 8422 sectionWidgets[sectionName] = sectionRes.widgets; 8423 viewsWithButtons.push(...sectionRes.viewsWithButtons); 8424 hasTitle = hasTitle || sectionRes.hasTitle; 8425 } 8426 return { sectionWidgets, viewsWithButtons, hasTitle }; 8427 } 8428 /* 8429 BAD: querying icons and text here. should be done at render time 8430 */ 8431 function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined 8432 calendarOptionOverrides, // overrides only!, unrefined :( 8433 theme, viewSpecs, calendarApi) { 8434 let isRtl = calendarOptions.direction === 'rtl'; 8435 let calendarCustomButtons = calendarOptions.customButtons || {}; 8436 let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; 8437 let calendarButtonText = calendarOptions.buttonText || {}; 8438 let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {}; 8439 let calendarButtonHints = calendarOptions.buttonHints || {}; 8440 let sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; 8441 let viewsWithButtons = []; 8442 let hasTitle = false; 8443 let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => { 8444 if (buttonName === 'title') { 8445 hasTitle = true; 8446 return { buttonName }; 8447 } 8448 let customButtonProps; 8449 let viewSpec; 8450 let buttonClick; 8451 let buttonIcon; // only one of these will be set 8452 let buttonText; // " 8453 let buttonHint; 8454 // ^ for the title="" attribute, for accessibility 8455 if ((customButtonProps = calendarCustomButtons[buttonName])) { 8456 buttonClick = (ev) => { 8457 if (customButtonProps.click) { 8458 customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? 8459 } 8460 }; 8461 (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || 8462 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8463 (buttonText = customButtonProps.text); 8464 buttonHint = customButtonProps.hint || customButtonProps.text; 8465 } 8466 else if ((viewSpec = viewSpecs[buttonName])) { 8467 viewsWithButtons.push(buttonName); 8468 buttonClick = () => { 8469 calendarApi.changeView(buttonName); 8470 }; 8471 (buttonText = viewSpec.buttonTextOverride) || 8472 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8473 (buttonText = viewSpec.buttonTextDefault); 8474 let textFallback = viewSpec.buttonTextOverride || 8475 viewSpec.buttonTextDefault; 8476 buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride || 8477 viewSpec.buttonTitleDefault || 8478 calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName 8479 textFallback); 8480 } 8481 else if (calendarApi[buttonName]) { // a calendarApi method 8482 buttonClick = () => { 8483 calendarApi[buttonName](); 8484 }; 8485 (buttonText = calendarButtonTextOverrides[buttonName]) || 8486 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8487 (buttonText = calendarButtonText[buttonName]); // everything else is considered default 8488 if (buttonName === 'prevYear' || buttonName === 'nextYear') { 8489 let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next'; 8490 buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] || 8491 calendarButtonHints[prevOrNext], [ 8492 calendarButtonText.year || 'year', 8493 'year', 8494 ], calendarButtonText[buttonName]); 8495 } 8496 else { 8497 buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] || 8498 calendarButtonHints[buttonName], [ 8499 calendarButtonText[navUnit] || navUnit, 8500 navUnit, 8501 ], calendarButtonText[buttonName]); 8502 } 8503 } 8504 return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint }; 8505 }))); 8506 return { widgets, viewsWithButtons, hasTitle }; 8507 } 8508 8509 // always represents the current view. otherwise, it'd need to change value every time date changes 8510 class ViewImpl { 8511 constructor(type, getCurrentData, dateEnv) { 8512 this.type = type; 8513 this.getCurrentData = getCurrentData; 8514 this.dateEnv = dateEnv; 8515 } 8516 get calendar() { 8517 return this.getCurrentData().calendarApi; 8518 } 8519 get title() { 8520 return this.getCurrentData().viewTitle; 8521 } 8522 get activeStart() { 8523 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); 8524 } 8525 get activeEnd() { 8526 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); 8527 } 8528 get currentStart() { 8529 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); 8530 } 8531 get currentEnd() { 8532 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); 8533 } 8534 getOption(name) { 8535 return this.getCurrentData().options[name]; // are the view-specific options 8536 } 8537 } 8538 8539 let eventSourceDef$2 = { 8540 ignoreRange: true, 8541 parseMeta(refined) { 8542 if (Array.isArray(refined.events)) { 8543 return refined.events; 8544 } 8545 return null; 8546 }, 8547 fetch(arg, successCallback) { 8548 successCallback({ 8549 rawEvents: arg.eventSource.meta, 8550 }); 8551 }, 8552 }; 8553 const arrayEventSourcePlugin = createPlugin({ 8554 name: 'array-event-source', 8555 eventSourceDefs: [eventSourceDef$2], 8556 }); 8557 8558 let eventSourceDef$1 = { 8559 parseMeta(refined) { 8560 if (typeof refined.events === 'function') { 8561 return refined.events; 8562 } 8563 return null; 8564 }, 8565 fetch(arg, successCallback, errorCallback) { 8566 const { dateEnv } = arg.context; 8567 const func = arg.eventSource.meta; 8568 unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback); 8569 }, 8570 }; 8571 const funcEventSourcePlugin = createPlugin({ 8572 name: 'func-event-source', 8573 eventSourceDefs: [eventSourceDef$1], 8574 }); 8575 8576 const JSON_FEED_EVENT_SOURCE_REFINERS = { 8577 method: String, 8578 extraParams: identity, 8579 startParam: String, 8580 endParam: String, 8581 timeZoneParam: String, 8582 }; 8583 8584 let eventSourceDef = { 8585 parseMeta(refined) { 8586 if (refined.url && (refined.format === 'json' || !refined.format)) { 8587 return { 8588 url: refined.url, 8589 format: 'json', 8590 method: (refined.method || 'GET').toUpperCase(), 8591 extraParams: refined.extraParams, 8592 startParam: refined.startParam, 8593 endParam: refined.endParam, 8594 timeZoneParam: refined.timeZoneParam, 8595 }; 8596 } 8597 return null; 8598 }, 8599 fetch(arg, successCallback, errorCallback) { 8600 const { meta } = arg.eventSource; 8601 const requestParams = buildRequestParams(meta, arg.range, arg.context); 8602 requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => { 8603 successCallback({ rawEvents, response }); 8604 }, errorCallback); 8605 }, 8606 }; 8607 const jsonFeedEventSourcePlugin = createPlugin({ 8608 name: 'json-event-source', 8609 eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, 8610 eventSourceDefs: [eventSourceDef], 8611 }); 8612 function buildRequestParams(meta, range, context) { 8613 let { dateEnv, options } = context; 8614 let startParam; 8615 let endParam; 8616 let timeZoneParam; 8617 let customRequestParams; 8618 let params = {}; 8619 startParam = meta.startParam; 8620 if (startParam == null) { 8621 startParam = options.startParam; 8622 } 8623 endParam = meta.endParam; 8624 if (endParam == null) { 8625 endParam = options.endParam; 8626 } 8627 timeZoneParam = meta.timeZoneParam; 8628 if (timeZoneParam == null) { 8629 timeZoneParam = options.timeZoneParam; 8630 } 8631 // retrieve any outbound GET/POST data from the options 8632 if (typeof meta.extraParams === 'function') { 8633 // supplied as a function that returns a key/value object 8634 customRequestParams = meta.extraParams(); 8635 } 8636 else { 8637 // probably supplied as a straight key/value object 8638 customRequestParams = meta.extraParams || {}; 8639 } 8640 Object.assign(params, customRequestParams); 8641 params[startParam] = dateEnv.formatIso(range.start); 8642 params[endParam] = dateEnv.formatIso(range.end); 8643 if (dateEnv.timeZone !== 'local') { 8644 params[timeZoneParam] = dateEnv.timeZone; 8645 } 8646 return params; 8647 } 8648 8649 const SIMPLE_RECURRING_REFINERS = { 8650 daysOfWeek: identity, 8651 startTime: createDuration, 8652 endTime: createDuration, 8653 duration: createDuration, 8654 startRecur: identity, 8655 endRecur: identity, 8656 }; 8657 8658 let recurring = { 8659 parse(refined, dateEnv) { 8660 if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { 8661 let recurringData = { 8662 daysOfWeek: refined.daysOfWeek || null, 8663 startTime: refined.startTime || null, 8664 endTime: refined.endTime || null, 8665 startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, 8666 endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, 8667 }; 8668 let duration; 8669 if (refined.duration) { 8670 duration = refined.duration; 8671 } 8672 if (!duration && refined.startTime && refined.endTime) { 8673 duration = subtractDurations(refined.endTime, refined.startTime); 8674 } 8675 return { 8676 allDayGuess: Boolean(!refined.startTime && !refined.endTime), 8677 duration, 8678 typeData: recurringData, // doesn't need endTime anymore but oh well 8679 }; 8680 } 8681 return null; 8682 }, 8683 expand(typeData, framingRange, dateEnv) { 8684 let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); 8685 if (clippedFramingRange) { 8686 return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); 8687 } 8688 return []; 8689 }, 8690 }; 8691 const simpleRecurringEventsPlugin = createPlugin({ 8692 name: 'simple-recurring-event', 8693 recurringTypes: [recurring], 8694 eventRefiners: SIMPLE_RECURRING_REFINERS, 8695 }); 8696 function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { 8697 let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; 8698 let dayMarker = startOfDay(framingRange.start); 8699 let endMarker = framingRange.end; 8700 let instanceStarts = []; 8701 while (dayMarker < endMarker) { 8702 let instanceStart; 8703 // if everyday, or this particular day-of-week 8704 if (!dowHash || dowHash[dayMarker.getUTCDay()]) { 8705 if (startTime) { 8706 instanceStart = dateEnv.add(dayMarker, startTime); 8707 } 8708 else { 8709 instanceStart = dayMarker; 8710 } 8711 instanceStarts.push(instanceStart); 8712 } 8713 dayMarker = addDays(dayMarker, 1); 8714 } 8715 return instanceStarts; 8716 } 8717 8718 const changeHandlerPlugin = createPlugin({ 8719 name: 'change-handler', 8720 optionChangeHandlers: { 8721 events(events, context) { 8722 handleEventSources([events], context); 8723 }, 8724 eventSources: handleEventSources, 8725 }, 8726 }); 8727 /* 8728 BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out 8729 */ 8730 function handleEventSources(inputs, context) { 8731 let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); 8732 if (unfoundSources.length === 1 && 8733 inputs.length === 1 && 8734 Array.isArray(unfoundSources[0]._raw) && 8735 Array.isArray(inputs[0])) { 8736 context.dispatch({ 8737 type: 'RESET_RAW_EVENTS', 8738 sourceId: unfoundSources[0].sourceId, 8739 rawEvents: inputs[0], 8740 }); 8741 return; 8742 } 8743 let newInputs = []; 8744 for (let input of inputs) { 8745 let inputFound = false; 8746 for (let i = 0; i < unfoundSources.length; i += 1) { 8747 if (unfoundSources[i]._raw === input) { 8748 unfoundSources.splice(i, 1); // delete 8749 inputFound = true; 8750 break; 8751 } 8752 } 8753 if (!inputFound) { 8754 newInputs.push(input); 8755 } 8756 } 8757 for (let unfoundSource of unfoundSources) { 8758 context.dispatch({ 8759 type: 'REMOVE_EVENT_SOURCE', 8760 sourceId: unfoundSource.sourceId, 8761 }); 8762 } 8763 for (let newInput of newInputs) { 8764 context.calendarApi.addEventSource(newInput); 8765 } 8766 } 8767 8768 function handleDateProfile(dateProfile, context) { 8769 context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); 8770 } 8771 8772 function handleEventStore(eventStore, context) { 8773 let { emitter } = context; 8774 if (emitter.hasHandlers('eventsSet')) { 8775 emitter.trigger('eventsSet', buildEventApis(eventStore, context)); 8776 } 8777 } 8778 8779 /* 8780 this array is exposed on the root namespace so that UMD plugins can add to it. 8781 see the rollup-bundles script. 8782 */ 8783 const globalPlugins = [ 8784 arrayEventSourcePlugin, 8785 funcEventSourcePlugin, 8786 jsonFeedEventSourcePlugin, 8787 simpleRecurringEventsPlugin, 8788 changeHandlerPlugin, 8789 createPlugin({ 8790 name: 'misc', 8791 isLoadingFuncs: [ 8792 (state) => computeEventSourcesLoading(state.eventSources), 8793 ], 8794 propSetHandlers: { 8795 dateProfile: handleDateProfile, 8796 eventStore: handleEventStore, 8797 }, 8798 }), 8799 ]; 8800 8801 class TaskRunner { 8802 constructor(runTaskOption, drainedOption) { 8803 this.runTaskOption = runTaskOption; 8804 this.drainedOption = drainedOption; 8805 this.queue = []; 8806 this.delayedRunner = new DelayedRunner(this.drain.bind(this)); 8807 } 8808 request(task, delay) { 8809 this.queue.push(task); 8810 this.delayedRunner.request(delay); 8811 } 8812 pause(scope) { 8813 this.delayedRunner.pause(scope); 8814 } 8815 resume(scope, force) { 8816 this.delayedRunner.resume(scope, force); 8817 } 8818 drain() { 8819 let { queue } = this; 8820 while (queue.length) { 8821 let completedTasks = []; 8822 let task; 8823 while ((task = queue.shift())) { 8824 this.runTask(task); 8825 completedTasks.push(task); 8826 } 8827 this.drained(completedTasks); 8828 } // keep going, in case new tasks were added in the drained handler 8829 } 8830 runTask(task) { 8831 if (this.runTaskOption) { 8832 this.runTaskOption(task); 8833 } 8834 } 8835 drained(completedTasks) { 8836 if (this.drainedOption) { 8837 this.drainedOption(completedTasks); 8838 } 8839 } 8840 } 8841 8842 // Computes what the title at the top of the calendarApi should be for this view 8843 function buildTitle(dateProfile, viewOptions, dateEnv) { 8844 let range; 8845 // for views that span a large unit of time, show the proper interval, ignoring stray days before and after 8846 if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { 8847 range = dateProfile.currentRange; 8848 } 8849 else { // for day units or smaller, use the actual day range 8850 range = dateProfile.activeRange; 8851 } 8852 return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { 8853 isEndExclusive: dateProfile.isRangeAllDay, 8854 defaultSeparator: viewOptions.titleRangeSeparator, 8855 }); 8856 } 8857 // Generates the format string that should be used to generate the title for the current date range. 8858 // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. 8859 function buildTitleFormat(dateProfile) { 8860 let { currentRangeUnit } = dateProfile; 8861 if (currentRangeUnit === 'year') { 8862 return { year: 'numeric' }; 8863 } 8864 if (currentRangeUnit === 'month') { 8865 return { year: 'numeric', month: 'long' }; // like "September 2014" 8866 } 8867 let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); 8868 if (days !== null && days > 1) { 8869 // multi-day range. shorter, like "Sep 9 - 10 2014" 8870 return { year: 'numeric', month: 'short', day: 'numeric' }; 8871 } 8872 // one day. longer, like "September 9 2014" 8873 return { year: 'numeric', month: 'long', day: 'numeric' }; 8874 } 8875 8876 // in future refactor, do the redux-style function(state=initial) for initial-state 8877 // also, whatever is happening in constructor, have it happen in action queue too 8878 class CalendarDataManager { 8879 constructor(props) { 8880 this.computeCurrentViewData = memoize(this._computeCurrentViewData); 8881 this.organizeRawLocales = memoize(organizeRawLocales); 8882 this.buildLocale = memoize(buildLocale); 8883 this.buildPluginHooks = buildBuildPluginHooks(); 8884 this.buildDateEnv = memoize(buildDateEnv$1); 8885 this.buildTheme = memoize(buildTheme); 8886 this.parseToolbars = memoize(parseToolbars); 8887 this.buildViewSpecs = memoize(buildViewSpecs); 8888 this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); 8889 this.buildViewApi = memoize(buildViewApi); 8890 this.buildViewUiProps = memoizeObjArg(buildViewUiProps); 8891 this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); 8892 this.buildEventUiBases = memoize(buildEventUiBases); 8893 this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); 8894 this.buildTitle = memoize(buildTitle); 8895 this.emitter = new Emitter(); 8896 this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); 8897 this.currentCalendarOptionsInput = {}; 8898 this.currentCalendarOptionsRefined = {}; 8899 this.currentViewOptionsInput = {}; 8900 this.currentViewOptionsRefined = {}; 8901 this.currentCalendarOptionsRefiners = {}; 8902 this.optionsForRefining = []; 8903 this.optionsForHandling = []; 8904 this.getCurrentData = () => this.data; 8905 this.dispatch = (action) => { 8906 this.actionRunner.request(action); // protects against recursive calls to _handleAction 8907 }; 8908 this.props = props; 8909 this.actionRunner.pause(); 8910 let dynamicOptionOverrides = {}; 8911 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); 8912 let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; 8913 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); 8914 // wire things up 8915 // TODO: not DRY 8916 props.calendarApi.currentDataManager = this; 8917 this.emitter.setThisContext(props.calendarApi); 8918 this.emitter.setOptions(currentViewData.options); 8919 let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); 8920 let dateProfile = currentViewData.dateProfileGenerator.build(currentDate); 8921 if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { 8922 currentDate = dateProfile.currentRange.start; 8923 } 8924 let calendarContext = { 8925 dateEnv: optionsData.dateEnv, 8926 options: optionsData.calendarOptions, 8927 pluginHooks: optionsData.pluginHooks, 8928 calendarApi: props.calendarApi, 8929 dispatch: this.dispatch, 8930 emitter: this.emitter, 8931 getCurrentData: this.getCurrentData, 8932 }; 8933 // needs to be after setThisContext 8934 for (let callback of optionsData.pluginHooks.contextInit) { 8935 callback(calendarContext); 8936 } 8937 // NOT DRY 8938 let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); 8939 let initialState = { 8940 dynamicOptionOverrides, 8941 currentViewType, 8942 currentDate, 8943 dateProfile, 8944 businessHours: this.parseContextBusinessHours(calendarContext), 8945 eventSources, 8946 eventUiBases: {}, 8947 eventStore: createEmptyEventStore(), 8948 renderableEventStore: createEmptyEventStore(), 8949 dateSelection: null, 8950 eventSelection: '', 8951 eventDrag: null, 8952 eventResize: null, 8953 selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, 8954 }; 8955 let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState); 8956 for (let reducer of optionsData.pluginHooks.reducers) { 8957 Object.assign(initialState, reducer(null, null, contextAndState)); 8958 } 8959 if (computeIsLoading(initialState, calendarContext)) { 8960 this.emitter.trigger('loading', true); // NOT DRY 8961 } 8962 this.state = initialState; 8963 this.updateData(); 8964 this.actionRunner.resume(); 8965 } 8966 resetOptions(optionOverrides, changedOptionNames) { 8967 let { props } = this; 8968 if (changedOptionNames === undefined) { 8969 props.optionOverrides = optionOverrides; 8970 } 8971 else { 8972 props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides); 8973 this.optionsForRefining.push(...changedOptionNames); 8974 } 8975 if (changedOptionNames === undefined || changedOptionNames.length) { 8976 this.actionRunner.request({ 8977 type: 'NOTHING', 8978 }); 8979 } 8980 } 8981 _handleAction(action) { 8982 let { props, state, emitter } = this; 8983 let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); 8984 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); 8985 let currentViewType = reduceViewType(state.currentViewType, action); 8986 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); 8987 // wire things up 8988 // TODO: not DRY 8989 props.calendarApi.currentDataManager = this; 8990 emitter.setThisContext(props.calendarApi); 8991 emitter.setOptions(currentViewData.options); 8992 let calendarContext = { 8993 dateEnv: optionsData.dateEnv, 8994 options: optionsData.calendarOptions, 8995 pluginHooks: optionsData.pluginHooks, 8996 calendarApi: props.calendarApi, 8997 dispatch: this.dispatch, 8998 emitter, 8999 getCurrentData: this.getCurrentData, 9000 }; 9001 let { currentDate, dateProfile } = state; 9002 if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack 9003 dateProfile = currentViewData.dateProfileGenerator.build(currentDate); 9004 } 9005 currentDate = reduceCurrentDate(currentDate, action); 9006 dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); 9007 if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator 9008 action.type === 'NEXT' || // " 9009 !rangeContainsMarker(dateProfile.currentRange, currentDate)) { 9010 currentDate = dateProfile.currentRange.start; 9011 } 9012 let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); 9013 let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); 9014 let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading 9015 let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? 9016 (state.renderableEventStore || eventStore) : // try from previous state 9017 eventStore; 9018 let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj 9019 let eventUiBySource = this.buildEventUiBySource(eventSources); 9020 let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); 9021 let newState = { 9022 dynamicOptionOverrides, 9023 currentViewType, 9024 currentDate, 9025 dateProfile, 9026 eventSources, 9027 eventStore, 9028 renderableEventStore, 9029 selectionConfig, 9030 eventUiBases, 9031 businessHours: this.parseContextBusinessHours(calendarContext), 9032 dateSelection: reduceDateSelection(state.dateSelection, action), 9033 eventSelection: reduceSelectedEvent(state.eventSelection, action), 9034 eventDrag: reduceEventDrag(state.eventDrag, action), 9035 eventResize: reduceEventResize(state.eventResize, action), 9036 }; 9037 let contextAndState = Object.assign(Object.assign({}, calendarContext), newState); 9038 for (let reducer of optionsData.pluginHooks.reducers) { 9039 Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value 9040 } 9041 let wasLoading = computeIsLoading(state, calendarContext); 9042 let isLoading = computeIsLoading(newState, calendarContext); 9043 // TODO: use propSetHandlers in plugin system 9044 if (!wasLoading && isLoading) { 9045 emitter.trigger('loading', true); 9046 } 9047 else if (wasLoading && !isLoading) { 9048 emitter.trigger('loading', false); 9049 } 9050 this.state = newState; 9051 if (props.onAction) { 9052 props.onAction(action); 9053 } 9054 } 9055 updateData() { 9056 let { props, state } = this; 9057 let oldData = this.data; 9058 let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); 9059 let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); 9060 let data = this.data = Object.assign(Object.assign(Object.assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); 9061 let changeHandlers = optionsData.pluginHooks.optionChangeHandlers; 9062 let oldCalendarOptions = oldData && oldData.calendarOptions; 9063 let newCalendarOptions = optionsData.calendarOptions; 9064 if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { 9065 if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { 9066 // hack 9067 state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); 9068 state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); 9069 state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv); 9070 } 9071 for (let optionName in changeHandlers) { 9072 if (this.optionsForHandling.indexOf(optionName) !== -1 || 9073 oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { 9074 changeHandlers[optionName](newCalendarOptions[optionName], data); 9075 } 9076 } 9077 } 9078 this.optionsForHandling = []; 9079 if (props.onData) { 9080 props.onData(data); 9081 } 9082 } 9083 computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) { 9084 // TODO: blacklist options that are handled by optionChangeHandlers 9085 if (!this.optionsForRefining.length && 9086 optionOverrides === this.stableOptionOverrides && 9087 dynamicOptionOverrides === this.stableDynamicOptionOverrides) { 9088 return this.stableCalendarOptionsData; 9089 } 9090 let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides); 9091 warnUnknownOptions(extra); 9092 let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); 9093 let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults); 9094 let theme = this.buildTheme(refinedOptions, pluginHooks); 9095 let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi); 9096 return this.stableCalendarOptionsData = { 9097 calendarOptions: refinedOptions, 9098 pluginHooks, 9099 dateEnv, 9100 viewSpecs, 9101 theme, 9102 toolbarConfig, 9103 localeDefaults, 9104 availableRawLocales: availableLocaleData.map, 9105 }; 9106 } 9107 // always called from behind a memoizer 9108 processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) { 9109 let { locales, locale } = mergeRawOptions([ 9110 BASE_OPTION_DEFAULTS, 9111 optionOverrides, 9112 dynamicOptionOverrides, 9113 ]); 9114 let availableLocaleData = this.organizeRawLocales(locales); 9115 let availableRawLocales = availableLocaleData.map; 9116 let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; 9117 let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); 9118 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); 9119 let extra = {}; 9120 let raw = mergeRawOptions([ 9121 BASE_OPTION_DEFAULTS, 9122 localeDefaults, 9123 optionOverrides, 9124 dynamicOptionOverrides, 9125 ]); 9126 let refined = {}; 9127 let currentRaw = this.currentCalendarOptionsInput; 9128 let currentRefined = this.currentCalendarOptionsRefined; 9129 let anyChanges = false; 9130 for (let optionName in raw) { 9131 if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] && 9132 (optionName in currentRaw) && 9133 COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) { 9134 refined[optionName] = currentRefined[optionName]; 9135 } 9136 else if (refiners[optionName]) { 9137 refined[optionName] = refiners[optionName](raw[optionName]); 9138 anyChanges = true; 9139 } 9140 else { 9141 extra[optionName] = currentRaw[optionName]; 9142 } 9143 } 9144 if (anyChanges) { 9145 this.currentCalendarOptionsInput = raw; 9146 this.currentCalendarOptionsRefined = refined; 9147 this.stableOptionOverrides = optionOverrides; 9148 this.stableDynamicOptionOverrides = dynamicOptionOverrides; 9149 } 9150 this.optionsForHandling.push(...this.optionsForRefining); 9151 this.optionsForRefining = []; 9152 return { 9153 rawOptions: this.currentCalendarOptionsInput, 9154 refinedOptions: this.currentCalendarOptionsRefined, 9155 pluginHooks, 9156 availableLocaleData, 9157 localeDefaults, 9158 extra, 9159 }; 9160 } 9161 _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) { 9162 let viewSpec = optionsData.viewSpecs[viewType]; 9163 if (!viewSpec) { 9164 throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`); 9165 } 9166 let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides); 9167 warnUnknownOptions(extra); 9168 let dateProfileGenerator = this.buildDateProfileGenerator({ 9169 dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, 9170 duration: viewSpec.duration, 9171 durationUnit: viewSpec.durationUnit, 9172 usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, 9173 dateEnv: optionsData.dateEnv, 9174 calendarApi: this.props.calendarApi, 9175 slotMinTime: refinedOptions.slotMinTime, 9176 slotMaxTime: refinedOptions.slotMaxTime, 9177 showNonCurrentDates: refinedOptions.showNonCurrentDates, 9178 dayCount: refinedOptions.dayCount, 9179 dateAlignment: refinedOptions.dateAlignment, 9180 dateIncrement: refinedOptions.dateIncrement, 9181 hiddenDays: refinedOptions.hiddenDays, 9182 weekends: refinedOptions.weekends, 9183 nowInput: refinedOptions.now, 9184 validRangeInput: refinedOptions.validRange, 9185 visibleRangeInput: refinedOptions.visibleRange, 9186 fixedWeekCount: refinedOptions.fixedWeekCount, 9187 }); 9188 let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); 9189 return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi }; 9190 } 9191 processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { 9192 let raw = mergeRawOptions([ 9193 BASE_OPTION_DEFAULTS, 9194 viewSpec.optionDefaults, 9195 localeDefaults, 9196 optionOverrides, 9197 viewSpec.optionOverrides, 9198 dynamicOptionOverrides, 9199 ]); 9200 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); 9201 let refined = {}; 9202 let currentRaw = this.currentViewOptionsInput; 9203 let currentRefined = this.currentViewOptionsRefined; 9204 let anyChanges = false; 9205 let extra = {}; 9206 for (let optionName in raw) { 9207 if (raw[optionName] === currentRaw[optionName] || 9208 (COMPLEX_OPTION_COMPARATORS[optionName] && 9209 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) { 9210 refined[optionName] = currentRefined[optionName]; 9211 } 9212 else { 9213 if (raw[optionName] === this.currentCalendarOptionsInput[optionName] || 9214 (COMPLEX_OPTION_COMPARATORS[optionName] && 9215 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) { 9216 if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop 9217 refined[optionName] = this.currentCalendarOptionsRefined[optionName]; 9218 } 9219 } 9220 else if (refiners[optionName]) { 9221 refined[optionName] = refiners[optionName](raw[optionName]); 9222 } 9223 else { 9224 extra[optionName] = raw[optionName]; 9225 } 9226 anyChanges = true; 9227 } 9228 } 9229 if (anyChanges) { 9230 this.currentViewOptionsInput = raw; 9231 this.currentViewOptionsRefined = refined; 9232 } 9233 return { 9234 rawOptions: this.currentViewOptionsInput, 9235 refinedOptions: this.currentViewOptionsRefined, 9236 extra, 9237 }; 9238 } 9239 } 9240 function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { 9241 let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); 9242 return new DateEnv({ 9243 calendarSystem: 'gregory', 9244 timeZone, 9245 namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, 9246 locale, 9247 weekNumberCalculation, 9248 firstDay, 9249 weekText, 9250 cmdFormatter: pluginHooks.cmdFormatter, 9251 defaultSeparator, 9252 }); 9253 } 9254 function buildTheme(options, pluginHooks) { 9255 let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; 9256 return new ThemeClass(options); 9257 } 9258 function buildDateProfileGenerator(props) { 9259 let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; 9260 return new DateProfileGeneratorClass(props); 9261 } 9262 function buildViewApi(type, getCurrentData, dateEnv) { 9263 return new ViewImpl(type, getCurrentData, dateEnv); 9264 } 9265 function buildEventUiBySource(eventSources) { 9266 return mapHash(eventSources, (eventSource) => eventSource.ui); 9267 } 9268 function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { 9269 let eventUiBases = { '': eventUiSingleBase }; 9270 for (let defId in eventDefs) { 9271 let def = eventDefs[defId]; 9272 if (def.sourceId && eventUiBySource[def.sourceId]) { 9273 eventUiBases[defId] = eventUiBySource[def.sourceId]; 9274 } 9275 } 9276 return eventUiBases; 9277 } 9278 function buildViewUiProps(calendarContext) { 9279 let { options } = calendarContext; 9280 return { 9281 eventUiSingleBase: createEventUi({ 9282 display: options.eventDisplay, 9283 editable: options.editable, 9284 startEditable: options.eventStartEditable, 9285 durationEditable: options.eventDurationEditable, 9286 constraint: options.eventConstraint, 9287 overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, 9288 allow: options.eventAllow, 9289 backgroundColor: options.eventBackgroundColor, 9290 borderColor: options.eventBorderColor, 9291 textColor: options.eventTextColor, 9292 color: options.eventColor, 9293 // classNames: options.eventClassNames // render hook will handle this 9294 }, calendarContext), 9295 selectionConfig: createEventUi({ 9296 constraint: options.selectConstraint, 9297 overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, 9298 allow: options.selectAllow, 9299 }, calendarContext), 9300 }; 9301 } 9302 function computeIsLoading(state, context) { 9303 for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) { 9304 if (isLoadingFunc(state)) { 9305 return true; 9306 } 9307 } 9308 return false; 9309 } 9310 function parseContextBusinessHours(calendarContext) { 9311 return parseBusinessHours(calendarContext.options.businessHours, calendarContext); 9312 } 9313 function warnUnknownOptions(options, viewName) { 9314 for (let optionName in options) { 9315 console.warn(`Unknown option '${optionName}'` + 9316 (viewName ? ` for view '${viewName}'` : '')); 9317 } 9318 } 9319 9320 class ToolbarSection extends BaseComponent { 9321 render() { 9322 let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup)); 9323 return y('div', { className: 'fc-toolbar-chunk' }, ...children); 9324 } 9325 renderWidgetGroup(widgetGroup) { 9326 let { props } = this; 9327 let { theme } = this.context; 9328 let children = []; 9329 let isOnlyButtons = true; 9330 for (let widget of widgetGroup) { 9331 let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget; 9332 if (buttonName === 'title') { 9333 isOnlyButtons = false; 9334 children.push(y("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title)); 9335 } 9336 else { 9337 let isPressed = buttonName === props.activeButton; 9338 let isDisabled = (!props.isTodayEnabled && buttonName === 'today') || 9339 (!props.isPrevEnabled && buttonName === 'prev') || 9340 (!props.isNextEnabled && buttonName === 'next'); 9341 let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')]; 9342 if (isPressed) { 9343 buttonClasses.push(theme.getClass('buttonActive')); 9344 } 9345 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" }) : ''))); 9346 } 9347 } 9348 if (children.length > 1) { 9349 let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; 9350 return y('div', { className: groupClassName }, ...children); 9351 } 9352 return children[0]; 9353 } 9354 } 9355 9356 class Toolbar extends BaseComponent { 9357 render() { 9358 let { model, extraClassName } = this.props; 9359 let forceLtr = false; 9360 let startContent; 9361 let endContent; 9362 let sectionWidgets = model.sectionWidgets; 9363 let centerContent = sectionWidgets.center; 9364 if (sectionWidgets.left) { 9365 forceLtr = true; 9366 startContent = sectionWidgets.left; 9367 } 9368 else { 9369 startContent = sectionWidgets.start; 9370 } 9371 if (sectionWidgets.right) { 9372 forceLtr = true; 9373 endContent = sectionWidgets.right; 9374 } 9375 else { 9376 endContent = sectionWidgets.end; 9377 } 9378 let classNames = [ 9379 extraClassName || '', 9380 'fc-toolbar', 9381 forceLtr ? 'fc-toolbar-ltr' : '', 9382 ]; 9383 return (y("div", { className: classNames.join(' ') }, 9384 this.renderSection('start', startContent || []), 9385 this.renderSection('center', centerContent || []), 9386 this.renderSection('end', endContent || []))); 9387 } 9388 renderSection(key, widgetGroups) { 9389 let { props } = this; 9390 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 })); 9391 } 9392 } 9393 9394 class ViewHarness extends BaseComponent { 9395 constructor() { 9396 super(...arguments); 9397 this.state = { 9398 availableWidth: null, 9399 }; 9400 this.handleEl = (el) => { 9401 this.el = el; 9402 setRef(this.props.elRef, el); 9403 this.updateAvailableWidth(); 9404 }; 9405 this.handleResize = () => { 9406 this.updateAvailableWidth(); 9407 }; 9408 } 9409 render() { 9410 let { props, state } = this; 9411 let { aspectRatio } = props; 9412 let classNames = [ 9413 'fc-view-harness', 9414 (aspectRatio || props.liquid || props.height) 9415 ? 'fc-view-harness-active' // harness controls the height 9416 : 'fc-view-harness-passive', // let the view do the height 9417 ]; 9418 let height = ''; 9419 let paddingBottom = ''; 9420 if (aspectRatio) { 9421 if (state.availableWidth !== null) { 9422 height = state.availableWidth / aspectRatio; 9423 } 9424 else { 9425 // while waiting to know availableWidth, we can't set height to *zero* 9426 // because will cause lots of unnecessary scrollbars within scrollgrid. 9427 // BETTER: don't start rendering ANYTHING yet until we know container width 9428 // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) 9429 paddingBottom = `${(1 / aspectRatio) * 100}%`; 9430 } 9431 } 9432 else { 9433 height = props.height || ''; 9434 } 9435 return (y("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children)); 9436 } 9437 componentDidMount() { 9438 this.context.addResizeHandler(this.handleResize); 9439 } 9440 componentWillUnmount() { 9441 this.context.removeResizeHandler(this.handleResize); 9442 } 9443 updateAvailableWidth() { 9444 if (this.el && // needed. but why? 9445 this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth 9446 ) { 9447 this.setState({ availableWidth: this.el.offsetWidth }); 9448 } 9449 } 9450 } 9451 9452 /* 9453 Detects when the user clicks on an event within a DateComponent 9454 */ 9455 class EventClicking extends Interaction { 9456 constructor(settings) { 9457 super(settings); 9458 this.handleSegClick = (ev, segEl) => { 9459 let { component } = this; 9460 let { context } = component; 9461 let seg = getElSeg(segEl); 9462 if (seg && // might be the <div> surrounding the more link 9463 component.isValidSegDownEl(ev.target)) { 9464 // our way to simulate a link click for elements that can't be <a> tags 9465 // grab before trigger fired in case trigger trashes DOM thru rerendering 9466 let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); 9467 let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; 9468 context.emitter.trigger('eventClick', { 9469 el: segEl, 9470 event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance), 9471 jsEvent: ev, 9472 view: context.viewApi, 9473 }); 9474 if (url && !ev.defaultPrevented) { 9475 window.location.href = url; 9476 } 9477 } 9478 }; 9479 this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events 9480 this.handleSegClick); 9481 } 9482 } 9483 9484 /* 9485 Triggers events and adds/removes core classNames when the user's pointer 9486 enters/leaves event-elements of a component. 9487 */ 9488 class EventHovering extends Interaction { 9489 constructor(settings) { 9490 super(settings); 9491 // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it 9492 this.handleEventElRemove = (el) => { 9493 if (el === this.currentSegEl) { 9494 this.handleSegLeave(null, this.currentSegEl); 9495 } 9496 }; 9497 this.handleSegEnter = (ev, segEl) => { 9498 if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper 9499 this.currentSegEl = segEl; 9500 this.triggerEvent('eventMouseEnter', ev, segEl); 9501 } 9502 }; 9503 this.handleSegLeave = (ev, segEl) => { 9504 if (this.currentSegEl) { 9505 this.currentSegEl = null; 9506 this.triggerEvent('eventMouseLeave', ev, segEl); 9507 } 9508 }; 9509 this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events 9510 this.handleSegEnter, this.handleSegLeave); 9511 } 9512 destroy() { 9513 this.removeHoverListeners(); 9514 } 9515 triggerEvent(publicEvName, ev, segEl) { 9516 let { component } = this; 9517 let { context } = component; 9518 let seg = getElSeg(segEl); 9519 if (!ev || component.isValidSegDownEl(ev.target)) { 9520 context.emitter.trigger(publicEvName, { 9521 el: segEl, 9522 event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance), 9523 jsEvent: ev, 9524 view: context.viewApi, 9525 }); 9526 } 9527 } 9528 } 9529 9530 class CalendarContent extends PureComponent { 9531 constructor() { 9532 super(...arguments); 9533 this.buildViewContext = memoize(buildViewContext); 9534 this.buildViewPropTransformers = memoize(buildViewPropTransformers); 9535 this.buildToolbarProps = memoize(buildToolbarProps); 9536 this.headerRef = d(); 9537 this.footerRef = d(); 9538 this.interactionsStore = {}; 9539 // eslint-disable-next-line 9540 this.state = { 9541 viewLabelId: getUniqueDomId(), 9542 }; 9543 // Component Registration 9544 // ----------------------------------------------------------------------------------------------------------------- 9545 this.registerInteractiveComponent = (component, settingsInput) => { 9546 let settings = parseInteractionSettings(component, settingsInput); 9547 let DEFAULT_INTERACTIONS = [ 9548 EventClicking, 9549 EventHovering, 9550 ]; 9551 let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions); 9552 let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings)); 9553 this.interactionsStore[component.uid] = interactions; 9554 interactionSettingsStore[component.uid] = settings; 9555 }; 9556 this.unregisterInteractiveComponent = (component) => { 9557 let listeners = this.interactionsStore[component.uid]; 9558 if (listeners) { 9559 for (let listener of listeners) { 9560 listener.destroy(); 9561 } 9562 delete this.interactionsStore[component.uid]; 9563 } 9564 delete interactionSettingsStore[component.uid]; 9565 }; 9566 // Resizing 9567 // ----------------------------------------------------------------------------------------------------------------- 9568 this.resizeRunner = new DelayedRunner(() => { 9569 this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? 9570 this.props.emitter.trigger('windowResize', { view: this.props.viewApi }); 9571 }); 9572 this.handleWindowResize = (ev) => { 9573 let { options } = this.props; 9574 if (options.handleWindowResize && 9575 ev.target === window // avoid jqui events 9576 ) { 9577 this.resizeRunner.request(options.windowResizeDelay); 9578 } 9579 }; 9580 } 9581 /* 9582 renders INSIDE of an outer div 9583 */ 9584 render() { 9585 let { props } = this; 9586 let { toolbarConfig, options } = props; 9587 let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? 9588 props.viewTitle); 9589 let viewVGrow = false; 9590 let viewHeight = ''; 9591 let viewAspectRatio; 9592 if (props.isHeightAuto || props.forPrint) { 9593 viewHeight = ''; 9594 } 9595 else if (options.height != null) { 9596 viewVGrow = true; 9597 } 9598 else if (options.contentHeight != null) { 9599 viewHeight = options.contentHeight; 9600 } 9601 else { 9602 viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall 9603 } 9604 let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); 9605 let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle) 9606 ? this.state.viewLabelId 9607 : ''; 9608 return (y(ViewContextType.Provider, { value: viewContext }, 9609 toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))), 9610 y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId }, 9611 this.renderView(props), 9612 this.buildAppendContent()), 9613 toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps))))); 9614 } 9615 componentDidMount() { 9616 let { props } = this; 9617 this.calendarInteractions = props.pluginHooks.calendarInteractions 9618 .map((CalendarInteractionClass) => new CalendarInteractionClass(props)); 9619 window.addEventListener('resize', this.handleWindowResize); 9620 let { propSetHandlers } = props.pluginHooks; 9621 for (let propName in propSetHandlers) { 9622 propSetHandlers[propName](props[propName], props); 9623 } 9624 } 9625 componentDidUpdate(prevProps) { 9626 let { props } = this; 9627 let { propSetHandlers } = props.pluginHooks; 9628 for (let propName in propSetHandlers) { 9629 if (props[propName] !== prevProps[propName]) { 9630 propSetHandlers[propName](props[propName], props); 9631 } 9632 } 9633 } 9634 componentWillUnmount() { 9635 window.removeEventListener('resize', this.handleWindowResize); 9636 this.resizeRunner.clear(); 9637 for (let interaction of this.calendarInteractions) { 9638 interaction.destroy(); 9639 } 9640 this.props.emitter.trigger('_unmount'); 9641 } 9642 buildAppendContent() { 9643 let { props } = this; 9644 let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props)); 9645 return y(_, {}, ...children); 9646 } 9647 renderView(props) { 9648 let { pluginHooks } = props; 9649 let { viewSpec } = props; 9650 let viewProps = { 9651 dateProfile: props.dateProfile, 9652 businessHours: props.businessHours, 9653 eventStore: props.renderableEventStore, 9654 eventUiBases: props.eventUiBases, 9655 dateSelection: props.dateSelection, 9656 eventSelection: props.eventSelection, 9657 eventDrag: props.eventDrag, 9658 eventResize: props.eventResize, 9659 isHeightAuto: props.isHeightAuto, 9660 forPrint: props.forPrint, 9661 }; 9662 let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); 9663 for (let transformer of transformers) { 9664 Object.assign(viewProps, transformer.transform(viewProps, props)); 9665 } 9666 let ViewComponent = viewSpec.component; 9667 return (y(ViewComponent, Object.assign({}, viewProps))); 9668 } 9669 } 9670 function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { 9671 // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid 9672 let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason 9673 let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); 9674 let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); 9675 return { 9676 title, 9677 activeButton: viewSpec.type, 9678 navUnit: viewSpec.singleUnit, 9679 isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), 9680 isPrevEnabled: prevInfo.isValid, 9681 isNextEnabled: nextInfo.isValid, 9682 }; 9683 } 9684 // Plugin 9685 // ----------------------------------------------------------------------------------------------------------------- 9686 function buildViewPropTransformers(theClasses) { 9687 return theClasses.map((TheClass) => new TheClass()); 9688 } 9689 9690 class Calendar extends CalendarImpl { 9691 constructor(el, optionOverrides = {}) { 9692 super(); 9693 this.isRendering = false;
9694 this.isRendered = false; 9695 this.currentClassNames = []; 9696 this.customContentRenderId = 0; 9697 this.handleAction = (action) => { 9698 // actions we know we want to render immediately 9699 switch (action.type) { 9700 case 'SET_EVENT_DRAG': 9701 case 'SET_EVENT_RESIZE': 9702 this.renderRunner.tryDrain(); 9703 } 9704 }; 9705 this.handleData = (data) => { 9706 this.currentData = data; 9707 this.renderRunner.request(data.calendarOptions.rerenderDelay); 9708 }; 9709 this.handleRenderRequest = () => { 9710 if (this.isRendering) { 9711 this.isRendered = true; 9712 let { currentData } = this; 9713 flushSync(() => { 9714 D$1(y(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => { 9715 this.setClassNames(classNames); 9716 this.setHeight(height); 9717 return (y(RenderId.Provider, { value: this.customContentRenderId }, 9718 y(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData)))); 9719 }), this.el); 9720 }); 9721 } 9722 else if (this.isRendered) { 9723 this.isRendered = false; 9724 D$1(null, this.el); 9725 this.setClassNames([]); 9726 this.setHeight(''); 9727 } 9728 }; 9729 ensureElHasStyles(el); 9730 this.el = el; 9731 this.renderRunner = new DelayedRunner(this.handleRenderRequest); 9732 new CalendarDataManager({ 9733 optionOverrides, 9734 calendarApi: this, 9735 onAction: this.handleAction, 9736 onData: this.handleData, 9737 }); 9738 } 9739 render() { 9740 let wasRendering = this.isRendering; 9741 if (!wasRendering) { 9742 this.isRendering = true; 9743 } 9744 else { 9745 this.customContentRenderId += 1; 9746 } 9747 this.renderRunner.request(); 9748 if (wasRendering) { 9749 this.updateSize(); 9750 } 9751 } 9752 destroy() { 9753 if (this.isRendering) { 9754 this.isRendering = false; 9755 this.renderRunner.request(); 9756 } 9757 } 9758 updateSize() { 9759 flushSync(() => { 9760 super.updateSize(); 9761 }); 9762 } 9763 batchRendering(func) { 9764 this.renderRunner.pause('batchRendering'); 9765 func(); 9766 this.renderRunner.resume('batchRendering'); 9767 } 9768 pauseRendering() { 9769 this.renderRunner.pause('pauseRendering'); 9770 } 9771 resumeRendering() { 9772 this.renderRunner.resume('pauseRendering', true); 9773 } 9774 resetOptions(optionOverrides, changedOptionNames) { 9775 this.currentDataManager.resetOptions(optionOverrides, changedOptionNames); 9776 } 9777 setClassNames(classNames) { 9778 if (!isArraysEqual(classNames, this.currentClassNames)) { 9779 let { classList } = this.el; 9780 for (let className of this.currentClassNames) { 9781 classList.remove(className); 9782 } 9783 for (let className of classNames) { 9784 classList.add(className); 9785 } 9786 this.currentClassNames = classNames; 9787 } 9788 } 9789 setHeight(height) { 9790 applyStyleProp(this.el, 'height', height); 9791 } 9792 } 9793 9794 function formatDate(dateInput, options = {}) { 9795 let dateEnv = buildDateEnv(options); 9796 let formatter = createFormatter(options); 9797 let dateMeta = dateEnv.createMarkerMeta(dateInput); 9798 if (!dateMeta) { // TODO: warning? 9799 return ''; 9800 } 9801 return dateEnv.format(dateMeta.marker, formatter, { 9802 forcedTzo: dateMeta.forcedTzo, 9803 }); 9804 } 9805 function formatRange(startInput, endInput, options) { 9806 let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object 9807 let formatter = createFormatter(options); 9808 let startMeta = dateEnv.createMarkerMeta(startInput); 9809 let endMeta = dateEnv.createMarkerMeta(endInput); 9810 if (!startMeta || !endMeta) { // TODO: warning? 9811 return ''; 9812 } 9813 return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { 9814 forcedStartTzo: startMeta.forcedTzo, 9815 forcedEndTzo: endMeta.forcedTzo, 9816 isEndExclusive: options.isEndExclusive, 9817 defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, 9818 }); 9819 } 9820 // TODO: more DRY and optimized 9821 function buildDateEnv(settings) { 9822 let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere 9823 return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale })); 9824 } 9825 9826 // HELPERS 9827 /* 9828 if nextDayThreshold is specified, slicing is done in an all-day fashion. 9829 you can get nextDayThreshold from context.nextDayThreshold 9830 */ 9831 function sliceEvents(props, allDay) { 9832 return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; 9833 } 9834 9835 const version = '6.1.10'; 9836 9837 config.touchMouseIgnoreWait = 500; 9838 let ignoreMouseDepth = 0; 9839 let listenerCnt = 0; 9840 let isWindowTouchMoveCancelled = false;
vendor: 4,736 bytes, lines 9841-9928
9841 /* 9842 Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. 9843 Tracks when the pointer "drags" on a certain element, meaning down+move+up. 9844 9845 Also, tracks if there was touch-scrolling. 9846 Also, can prevent touch-scrolling from happening. 9847 Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. 9848 9849 emits: 9850 - pointerdown 9851 - pointermove 9852 - pointerup 9853 */ 9854 class PointerDragging { 9855 constructor(containerEl) { 9856 this.subjectEl = null; 9857 // options that can be directly assigned by caller 9858 this.selector = ''; // will cause subjectEl in all emitted events to be this element 9859 this.handleSelector = ''; 9860 this.shouldIgnoreMove = false; 9861 this.shouldWatchScroll = true; // for simulating pointermove on scroll 9862 // internal states 9863 this.isDragging = false; 9864 this.isTouchDragging = false; 9865 this.wasTouchScroll = false; 9866 // Mouse 9867 // ---------------------------------------------------------------------------------------------------- 9868 this.handleMouseDown = (ev) => { 9869 if (!this.shouldIgnoreMouse() && 9870 isPrimaryMouseButton(ev) && 9871 this.tryStart(ev)) { 9872 let pev = this.createEventFromMouse(ev, true); 9873 this.emitter.trigger('pointerdown', pev); 9874 this.initScrollWatch(pev); 9875 if (!this.shouldIgnoreMove) { 9876 document.addEventListener('mousemove', this.handleMouseMove); 9877 } 9878 document.addEventListener('mouseup', this.handleMouseUp); 9879 } 9880 }; 9881 this.handleMouseMove = (ev) => { 9882 let pev = this.createEventFromMouse(ev); 9883 this.recordCoords(pev); 9884 this.emitter.trigger('pointermove', pev); 9885 }; 9886 this.handleMouseUp = (ev) => { 9887 document.removeEventListener('mousemove', this.handleMouseMove); 9888 document.removeEventListener('mouseup', this.handleMouseUp); 9889 this.emitter.trigger('pointerup', this.createEventFromMouse(ev)); 9890 this.cleanup(); // call last so that pointerup has access to props 9891 }; 9892 // Touch 9893 // ---------------------------------------------------------------------------------------------------- 9894 this.handleTouchStart = (ev) => { 9895 if (this.tryStart(ev)) { 9896 this.isTouchDragging = true; 9897 let pev = this.createEventFromTouch(ev, true); 9898 this.emitter.trigger('pointerdown', pev); 9899 this.initScrollWatch(pev); 9900 // unlike mouse, need to attach to target, not document 9901 // https://stackoverflow.com/a/45760014 9902 let targetEl = ev.target; 9903 if (!this.shouldIgnoreMove) { 9904 targetEl.addEventListener('touchmove', this.handleTouchMove); 9905 } 9906 targetEl.addEventListener('touchend', this.handleTouchEnd); 9907 targetEl.addEventListener('touchcancel', this.handleTouchEnd); // treat it as a touch end 9908 // attach a handler to get called when ANY scroll action happens on the page. 9909 // this was impossible to do with normal on/off because 'scroll' doesn't bubble. 9910 // http://stackoverflow.com/a/32954565/96342 9911 window.addEventListener('scroll', this.handleTouchScroll, true); 9912 } 9913 }; 9914 this.handleTouchMove = (ev) => { 9915 let pev = this.createEventFromTouch(ev); 9916 this.recordCoords(pev); 9917 this.emitter.trigger('pointermove', pev); 9918 }; 9919 this.handleTouchEnd = (ev) => { 9920 if (this.isDragging) { // done to guard against touchend followed by touchcancel 9921 let targetEl = ev.target; 9922 targetEl.removeEventListener('touchmove', this.handleTouchMove); 9923 targetEl.removeEventListener('touchend', this.handleTouchEnd); 9924 targetEl.removeEventListener('touchcancel', this.handleTouchEnd); 9925 window.removeEventListener('scroll', this.handleTouchScroll, true); // useCaptured=true 9926 this.emitter.trigger('pointerup', this.createEventFromTouch(ev)); 9927 this.cleanup(); // call last so that pointerup has access to props 9928 this.isTouchDragging = false;
vendor: 16,857 bytes, lines 9929-10362
9929 startIgnoringMouse(); 9930 } 9931 }; 9932 this.handleTouchScroll = () => { 9933 this.wasTouchScroll = true; 9934 }; 9935 this.handleScroll = (ev) => { 9936 if (!this.shouldIgnoreMove) { 9937 let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX; 9938 let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY; 9939 this.emitter.trigger('pointermove', { 9940 origEvent: ev, 9941 isTouch: this.isTouchDragging, 9942 subjectEl: this.subjectEl, 9943 pageX, 9944 pageY, 9945 deltaX: pageX - this.origPageX, 9946 deltaY: pageY - this.origPageY, 9947 }); 9948 } 9949 }; 9950 this.containerEl = containerEl; 9951 this.emitter = new Emitter(); 9952 containerEl.addEventListener('mousedown', this.handleMouseDown); 9953 containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true }); 9954 listenerCreated(); 9955 } 9956 destroy() { 9957 this.containerEl.removeEventListener('mousedown', this.handleMouseDown); 9958 this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); 9959 listenerDestroyed(); 9960 } 9961 tryStart(ev) { 9962 let subjectEl = this.querySubjectEl(ev); 9963 let downEl = ev.target; 9964 if (subjectEl && 9965 (!this.handleSelector || elementClosest(downEl, this.handleSelector))) { 9966 this.subjectEl = subjectEl; 9967 this.isDragging = true; // do this first so cancelTouchScroll will work 9968 this.wasTouchScroll = false; 9969 return true; 9970 } 9971 return false; 9972 } 9973 cleanup() { 9974 isWindowTouchMoveCancelled = false; 9975 this.isDragging = false; 9976 this.subjectEl = null; 9977 // keep wasTouchScroll around for later access 9978 this.destroyScrollWatch(); 9979 } 9980 querySubjectEl(ev) { 9981 if (this.selector) { 9982 return elementClosest(ev.target, this.selector); 9983 } 9984 return this.containerEl; 9985 } 9986 shouldIgnoreMouse() { 9987 return ignoreMouseDepth || this.isTouchDragging; 9988 } 9989 // can be called by user of this class, to cancel touch-based scrolling for the current drag 9990 cancelTouchScroll() { 9991 if (this.isDragging) { 9992 isWindowTouchMoveCancelled = true; 9993 } 9994 } 9995 // Scrolling that simulates pointermoves 9996 // ---------------------------------------------------------------------------------------------------- 9997 initScrollWatch(ev) { 9998 if (this.shouldWatchScroll) { 9999 this.recordCoords(ev); 10000 window.addEventListener('scroll', this.handleScroll, true); // useCapture=true 10001 } 10002 } 10003 recordCoords(ev) { 10004 if (this.shouldWatchScroll) { 10005 this.prevPageX = ev.pageX; 10006 this.prevPageY = ev.pageY; 10007 this.prevScrollX = window.pageXOffset; 10008 this.prevScrollY = window.pageYOffset; 10009 } 10010 } 10011 destroyScrollWatch() { 10012 if (this.shouldWatchScroll) { 10013 window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true 10014 } 10015 } 10016 // Event Normalization 10017 // ---------------------------------------------------------------------------------------------------- 10018 createEventFromMouse(ev, isFirst) { 10019 let deltaX = 0; 10020 let deltaY = 0; 10021 // TODO: repeat code 10022 if (isFirst) { 10023 this.origPageX = ev.pageX; 10024 this.origPageY = ev.pageY; 10025 } 10026 else { 10027 deltaX = ev.pageX - this.origPageX; 10028 deltaY = ev.pageY - this.origPageY; 10029 } 10030 return { 10031 origEvent: ev, 10032 isTouch: false, 10033 subjectEl: this.subjectEl, 10034 pageX: ev.pageX, 10035 pageY: ev.pageY, 10036 deltaX, 10037 deltaY, 10038 }; 10039 } 10040 createEventFromTouch(ev, isFirst) { 10041 let touches = ev.touches; 10042 let pageX; 10043 let pageY; 10044 let deltaX = 0; 10045 let deltaY = 0; 10046 // if touch coords available, prefer, 10047 // because FF would give bad ev.pageX ev.pageY 10048 if (touches && touches.length) { 10049 pageX = touches[0].pageX; 10050 pageY = touches[0].pageY; 10051 } 10052 else { 10053 pageX = ev.pageX; 10054 pageY = ev.pageY; 10055 } 10056 // TODO: repeat code 10057 if (isFirst) { 10058 this.origPageX = pageX; 10059 this.origPageY = pageY; 10060 } 10061 else { 10062 deltaX = pageX - this.origPageX; 10063 deltaY = pageY - this.origPageY; 10064 } 10065 return { 10066 origEvent: ev, 10067 isTouch: true, 10068 subjectEl: this.subjectEl, 10069 pageX, 10070 pageY, 10071 deltaX, 10072 deltaY, 10073 }; 10074 } 10075 } 10076 // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) 10077 function isPrimaryMouseButton(ev) { 10078 return ev.button === 0 && !ev.ctrlKey; 10079 } 10080 // Ignoring fake mouse events generated by touch 10081 // ---------------------------------------------------------------------------------------------------- 10082 function startIgnoringMouse() { 10083 ignoreMouseDepth += 1; 10084 setTimeout(() => { 10085 ignoreMouseDepth -= 1; 10086 }, config.touchMouseIgnoreWait); 10087 } 10088 // We want to attach touchmove as early as possible for Safari 10089 // ---------------------------------------------------------------------------------------------------- 10090 function listenerCreated() { 10091 listenerCnt += 1; 10092 if (listenerCnt === 1) { 10093 window.addEventListener('touchmove', onWindowTouchMove, { passive: false }); 10094 } 10095 } 10096 function listenerDestroyed() { 10097 listenerCnt -= 1; 10098 if (!listenerCnt) { 10099 window.removeEventListener('touchmove', onWindowTouchMove, { passive: false }); 10100 } 10101 } 10102 function onWindowTouchMove(ev) { 10103 if (isWindowTouchMoveCancelled) { 10104 ev.preventDefault(); 10105 } 10106 } 10107 10108 /* 10109 An effect in which an element follows the movement of a pointer across the screen. 10110 The moving element is a clone of some other element. 10111 Must call start + handleMove + stop. 10112 */ 10113 class ElementMirror { 10114 constructor() { 10115 this.isVisible = false; // must be explicitly enabled 10116 this.sourceEl = null; 10117 this.mirrorEl = null; 10118 this.sourceElRect = null; // screen coords relative to viewport 10119 // options that can be set directly by caller 10120 this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues 10121 this.zIndex = 9999; 10122 this.revertDuration = 0; 10123 } 10124 start(sourceEl, pageX, pageY) { 10125 this.sourceEl = sourceEl; 10126 this.sourceElRect = this.sourceEl.getBoundingClientRect(); 10127 this.origScreenX = pageX - window.pageXOffset; 10128 this.origScreenY = pageY - window.pageYOffset; 10129 this.deltaX = 0; 10130 this.deltaY = 0; 10131 this.updateElPosition(); 10132 } 10133 handleMove(pageX, pageY) { 10134 this.deltaX = (pageX - window.pageXOffset) - this.origScreenX; 10135 this.deltaY = (pageY - window.pageYOffset) - this.origScreenY; 10136 this.updateElPosition(); 10137 } 10138 // can be called before start 10139 setIsVisible(bool) { 10140 if (bool) { 10141 if (!this.isVisible) { 10142 if (this.mirrorEl) { 10143 this.mirrorEl.style.display = ''; 10144 } 10145 this.isVisible = bool; // needs to happen before updateElPosition 10146 this.updateElPosition(); // because was not updating the position while invisible 10147 } 10148 } 10149 else if (this.isVisible) { 10150 if (this.mirrorEl) { 10151 this.mirrorEl.style.display = 'none'; 10152 } 10153 this.isVisible = bool; 10154 } 10155 } 10156 // always async 10157 stop(needsRevertAnimation, callback) { 10158 let done = () => { 10159 this.cleanup(); 10160 callback(); 10161 }; 10162 if (needsRevertAnimation && 10163 this.mirrorEl && 10164 this.isVisible && 10165 this.revertDuration && // if 0, transition won't work 10166 (this.deltaX || this.deltaY) // if same coords, transition won't work 10167 ) { 10168 this.doRevertAnimation(done, this.revertDuration); 10169 } 10170 else { 10171 setTimeout(done, 0); 10172 } 10173 } 10174 doRevertAnimation(callback, revertDuration) { 10175 let mirrorEl = this.mirrorEl; 10176 let finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened 10177 mirrorEl.style.transition = 10178 'top ' + revertDuration + 'ms,' + 10179 'left ' + revertDuration + 'ms'; 10180 applyStyle(mirrorEl, { 10181 left: finalSourceElRect.left, 10182 top: finalSourceElRect.top, 10183 }); 10184 whenTransitionDone(mirrorEl, () => { 10185 mirrorEl.style.transition = ''; 10186 callback(); 10187 }); 10188 } 10189 cleanup() { 10190 if (this.mirrorEl) { 10191 removeElement(this.mirrorEl); 10192 this.mirrorEl = null; 10193 } 10194 this.sourceEl = null; 10195 } 10196 updateElPosition() { 10197 if (this.sourceEl && this.isVisible) { 10198 applyStyle(this.getMirrorEl(), { 10199 left: this.sourceElRect.left + this.deltaX, 10200 top: this.sourceElRect.top + this.deltaY, 10201 }); 10202 } 10203 } 10204 getMirrorEl() { 10205 let sourceElRect = this.sourceElRect; 10206 let mirrorEl = this.mirrorEl; 10207 if (!mirrorEl) { 10208 mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true 10209 // we don't want long taps or any mouse interaction causing selection/menus. 10210 // would use preventSelection(), but that prevents selectstart, causing problems. 10211 mirrorEl.style.userSelect = 'none'; 10212 mirrorEl.style.webkitUserSelect = 'none'; 10213 mirrorEl.classList.add('fc-event-dragging'); 10214 applyStyle(mirrorEl, { 10215 position: 'fixed', 10216 zIndex: this.zIndex, 10217 visibility: '', 10218 boxSizing: 'border-box', 10219 width: sourceElRect.right - sourceElRect.left, 10220 height: sourceElRect.bottom - sourceElRect.top, 10221 right: 'auto', 10222 bottom: 'auto', 10223 margin: 0, 10224 }); 10225 this.parentNode.appendChild(mirrorEl); 10226 } 10227 return mirrorEl; 10228 } 10229 } 10230 10231 /* 10232 Is a cache for a given element's scroll information (all the info that ScrollController stores) 10233 in addition the "client rectangle" of the element.. the area within the scrollbars. 10234 10235 The cache can be in one of two modes: 10236 - doesListening:false - ignores when the container is scrolled by someone else 10237 - doesListening:true - watch for scrolling and update the cache 10238 */ 10239 class ScrollGeomCache extends ScrollController { 10240 constructor(scrollController, doesListening) { 10241 super(); 10242 this.handleScroll = () => { 10243 this.scrollTop = this.scrollController.getScrollTop(); 10244 this.scrollLeft = this.scrollController.getScrollLeft(); 10245 this.handleScrollChange(); 10246 }; 10247 this.scrollController = scrollController; 10248 this.doesListening = doesListening; 10249 this.scrollTop = this.origScrollTop = scrollController.getScrollTop(); 10250 this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft(); 10251 this.scrollWidth = scrollController.getScrollWidth(); 10252 this.scrollHeight = scrollController.getScrollHeight(); 10253 this.clientWidth = scrollController.getClientWidth(); 10254 this.clientHeight = scrollController.getClientHeight(); 10255 this.clientRect = this.computeClientRect(); // do last in case it needs cached values 10256 if (this.doesListening) { 10257 this.getEventTarget().addEventListener('scroll', this.handleScroll); 10258 } 10259 } 10260 destroy() { 10261 if (this.doesListening) { 10262 this.getEventTarget().removeEventListener('scroll', this.handleScroll); 10263 } 10264 } 10265 getScrollTop() { 10266 return this.scrollTop; 10267 } 10268 getScrollLeft() { 10269 return this.scrollLeft; 10270 } 10271 setScrollTop(top) { 10272 this.scrollController.setScrollTop(top); 10273 if (!this.doesListening) { 10274 // we are not relying on the element to normalize out-of-bounds scroll values 10275 // so we need to sanitize ourselves 10276 this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0); 10277 this.handleScrollChange(); 10278 } 10279 } 10280 setScrollLeft(top) { 10281 this.scrollController.setScrollLeft(top); 10282 if (!this.doesListening) { 10283 // we are not relying on the element to normalize out-of-bounds scroll values 10284 // so we need to sanitize ourselves 10285 this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0); 10286 this.handleScrollChange(); 10287 } 10288 } 10289 getClientWidth() { 10290 return this.clientWidth; 10291 } 10292 getClientHeight() { 10293 return this.clientHeight; 10294 } 10295 getScrollWidth() { 10296 return this.scrollWidth; 10297 } 10298 getScrollHeight() { 10299 return this.scrollHeight; 10300 } 10301 handleScrollChange() { 10302 } 10303 } 10304 10305 class ElementScrollGeomCache extends ScrollGeomCache { 10306 constructor(el, doesListening) { 10307 super(new ElementScrollController(el), doesListening); 10308 } 10309 getEventTarget() { 10310 return this.scrollController.el; 10311 } 10312 computeClientRect() { 10313 return computeInnerRect(this.scrollController.el); 10314 } 10315 } 10316 10317 class WindowScrollGeomCache extends ScrollGeomCache { 10318 constructor(doesListening) { 10319 super(new WindowScrollController(), doesListening); 10320 } 10321 getEventTarget() { 10322 return window; 10323 } 10324 computeClientRect() { 10325 return { 10326 left: this.scrollLeft, 10327 right: this.scrollLeft + this.clientWidth, 10328 top: this.scrollTop, 10329 bottom: this.scrollTop + this.clientHeight, 10330 }; 10331 } 10332 // the window is the only scroll object that changes it's rectangle relative 10333 // to the document's topleft as it scrolls 10334 handleScrollChange() { 10335 this.clientRect = this.computeClientRect(); 10336 } 10337 } 10338 10339 // If available we are using native "performance" API instead of "Date" 10340 // Read more about it on MDN: 10341 // https://developer.mozilla.org/en-US/docs/Web/API/Performance 10342 const getTime = typeof performance === 'function' ? performance.now : Date.now; 10343 /* 10344 For a pointer interaction, automatically scrolls certain scroll containers when the pointer 10345 approaches the edge. 10346 10347 The caller must call start + handleMove + stop. 10348 */ 10349 class AutoScroller { 10350 constructor() { 10351 // options that can be set by caller 10352 this.isEnabled = true; 10353 this.scrollQuery = [window, '.fc-scroller']; 10354 this.edgeThreshold = 50; // pixels 10355 this.maxVelocity = 300; // pixels per second 10356 // internal state 10357 this.pointerScreenX = null; 10358 this.pointerScreenY = null; 10359 this.isAnimating = false; 10360 this.scrollCaches = null; 10361 // protect against the initial pointerdown being too close to an edge and starting the scroll 10362 this.everMovedUp = false;
vendor: 6,701 bytes, lines 10363-10514
10363 this.everMovedDown = false; 10364 this.everMovedLeft = false; 10365 this.everMovedRight = false; 10366 this.animate = () => { 10367 if (this.isAnimating) { // wasn't cancelled between animation calls 10368 let edge = this.computeBestEdge(this.pointerScreenX + window.pageXOffset, this.pointerScreenY + window.pageYOffset); 10369 if (edge) { 10370 let now = getTime(); 10371 this.handleSide(edge, (now - this.msSinceRequest) / 1000); 10372 this.requestAnimation(now); 10373 } 10374 else { 10375 this.isAnimating = false; // will stop animation 10376 } 10377 } 10378 }; 10379 } 10380 start(pageX, pageY, scrollStartEl) { 10381 if (this.isEnabled) { 10382 this.scrollCaches = this.buildCaches(scrollStartEl); 10383 this.pointerScreenX = null; 10384 this.pointerScreenY = null; 10385 this.everMovedUp = false; 10386 this.everMovedDown = false; 10387 this.everMovedLeft = false; 10388 this.everMovedRight = false; 10389 this.handleMove(pageX, pageY); 10390 } 10391 } 10392 handleMove(pageX, pageY) { 10393 if (this.isEnabled) { 10394 let pointerScreenX = pageX - window.pageXOffset; 10395 let pointerScreenY = pageY - window.pageYOffset; 10396 let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY; 10397 let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX; 10398 if (yDelta < 0) { 10399 this.everMovedUp = true; 10400 } 10401 else if (yDelta > 0) { 10402 this.everMovedDown = true; 10403 } 10404 if (xDelta < 0) { 10405 this.everMovedLeft = true; 10406 } 10407 else if (xDelta > 0) { 10408 this.everMovedRight = true; 10409 } 10410 this.pointerScreenX = pointerScreenX; 10411 this.pointerScreenY = pointerScreenY; 10412 if (!this.isAnimating) { 10413 this.isAnimating = true; 10414 this.requestAnimation(getTime()); 10415 } 10416 } 10417 } 10418 stop() { 10419 if (this.isEnabled) { 10420 this.isAnimating = false; // will stop animation 10421 for (let scrollCache of this.scrollCaches) { 10422 scrollCache.destroy(); 10423 } 10424 this.scrollCaches = null; 10425 } 10426 } 10427 requestAnimation(now) { 10428 this.msSinceRequest = now; 10429 requestAnimationFrame(this.animate); 10430 } 10431 handleSide(edge, seconds) { 10432 let { scrollCache } = edge; 10433 let { edgeThreshold } = this; 10434 let invDistance = edgeThreshold - edge.distance; 10435 let velocity = // the closer to the edge, the faster we scroll 10436 ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic 10437 this.maxVelocity * seconds; 10438 let sign = 1; 10439 switch (edge.name) { 10440 case 'left': 10441 sign = -1; 10442 // falls through 10443 case 'right': 10444 scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); 10445 break; 10446 case 'top': 10447 sign = -1; 10448 // falls through 10449 case 'bottom': 10450 scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); 10451 break; 10452 } 10453 } 10454 // left/top are relative to document topleft 10455 computeBestEdge(left, top) { 10456 let { edgeThreshold } = this; 10457 let bestSide = null; 10458 let scrollCaches = this.scrollCaches || []; 10459 for (let scrollCache of scrollCaches) { 10460 let rect = scrollCache.clientRect; 10461 let leftDist = left - rect.left; 10462 let rightDist = rect.right - left; 10463 let topDist = top - rect.top; 10464 let bottomDist = rect.bottom - top; 10465 // completely within the rect? 10466 if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { 10467 if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && 10468 (!bestSide || bestSide.distance > topDist)) { 10469 bestSide = { scrollCache, name: 'top', distance: topDist }; 10470 } 10471 if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && 10472 (!bestSide || bestSide.distance > bottomDist)) { 10473 bestSide = { scrollCache, name: 'bottom', distance: bottomDist }; 10474 } 10475 if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && 10476 (!bestSide || bestSide.distance > leftDist)) { 10477 bestSide = { scrollCache, name: 'left', distance: leftDist }; 10478 } 10479 if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && 10480 (!bestSide || bestSide.distance > rightDist)) { 10481 bestSide = { scrollCache, name: 'right', distance: rightDist }; 10482 } 10483 } 10484 } 10485 return bestSide; 10486 } 10487 buildCaches(scrollStartEl) { 10488 return this.queryScrollEls(scrollStartEl).map((el) => { 10489 if (el === window) { 10490 return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls 10491 } 10492 return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls 10493 }); 10494 } 10495 queryScrollEls(scrollStartEl) { 10496 let els = []; 10497 for (let query of this.scrollQuery) { 10498 if (typeof query === 'object') { 10499 els.push(query); 10500 } 10501 else { 10502 els.push(...Array.prototype.slice.call(scrollStartEl.getRootNode().querySelectorAll(query))); 10503 } 10504 } 10505 return els; 10506 } 10507 } 10508 10509 /* 10510 Monitors dragging on an element. Has a number of high-level features: 10511 - minimum distance required before dragging 10512 - minimum wait time ("delay") before dragging 10513 - a mirror element that follows the pointer 10514 */
vendor: 8,987 bytes, lines 10515-10710
10515 class FeaturefulElementDragging extends ElementDragging { 10516 constructor(containerEl, selector) { 10517 super(containerEl); 10518 this.containerEl = containerEl; 10519 // options that can be directly set by caller 10520 // the caller can also set the PointerDragging's options as well 10521 this.delay = null; 10522 this.minDistance = 0; 10523 this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag 10524 this.mirrorNeedsRevert = false; 10525 this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup 10526 this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation 10527 this.isDelayEnded = false; 10528 this.isDistanceSurpassed = false; 10529 this.delayTimeoutId = null; 10530 this.onPointerDown = (ev) => { 10531 if (!this.isDragging) { // so new drag doesn't happen while revert animation is going 10532 this.isInteracting = true; 10533 this.isDelayEnded = false; 10534 this.isDistanceSurpassed = false; 10535 preventSelection(document.body); 10536 preventContextMenu(document.body); 10537 // prevent links from being visited if there's an eventual drag. 10538 // also prevents selection in older browsers (maybe?). 10539 // not necessary for touch, besides, browser would complain about passiveness. 10540 if (!ev.isTouch) { 10541 ev.origEvent.preventDefault(); 10542 } 10543 this.emitter.trigger('pointerdown', ev); 10544 if (this.isInteracting && // not destroyed via pointerdown handler 10545 !this.pointer.shouldIgnoreMove) { 10546 // actions related to initiating dragstart+dragmove+dragend... 10547 this.mirror.setIsVisible(false); // reset. caller must set-visible 10548 this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down 10549 this.startDelay(ev); 10550 if (!this.minDistance) { 10551 this.handleDistanceSurpassed(ev); 10552 } 10553 } 10554 } 10555 }; 10556 this.onPointerMove = (ev) => { 10557 if (this.isInteracting) { 10558 this.emitter.trigger('pointermove', ev); 10559 if (!this.isDistanceSurpassed) { 10560 let minDistance = this.minDistance; 10561 let distanceSq; // current distance from the origin, squared 10562 let { deltaX, deltaY } = ev; 10563 distanceSq = deltaX * deltaX + deltaY * deltaY; 10564 if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem 10565 this.handleDistanceSurpassed(ev); 10566 } 10567 } 10568 if (this.isDragging) { 10569 // a real pointer move? (not one simulated by scrolling) 10570 if (ev.origEvent.type !== 'scroll') { 10571 this.mirror.handleMove(ev.pageX, ev.pageY); 10572 this.autoScroller.handleMove(ev.pageX, ev.pageY); 10573 } 10574 this.emitter.trigger('dragmove', ev); 10575 } 10576 } 10577 }; 10578 this.onPointerUp = (ev) => { 10579 if (this.isInteracting) { 10580 this.isInteracting = false; 10581 allowSelection(document.body); 10582 allowContextMenu(document.body); 10583 this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert 10584 if (this.isDragging) { 10585 this.autoScroller.stop(); 10586 this.tryStopDrag(ev); // which will stop the mirror 10587 } 10588 if (this.delayTimeoutId) { 10589 clearTimeout(this.delayTimeoutId); 10590 this.delayTimeoutId = null; 10591 } 10592 } 10593 }; 10594 let pointer = this.pointer = new PointerDragging(containerEl); 10595 pointer.emitter.on('pointerdown', this.onPointerDown); 10596 pointer.emitter.on('pointermove', this.onPointerMove); 10597 pointer.emitter.on('pointerup', this.onPointerUp); 10598 if (selector) { 10599 pointer.selector = selector; 10600 } 10601 this.mirror = new ElementMirror(); 10602 this.autoScroller = new AutoScroller(); 10603 } 10604 destroy() { 10605 this.pointer.destroy(); 10606 // HACK: simulate a pointer-up to end the current drag 10607 // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) 10608 this.onPointerUp({}); 10609 } 10610 startDelay(ev) { 10611 if (typeof this.delay === 'number') { 10612 this.delayTimeoutId = setTimeout(() => { 10613 this.delayTimeoutId = null; 10614 this.handleDelayEnd(ev); 10615 }, this.delay); // not assignable to number! 10616 } 10617 else { 10618 this.handleDelayEnd(ev); 10619 } 10620 } 10621 handleDelayEnd(ev) { 10622 this.isDelayEnded = true; 10623 this.tryStartDrag(ev); 10624 } 10625 handleDistanceSurpassed(ev) { 10626 this.isDistanceSurpassed = true; 10627 this.tryStartDrag(ev); 10628 } 10629 tryStartDrag(ev) { 10630 if (this.isDelayEnded && this.isDistanceSurpassed) { 10631 if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { 10632 this.isDragging = true; 10633 this.mirrorNeedsRevert = false; 10634 this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl); 10635 this.emitter.trigger('dragstart', ev); 10636 if (this.touchScrollAllowed === false) { 10637 this.pointer.cancelTouchScroll(); 10638 } 10639 } 10640 } 10641 } 10642 tryStopDrag(ev) { 10643 // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events 10644 // that come from the document to fire beforehand. much more convenient this way. 10645 this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev)); 10646 } 10647 stopDrag(ev) { 10648 this.isDragging = false; 10649 this.emitter.trigger('dragend', ev); 10650 } 10651 // fill in the implementations... 10652 setIgnoreMove(bool) { 10653 this.pointer.shouldIgnoreMove = bool; 10654 } 10655 setMirrorIsVisible(bool) { 10656 this.mirror.setIsVisible(bool); 10657 } 10658 setMirrorNeedsRevert(bool) { 10659 this.mirrorNeedsRevert = bool; 10660 } 10661 setAutoScrollEnabled(bool) { 10662 this.autoScroller.isEnabled = bool; 10663 } 10664 } 10665 10666 /* 10667 When this class is instantiated, it records the offset of an element (relative to the document topleft), 10668 and continues to monitor scrolling, updating the cached coordinates if it needs to. 10669 Does not access the DOM after instantiation, so highly performant. 10670 10671 Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element 10672 and an determine if a given point is inside the combined clipping rectangle. 10673 */ 10674 class OffsetTracker { 10675 constructor(el) { 10676 this.origRect = computeRect(el); 10677 // will work fine for divs that have overflow:hidden 10678 this.scrollCaches = getClippingParents(el).map((scrollEl) => new ElementScrollGeomCache(scrollEl, true)); 10679 } 10680 destroy() { 10681 for (let scrollCache of this.scrollCaches) { 10682 scrollCache.destroy(); 10683 } 10684 } 10685 computeLeft() { 10686 let left = this.origRect.left; 10687 for (let scrollCache of this.scrollCaches) { 10688 left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); 10689 } 10690 return left; 10691 } 10692 computeTop() { 10693 let top = this.origRect.top; 10694 for (let scrollCache of this.scrollCaches) { 10695 top += scrollCache.origScrollTop - scrollCache.getScrollTop(); 10696 } 10697 return top; 10698 } 10699 isWithinClipping(pageX, pageY) { 10700 let point = { left: pageX, top: pageY }; 10701 for (let scrollCache of this.scrollCaches) { 10702 if (!isIgnoredClipping(scrollCache.getEventTarget()) && 10703 !pointInsideRect(point, scrollCache.clientRect)) { 10704 return false; 10705 } 10706 } 10707 return true; 10708 } 10709 } 10710 // certain clipping containers should never constrain interactions, like <html> and <body>
10711 // https://github.com/fullcalendar/fullcalendar/issues/3615 10712 function isIgnoredClipping(node) { 10713 let tagName = node.tagName; 10714 return tagName === 'HTML' || tagName === 'BODY'; 10715 } 10716 10717 /* 10718 Tracks movement over multiple droppable areas (aka "hits") 10719 that exist in one or more DateComponents. 10720 Relies on an existing draggable. 10721 10722 emits: 10723 - pointerdown 10724 - dragstart 10725 - hitchange - fires initially, even if not over a hit 10726 - pointerup 10727 - (hitchange - again, to null, if ended over a hit) 10728 - dragend 10729 */ 10730 class HitDragging { 10731 constructor(dragging, droppableStore) { 10732 // options that can be set by caller 10733 this.useSubjectCenter = false; 10734 this.requireInitial = true; // if doesn't start out on a hit, won't emit any events 10735 this.initialHit = null; 10736 this.movingHit = null; 10737 this.finalHit = null; // won't ever be populated if shouldIgnoreMove 10738 this.handlePointerDown = (ev) => { 10739 let { dragging } = this; 10740 this.initialHit = null; 10741 this.movingHit = null; 10742 this.finalHit = null; 10743 this.prepareHits(); 10744 this.processFirstCoord(ev); 10745 if (this.initialHit || !this.requireInitial) { 10746 dragging.setIgnoreMove(false);
vendor: 52,367 bytes, lines 10747-11810
10747 // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( 10748 this.emitter.trigger('pointerdown', ev); 10749 } 10750 else { 10751 dragging.setIgnoreMove(true); 10752 } 10753 }; 10754 this.handleDragStart = (ev) => { 10755 this.emitter.trigger('dragstart', ev); 10756 this.handleMove(ev, true); // force = fire even if initially null 10757 }; 10758 this.handleDragMove = (ev) => { 10759 this.emitter.trigger('dragmove', ev); 10760 this.handleMove(ev); 10761 }; 10762 this.handlePointerUp = (ev) => { 10763 this.releaseHits(); 10764 this.emitter.trigger('pointerup', ev); 10765 }; 10766 this.handleDragEnd = (ev) => { 10767 if (this.movingHit) { 10768 this.emitter.trigger('hitupdate', null, true, ev); 10769 } 10770 this.finalHit = this.movingHit; 10771 this.movingHit = null; 10772 this.emitter.trigger('dragend', ev); 10773 }; 10774 this.droppableStore = droppableStore; 10775 dragging.emitter.on('pointerdown', this.handlePointerDown); 10776 dragging.emitter.on('dragstart', this.handleDragStart); 10777 dragging.emitter.on('dragmove', this.handleDragMove); 10778 dragging.emitter.on('pointerup', this.handlePointerUp); 10779 dragging.emitter.on('dragend', this.handleDragEnd); 10780 this.dragging = dragging; 10781 this.emitter = new Emitter(); 10782 } 10783 // sets initialHit 10784 // sets coordAdjust 10785 processFirstCoord(ev) { 10786 let origPoint = { left: ev.pageX, top: ev.pageY }; 10787 let adjustedPoint = origPoint; 10788 let subjectEl = ev.subjectEl; 10789 let subjectRect; 10790 if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot 10791 subjectRect = computeRect(subjectEl); 10792 adjustedPoint = constrainPoint(adjustedPoint, subjectRect); 10793 } 10794 let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); 10795 if (initialHit) { 10796 if (this.useSubjectCenter && subjectRect) { 10797 let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect); 10798 if (slicedSubjectRect) { 10799 adjustedPoint = getRectCenter(slicedSubjectRect); 10800 } 10801 } 10802 this.coordAdjust = diffPoints(adjustedPoint, origPoint); 10803 } 10804 else { 10805 this.coordAdjust = { left: 0, top: 0 }; 10806 } 10807 } 10808 handleMove(ev, forceHandle) { 10809 let hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); 10810 if (forceHandle || !isHitsEqual(this.movingHit, hit)) { 10811 this.movingHit = hit; 10812 this.emitter.trigger('hitupdate', hit, false, ev); 10813 } 10814 } 10815 prepareHits() { 10816 this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { 10817 interactionSettings.component.prepareHits(); 10818 return new OffsetTracker(interactionSettings.el); 10819 }); 10820 } 10821 releaseHits() { 10822 let { offsetTrackers } = this; 10823 for (let id in offsetTrackers) { 10824 offsetTrackers[id].destroy(); 10825 } 10826 this.offsetTrackers = {}; 10827 } 10828 queryHitForOffset(offsetLeft, offsetTop) { 10829 let { droppableStore, offsetTrackers } = this; 10830 let bestHit = null; 10831 for (let id in droppableStore) { 10832 let component = droppableStore[id].component; 10833 let offsetTracker = offsetTrackers[id]; 10834 if (offsetTracker && // wasn't destroyed mid-drag 10835 offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { 10836 let originLeft = offsetTracker.computeLeft(); 10837 let originTop = offsetTracker.computeTop(); 10838 let positionLeft = offsetLeft - originLeft; 10839 let positionTop = offsetTop - originTop; 10840 let { origRect } = offsetTracker; 10841 let width = origRect.right - origRect.left; 10842 let height = origRect.bottom - origRect.top; 10843 if ( 10844 // must be within the element's bounds 10845 positionLeft >= 0 && positionLeft < width && 10846 positionTop >= 0 && positionTop < height) { 10847 let hit = component.queryHit(positionLeft, positionTop, width, height); 10848 if (hit && ( 10849 // make sure the hit is within activeRange, meaning it's not a dead cell 10850 rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) && 10851 (!bestHit || hit.layer > bestHit.layer)) { 10852 hit.componentId = id; 10853 hit.context = component.context; 10854 // TODO: better way to re-orient rectangle 10855 hit.rect.left += originLeft; 10856 hit.rect.right += originLeft; 10857 hit.rect.top += originTop; 10858 hit.rect.bottom += originTop; 10859 bestHit = hit; 10860 } 10861 } 10862 } 10863 } 10864 return bestHit; 10865 } 10866 } 10867 function isHitsEqual(hit0, hit1) { 10868 if (!hit0 && !hit1) { 10869 return true; 10870 } 10871 if (Boolean(hit0) !== Boolean(hit1)) { 10872 return false; 10873 } 10874 return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); 10875 } 10876 10877 function buildDatePointApiWithContext(dateSpan, context) { 10878 let props = {}; 10879 for (let transform of context.pluginHooks.datePointTransforms) { 10880 Object.assign(props, transform(dateSpan, context)); 10881 } 10882 Object.assign(props, buildDatePointApi(dateSpan, context.dateEnv)); 10883 return props; 10884 } 10885 function buildDatePointApi(span, dateEnv) { 10886 return { 10887 date: dateEnv.toDate(span.range.start), 10888 dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), 10889 allDay: span.allDay, 10890 }; 10891 } 10892 10893 /* 10894 Monitors when the user clicks on a specific date/time of a component. 10895 A pointerdown+pointerup on the same "hit" constitutes a click. 10896 */ 10897 class DateClicking extends Interaction { 10898 constructor(settings) { 10899 super(settings); 10900 this.handlePointerDown = (pev) => { 10901 let { dragging } = this; 10902 let downEl = pev.origEvent.target; 10903 // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired 10904 dragging.setIgnoreMove(!this.component.isValidDateDownEl(downEl)); 10905 }; 10906 // won't even fire if moving was ignored 10907 this.handleDragEnd = (ev) => { 10908 let { component } = this; 10909 let { pointer } = this.dragging; 10910 if (!pointer.wasTouchScroll) { 10911 let { initialHit, finalHit } = this.hitDragging; 10912 if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { 10913 let { context } = component; 10914 let arg = Object.assign(Object.assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view }); 10915 context.emitter.trigger('dateClick', arg); 10916 } 10917 } 10918 }; 10919 // we DO want to watch pointer moves because otherwise finalHit won't get populated 10920 this.dragging = new FeaturefulElementDragging(settings.el); 10921 this.dragging.autoScroller.isEnabled = false; 10922 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 10923 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 10924 hitDragging.emitter.on('dragend', this.handleDragEnd); 10925 } 10926 destroy() { 10927 this.dragging.destroy(); 10928 } 10929 } 10930 10931 /* 10932 Tracks when the user selects a portion of time of a component, 10933 constituted by a drag over date cells, with a possible delay at the beginning of the drag. 10934 */ 10935 class DateSelecting extends Interaction { 10936 constructor(settings) { 10937 super(settings); 10938 this.dragSelection = null; 10939 this.handlePointerDown = (ev) => { 10940 let { component, dragging } = this; 10941 let { options } = component.context; 10942 let canSelect = options.selectable && 10943 component.isValidDateDownEl(ev.origEvent.target); 10944 // don't bother to watch expensive moves if component won't do selection 10945 dragging.setIgnoreMove(!canSelect); 10946 // if touch, require user to hold down 10947 dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null; 10948 }; 10949 this.handleDragStart = (ev) => { 10950 this.component.context.calendarApi.unselect(ev); // unselect previous selections 10951 }; 10952 this.handleHitUpdate = (hit, isFinal) => { 10953 let { context } = this.component; 10954 let dragSelection = null; 10955 let isInvalid = false; 10956 if (hit) { 10957 let initialHit = this.hitDragging.initialHit; 10958 let disallowed = hit.componentId === initialHit.componentId 10959 && this.isHitComboAllowed 10960 && !this.isHitComboAllowed(initialHit, hit); 10961 if (!disallowed) { 10962 dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers); 10963 } 10964 if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { 10965 isInvalid = true; 10966 dragSelection = null; 10967 } 10968 } 10969 if (dragSelection) { 10970 context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); 10971 } 10972 else if (!isFinal) { // only unselect if moved away while dragging 10973 context.dispatch({ type: 'UNSELECT_DATES' }); 10974 } 10975 if (!isInvalid) { 10976 enableCursor(); 10977 } 10978 else { 10979 disableCursor(); 10980 } 10981 if (!isFinal) { 10982 this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging 10983 } 10984 }; 10985 this.handlePointerUp = (pev) => { 10986 if (this.dragSelection) { 10987 // selection is already rendered, so just need to report selection 10988 triggerDateSelect(this.dragSelection, pev, this.component.context); 10989 this.dragSelection = null; 10990 } 10991 }; 10992 let { component } = settings; 10993 let { options } = component.context; 10994 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 10995 dragging.touchScrollAllowed = false; 10996 dragging.minDistance = options.selectMinDistance || 0; 10997 dragging.autoScroller.isEnabled = options.dragScroll; 10998 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 10999 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 11000 hitDragging.emitter.on('dragstart', this.handleDragStart); 11001 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11002 hitDragging.emitter.on('pointerup', this.handlePointerUp); 11003 } 11004 destroy() { 11005 this.dragging.destroy(); 11006 } 11007 } 11008 function getComponentTouchDelay$1(component) { 11009 let { options } = component.context; 11010 let delay = options.selectLongPressDelay; 11011 if (delay == null) { 11012 delay = options.longPressDelay; 11013 } 11014 return delay; 11015 } 11016 function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { 11017 let dateSpan0 = hit0.dateSpan; 11018 let dateSpan1 = hit1.dateSpan; 11019 let ms = [ 11020 dateSpan0.range.start, 11021 dateSpan0.range.end, 11022 dateSpan1.range.start, 11023 dateSpan1.range.end, 11024 ]; 11025 ms.sort(compareNumbers); 11026 let props = {}; 11027 for (let transformer of dateSelectionTransformers) { 11028 let res = transformer(hit0, hit1); 11029 if (res === false) { 11030 return null; 11031 } 11032 if (res) { 11033 Object.assign(props, res); 11034 } 11035 } 11036 props.range = { start: ms[0], end: ms[3] }; 11037 props.allDay = dateSpan0.allDay; 11038 return props; 11039 } 11040 11041 class EventDragging extends Interaction { 11042 constructor(settings) { 11043 super(settings); 11044 // internal state 11045 this.subjectEl = null; 11046 this.subjectSeg = null; // the seg being selected/dragged 11047 this.isDragging = false; 11048 this.eventRange = null; 11049 this.relevantEvents = null; // the events being dragged 11050 this.receivingContext = null; 11051 this.validMutation = null; 11052 this.mutatedRelevantEvents = null; 11053 this.handlePointerDown = (ev) => { 11054 let origTarget = ev.origEvent.target; 11055 let { component, dragging } = this; 11056 let { mirror } = dragging; 11057 let { options } = component.context; 11058 let initialContext = component.context; 11059 this.subjectEl = ev.subjectEl; 11060 let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl); 11061 let eventRange = this.eventRange = subjectSeg.eventRange; 11062 let eventInstanceId = eventRange.instance.instanceId; 11063 this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId); 11064 dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance; 11065 dragging.delay = 11066 // only do a touch delay if touch and this event hasn't been selected yet 11067 (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? 11068 getComponentTouchDelay(component) : 11069 null; 11070 if (options.fixedMirrorParent) { 11071 mirror.parentNode = options.fixedMirrorParent; 11072 } 11073 else { 11074 mirror.parentNode = elementClosest(origTarget, '.fc'); 11075 } 11076 mirror.revertDuration = options.dragRevertDuration; 11077 let isValid = component.isValidSegDownEl(origTarget) && 11078 !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer 11079 dragging.setIgnoreMove(!isValid); 11080 // disable dragging for elements that are resizable (ie, selectable) 11081 // but are not draggable 11082 this.isDragging = isValid && 11083 ev.subjectEl.classList.contains('fc-event-draggable'); 11084 }; 11085 this.handleDragStart = (ev) => { 11086 let initialContext = this.component.context; 11087 let eventRange = this.eventRange; 11088 let eventInstanceId = eventRange.instance.instanceId; 11089 if (ev.isTouch) { 11090 // need to select a different event? 11091 if (eventInstanceId !== this.component.props.eventSelection) { 11092 initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }); 11093 } 11094 } 11095 else { 11096 // if now using mouse, but was previous touch interaction, clear selected event 11097 initialContext.dispatch({ type: 'UNSELECT_EVENT' }); 11098 } 11099 if (this.isDragging) { 11100 initialContext.calendarApi.unselect(ev); // unselect *date* selection 11101 initialContext.emitter.trigger('eventDragStart', { 11102 el: this.subjectEl, 11103 event: new EventImpl(initialContext, eventRange.def, eventRange.instance), 11104 jsEvent: ev.origEvent, 11105 view: initialContext.viewApi, 11106 }); 11107 } 11108 }; 11109 this.handleHitUpdate = (hit, isFinal) => { 11110 if (!this.isDragging) { 11111 return; 11112 } 11113 let relevantEvents = this.relevantEvents; 11114 let initialHit = this.hitDragging.initialHit; 11115 let initialContext = this.component.context; 11116 // states based on new hit 11117 let receivingContext = null; 11118 let mutation = null; 11119 let mutatedRelevantEvents = null; 11120 let isInvalid = false; 11121 let interaction = { 11122 affectedEvents: relevantEvents, 11123 mutatedEvents: createEmptyEventStore(), 11124 isEvent: true, 11125 }; 11126 if (hit) { 11127 receivingContext = hit.context; 11128 let receivingOptions = receivingContext.options; 11129 if (initialContext === receivingContext || 11130 (receivingOptions.editable && receivingOptions.droppable)) { 11131 mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers); 11132 if (mutation) { 11133 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext); 11134 interaction.mutatedEvents = mutatedRelevantEvents; 11135 if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { 11136 isInvalid = true; 11137 mutation = null; 11138 mutatedRelevantEvents = null; 11139 interaction.mutatedEvents = createEmptyEventStore(); 11140 } 11141 } 11142 } 11143 else { 11144 receivingContext = null; 11145 } 11146 } 11147 this.displayDrag(receivingContext, interaction); 11148 if (!isInvalid) { 11149 enableCursor(); 11150 } 11151 else { 11152 disableCursor(); 11153 } 11154 if (!isFinal) { 11155 if (initialContext === receivingContext && // TODO: write test for this 11156 isHitsEqual(initialHit, hit)) { 11157 mutation = null; 11158 } 11159 this.dragging.setMirrorNeedsRevert(!mutation); 11160 // render the mirror if no already-rendered mirror 11161 // TODO: wish we could somehow wait for dispatch to guarantee render 11162 this.dragging.setMirrorIsVisible(!hit || !this.subjectEl.getRootNode().querySelector('.fc-event-mirror')); 11163 // assign states based on new hit 11164 this.receivingContext = receivingContext; 11165 this.validMutation = mutation; 11166 this.mutatedRelevantEvents = mutatedRelevantEvents; 11167 } 11168 }; 11169 this.handlePointerUp = () => { 11170 if (!this.isDragging) { 11171 this.cleanup(); // because handleDragEnd won't fire 11172 } 11173 }; 11174 this.handleDragEnd = (ev) => { 11175 if (this.isDragging) { 11176 let initialContext = this.component.context; 11177 let initialView = initialContext.viewApi; 11178 let { receivingContext, validMutation } = this; 11179 let eventDef = this.eventRange.def; 11180 let eventInstance = this.eventRange.instance; 11181 let eventApi = new EventImpl(initialContext, eventDef, eventInstance); 11182 let relevantEvents = this.relevantEvents; 11183 let mutatedRelevantEvents = this.mutatedRelevantEvents; 11184 let { finalHit } = this.hitDragging; 11185 this.clearDrag(); // must happen after revert animation 11186 initialContext.emitter.trigger('eventDragStop', { 11187 el: this.subjectEl, 11188 event: eventApi, 11189 jsEvent: ev.origEvent, 11190 view: initialView, 11191 }); 11192 if (validMutation) { 11193 // dropped within same calendar 11194 if (receivingContext === initialContext) { 11195 let updatedEventApi = new EventImpl(initialContext, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); 11196 initialContext.dispatch({ 11197 type: 'MERGE_EVENTS', 11198 eventStore: mutatedRelevantEvents, 11199 }); 11200 let eventChangeArg = { 11201 oldEvent: eventApi, 11202 event: updatedEventApi, 11203 relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), 11204 revert() { 11205 initialContext.dispatch({ 11206 type: 'MERGE_EVENTS', 11207 eventStore: relevantEvents, // the pre-change data 11208 }); 11209 }, 11210 }; 11211 let transformed = {}; 11212 for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { 11213 Object.assign(transformed, transformer(validMutation, initialContext)); 11214 } 11215 initialContext.emitter.trigger('eventDrop', Object.assign(Object.assign(Object.assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView })); 11216 initialContext.emitter.trigger('eventChange', eventChangeArg); 11217 // dropped in different calendar 11218 } 11219 else if (receivingContext) { 11220 let eventRemoveArg = { 11221 event: eventApi, 11222 relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), 11223 revert() { 11224 initialContext.dispatch({ 11225 type: 'MERGE_EVENTS', 11226 eventStore: relevantEvents, 11227 }); 11228 }, 11229 }; 11230 initialContext.emitter.trigger('eventLeave', Object.assign(Object.assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView })); 11231 initialContext.dispatch({ 11232 type: 'REMOVE_EVENTS', 11233 eventStore: relevantEvents, 11234 }); 11235 initialContext.emitter.trigger('eventRemove', eventRemoveArg); 11236 let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId]; 11237 let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId]; 11238 let addedEventApi = new EventImpl(receivingContext, addedEventDef, addedEventInstance); 11239 receivingContext.dispatch({ 11240 type: 'MERGE_EVENTS', 11241 eventStore: mutatedRelevantEvents, 11242 }); 11243 let eventAddArg = { 11244 event: addedEventApi, 11245 relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), 11246 revert() { 11247 receivingContext.dispatch({ 11248 type: 'REMOVE_EVENTS', 11249 eventStore: mutatedRelevantEvents, 11250 }); 11251 }, 11252 }; 11253 receivingContext.emitter.trigger('eventAdd', eventAddArg); 11254 if (ev.isTouch) { 11255 receivingContext.dispatch({ 11256 type: 'SELECT_EVENT', 11257 eventInstanceId: eventInstance.instanceId, 11258 }); 11259 } 11260 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi })); 11261 receivingContext.emitter.trigger('eventReceive', Object.assign(Object.assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi })); 11262 } 11263 } 11264 else { 11265 initialContext.emitter.trigger('_noEventDrop'); 11266 } 11267 } 11268 this.cleanup(); 11269 }; 11270 let { component } = this; 11271 let { options } = component.context; 11272 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 11273 dragging.pointer.selector = EventDragging.SELECTOR; 11274 dragging.touchScrollAllowed = false; 11275 dragging.autoScroller.isEnabled = options.dragScroll; 11276 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore); 11277 hitDragging.useSubjectCenter = settings.useEventCenter; 11278 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 11279 hitDragging.emitter.on('dragstart', this.handleDragStart); 11280 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11281 hitDragging.emitter.on('pointerup', this.handlePointerUp); 11282 hitDragging.emitter.on('dragend', this.handleDragEnd); 11283 } 11284 destroy() { 11285 this.dragging.destroy(); 11286 } 11287 // render a drag state on the next receivingCalendar 11288 displayDrag(nextContext, state) { 11289 let initialContext = this.component.context; 11290 let prevContext = this.receivingContext; 11291 // does the previous calendar need to be cleared? 11292 if (prevContext && prevContext !== nextContext) { 11293 // does the initial calendar need to be cleared? 11294 // if so, don't clear all the way. we still need to to hide the affectedEvents 11295 if (prevContext === initialContext) { 11296 prevContext.dispatch({ 11297 type: 'SET_EVENT_DRAG', 11298 state: { 11299 affectedEvents: state.affectedEvents, 11300 mutatedEvents: createEmptyEventStore(), 11301 isEvent: true, 11302 }, 11303 }); 11304 // completely clear the old calendar if it wasn't the initial 11305 } 11306 else { 11307 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11308 } 11309 } 11310 if (nextContext) { 11311 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); 11312 } 11313 } 11314 clearDrag() { 11315 let initialCalendar = this.component.context; 11316 let { receivingContext } = this; 11317 if (receivingContext) { 11318 receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11319 } 11320 // the initial calendar might have an dummy drag state from displayDrag 11321 if (initialCalendar !== receivingContext) { 11322 initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11323 } 11324 } 11325 cleanup() { 11326 this.subjectSeg = null; 11327 this.isDragging = false; 11328 this.eventRange = null; 11329 this.relevantEvents = null; 11330 this.receivingContext = null; 11331 this.validMutation = null; 11332 this.mutatedRelevantEvents = null; 11333 } 11334 } 11335 // TODO: test this in IE11 11336 // QUESTION: why do we need it on the resizable??? 11337 EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable'; 11338 function computeEventMutation(hit0, hit1, massagers) { 11339 let dateSpan0 = hit0.dateSpan; 11340 let dateSpan1 = hit1.dateSpan; 11341 let date0 = dateSpan0.range.start; 11342 let date1 = dateSpan1.range.start; 11343 let standardProps = {}; 11344 if (dateSpan0.allDay !== dateSpan1.allDay) { 11345 standardProps.allDay = dateSpan1.allDay; 11346 standardProps.hasEnd = hit1.context.options.allDayMaintainDuration; 11347 if (dateSpan1.allDay) { 11348 // means date1 is already start-of-day, 11349 // but date0 needs to be converted 11350 date0 = startOfDay(date0); 11351 } 11352 } 11353 let delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ? 11354 hit0.largeUnit : 11355 null); 11356 if (delta.milliseconds) { // has hours/minutes/seconds 11357 standardProps.allDay = false; 11358 } 11359 let mutation = { 11360 datesDelta: delta, 11361 standardProps, 11362 }; 11363 for (let massager of massagers) { 11364 massager(mutation, hit0, hit1); 11365 } 11366 return mutation; 11367 } 11368 function getComponentTouchDelay(component) { 11369 let { options } = component.context; 11370 let delay = options.eventLongPressDelay; 11371 if (delay == null) { 11372 delay = options.longPressDelay; 11373 } 11374 return delay; 11375 } 11376 11377 class EventResizing extends Interaction { 11378 constructor(settings) { 11379 super(settings); 11380 // internal state 11381 this.draggingSegEl = null; 11382 this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg? 11383 this.eventRange = null; 11384 this.relevantEvents = null; 11385 this.validMutation = null; 11386 this.mutatedRelevantEvents = null; 11387 this.handlePointerDown = (ev) => { 11388 let { component } = this; 11389 let segEl = this.querySegEl(ev); 11390 let seg = getElSeg(segEl); 11391 let eventRange = this.eventRange = seg.eventRange; 11392 this.dragging.minDistance = component.context.options.eventDragMinDistance; 11393 // if touch, need to be working with a selected event 11394 this.dragging.setIgnoreMove(!this.component.isValidSegDownEl(ev.origEvent.target) || 11395 (ev.isTouch && this.component.props.eventSelection !== eventRange.instance.instanceId)); 11396 }; 11397 this.handleDragStart = (ev) => { 11398 let { context } = this.component; 11399 let eventRange = this.eventRange; 11400 this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, this.eventRange.instance.instanceId); 11401 let segEl = this.querySegEl(ev); 11402 this.draggingSegEl = segEl; 11403 this.draggingSeg = getElSeg(segEl); 11404 context.calendarApi.unselect(); 11405 context.emitter.trigger('eventResizeStart', { 11406 el: segEl, 11407 event: new EventImpl(context, eventRange.def, eventRange.instance), 11408 jsEvent: ev.origEvent, 11409 view: context.viewApi, 11410 }); 11411 }; 11412 this.handleHitUpdate = (hit, isFinal, ev) => { 11413 let { context } = this.component; 11414 let relevantEvents = this.relevantEvents; 11415 let initialHit = this.hitDragging.initialHit; 11416 let eventInstance = this.eventRange.instance; 11417 let mutation = null; 11418 let mutatedRelevantEvents = null; 11419 let isInvalid = false; 11420 let interaction = { 11421 affectedEvents: relevantEvents, 11422 mutatedEvents: createEmptyEventStore(), 11423 isEvent: true, 11424 }; 11425 if (hit) { 11426 let disallowed = hit.componentId === initialHit.componentId 11427 && this.isHitComboAllowed 11428 && !this.isHitComboAllowed(initialHit, hit); 11429 if (!disallowed) { 11430 mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range); 11431 } 11432 } 11433 if (mutation) { 11434 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context); 11435 interaction.mutatedEvents = mutatedRelevantEvents; 11436 if (!isInteractionValid(interaction, hit.dateProfile, context)) { 11437 isInvalid = true; 11438 mutation = null; 11439 mutatedRelevantEvents = null; 11440 interaction.mutatedEvents = null; 11441 } 11442 } 11443 if (mutatedRelevantEvents) { 11444 context.dispatch({ 11445 type: 'SET_EVENT_RESIZE', 11446 state: interaction, 11447 }); 11448 } 11449 else { 11450 context.dispatch({ type: 'UNSET_EVENT_RESIZE' }); 11451 } 11452 if (!isInvalid) { 11453 enableCursor(); 11454 } 11455 else { 11456 disableCursor(); 11457 } 11458 if (!isFinal) { 11459 if (mutation && isHitsEqual(initialHit, hit)) { 11460 mutation = null; 11461 } 11462 this.validMutation = mutation; 11463 this.mutatedRelevantEvents = mutatedRelevantEvents; 11464 } 11465 }; 11466 this.handleDragEnd = (ev) => { 11467 let { context } = this.component; 11468 let eventDef = this.eventRange.def; 11469 let eventInstance = this.eventRange.instance; 11470 let eventApi = new EventImpl(context, eventDef, eventInstance); 11471 let relevantEvents = this.relevantEvents; 11472 let mutatedRelevantEvents = this.mutatedRelevantEvents; 11473 context.emitter.trigger('eventResizeStop', { 11474 el: this.draggingSegEl, 11475 event: eventApi, 11476 jsEvent: ev.origEvent, 11477 view: context.viewApi, 11478 }); 11479 if (this.validMutation) { 11480 let updatedEventApi = new EventImpl(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); 11481 context.dispatch({ 11482 type: 'MERGE_EVENTS', 11483 eventStore: mutatedRelevantEvents, 11484 }); 11485 let eventChangeArg = { 11486 oldEvent: eventApi, 11487 event: updatedEventApi, 11488 relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), 11489 revert() { 11490 context.dispatch({ 11491 type: 'MERGE_EVENTS', 11492 eventStore: relevantEvents, // the pre-change events 11493 }); 11494 }, 11495 }; 11496 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 })); 11497 context.emitter.trigger('eventChange', eventChangeArg); 11498 } 11499 else { 11500 context.emitter.trigger('_noEventResize'); 11501 } 11502 // reset all internal state 11503 this.draggingSeg = null; 11504 this.relevantEvents = null; 11505 this.validMutation = null; 11506 // okay to keep eventInstance around. useful to set it in handlePointerDown 11507 }; 11508 let { component } = settings; 11509 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 11510 dragging.pointer.selector = '.fc-event-resizer'; 11511 dragging.touchScrollAllowed = false; 11512 dragging.autoScroller.isEnabled = component.context.options.dragScroll; 11513 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 11514 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 11515 hitDragging.emitter.on('dragstart', this.handleDragStart); 11516 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11517 hitDragging.emitter.on('dragend', this.handleDragEnd); 11518 } 11519 destroy() { 11520 this.dragging.destroy(); 11521 } 11522 querySegEl(ev) { 11523 return elementClosest(ev.subjectEl, '.fc-event'); 11524 } 11525 } 11526 function computeMutation(hit0, hit1, isFromStart, instanceRange) { 11527 let dateEnv = hit0.context.dateEnv; 11528 let date0 = hit0.dateSpan.range.start; 11529 let date1 = hit1.dateSpan.range.start; 11530 let delta = diffDates(date0, date1, dateEnv, hit0.largeUnit); 11531 if (isFromStart) { 11532 if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { 11533 return { startDelta: delta }; 11534 } 11535 } 11536 else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { 11537 return { endDelta: delta }; 11538 } 11539 return null; 11540 } 11541 11542 class UnselectAuto { 11543 constructor(context) { 11544 this.context = context; 11545 this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system 11546 this.matchesCancel = false; 11547 this.matchesEvent = false; 11548 this.onSelect = (selectInfo) => { 11549 if (selectInfo.jsEvent) { 11550 this.isRecentPointerDateSelect = true; 11551 } 11552 }; 11553 this.onDocumentPointerDown = (pev) => { 11554 let unselectCancel = this.context.options.unselectCancel; 11555 let downEl = getEventTargetViaRoot(pev.origEvent); 11556 this.matchesCancel = !!elementClosest(downEl, unselectCancel); 11557 this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event? 11558 }; 11559 this.onDocumentPointerUp = (pev) => { 11560 let { context } = this; 11561 let { documentPointer } = this; 11562 let calendarState = context.getCurrentData(); 11563 // touch-scrolling should never unfocus any type of selection 11564 if (!documentPointer.wasTouchScroll) { 11565 if (calendarState.dateSelection && // an existing date selection? 11566 !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? 11567 ) { 11568 let unselectAuto = context.options.unselectAuto; 11569 if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { 11570 context.calendarApi.unselect(pev); 11571 } 11572 } 11573 if (calendarState.eventSelection && // an existing event selected? 11574 !this.matchesEvent // interaction DIDN'T start on an event 11575 ) { 11576 context.dispatch({ type: 'UNSELECT_EVENT' }); 11577 } 11578 } 11579 this.isRecentPointerDateSelect = false; 11580 }; 11581 let documentPointer = this.documentPointer = new PointerDragging(document); 11582 documentPointer.shouldIgnoreMove = true; 11583 documentPointer.shouldWatchScroll = false; 11584 documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown); 11585 documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); 11586 /* 11587 TODO: better way to know about whether there was a selection with the pointer 11588 */ 11589 context.emitter.on('select', this.onSelect); 11590 } 11591 destroy() { 11592 this.context.emitter.off('select', this.onSelect); 11593 this.documentPointer.destroy(); 11594 } 11595 } 11596 11597 const OPTION_REFINERS$3 = { 11598 fixedMirrorParent: identity, 11599 }; 11600 const LISTENER_REFINERS = { 11601 dateClick: identity, 11602 eventDragStart: identity, 11603 eventDragStop: identity, 11604 eventDrop: identity, 11605 eventResizeStart: identity, 11606 eventResizeStop: identity, 11607 eventResize: identity, 11608 drop: identity, 11609 eventReceive: identity, 11610 eventLeave: identity, 11611 }; 11612 11613 /* 11614 Given an already instantiated draggable object for one-or-more elements, 11615 Interprets any dragging as an attempt to drag an events that lives outside 11616 of a calendar onto a calendar. 11617 */ 11618 class ExternalElementDragging { 11619 constructor(dragging, suppliedDragMeta) { 11620 this.receivingContext = null; 11621 this.droppableEvent = null; // will exist for all drags, even if create:false 11622 this.suppliedDragMeta = null; 11623 this.dragMeta = null; 11624 this.handleDragStart = (ev) => { 11625 this.dragMeta = this.buildDragMeta(ev.subjectEl); 11626 }; 11627 this.handleHitUpdate = (hit, isFinal, ev) => { 11628 let { dragging } = this.hitDragging; 11629 let receivingContext = null; 11630 let droppableEvent = null; 11631 let isInvalid = false; 11632 let interaction = { 11633 affectedEvents: createEmptyEventStore(), 11634 mutatedEvents: createEmptyEventStore(), 11635 isEvent: this.dragMeta.create, 11636 }; 11637 if (hit) { 11638 receivingContext = hit.context; 11639 if (this.canDropElOnCalendar(ev.subjectEl, receivingContext)) { 11640 droppableEvent = computeEventForDateSpan(hit.dateSpan, this.dragMeta, receivingContext); 11641 interaction.mutatedEvents = eventTupleToStore(droppableEvent); 11642 isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext); 11643 if (isInvalid) { 11644 interaction.mutatedEvents = createEmptyEventStore(); 11645 droppableEvent = null; 11646 } 11647 } 11648 } 11649 this.displayDrag(receivingContext, interaction); 11650 // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) 11651 // TODO: wish we could somehow wait for dispatch to guarantee render 11652 dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror')); 11653 if (!isInvalid) { 11654 enableCursor(); 11655 } 11656 else { 11657 disableCursor(); 11658 } 11659 if (!isFinal) { 11660 dragging.setMirrorNeedsRevert(!droppableEvent); 11661 this.receivingContext = receivingContext; 11662 this.droppableEvent = droppableEvent; 11663 } 11664 }; 11665 this.handleDragEnd = (pev) => { 11666 let { receivingContext, droppableEvent } = this; 11667 this.clearDrag(); 11668 if (receivingContext && droppableEvent) { 11669 let finalHit = this.hitDragging.finalHit; 11670 let finalView = finalHit.context.viewApi; 11671 let dragMeta = this.dragMeta; 11672 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView })); 11673 if (dragMeta.create) { 11674 let addingEvents = eventTupleToStore(droppableEvent); 11675 receivingContext.dispatch({ 11676 type: 'MERGE_EVENTS', 11677 eventStore: addingEvents, 11678 }); 11679 if (pev.isTouch) { 11680 receivingContext.dispatch({ 11681 type: 'SELECT_EVENT', 11682 eventInstanceId: droppableEvent.instance.instanceId, 11683 }); 11684 } 11685 // signal that an external event landed 11686 receivingContext.emitter.trigger('eventReceive', { 11687 event: new EventImpl(receivingContext, droppableEvent.def, droppableEvent.instance), 11688 relatedEvents: [], 11689 revert() { 11690 receivingContext.dispatch({ 11691 type: 'REMOVE_EVENTS', 11692 eventStore: addingEvents, 11693 }); 11694 }, 11695 draggedEl: pev.subjectEl, 11696 view: finalView, 11697 }); 11698 } 11699 } 11700 this.receivingContext = null; 11701 this.droppableEvent = null; 11702 }; 11703 let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore); 11704 hitDragging.requireInitial = false; // will start outside of a component 11705 hitDragging.emitter.on('dragstart', this.handleDragStart); 11706 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11707 hitDragging.emitter.on('dragend', this.handleDragEnd); 11708 this.suppliedDragMeta = suppliedDragMeta; 11709 } 11710 buildDragMeta(subjectEl) { 11711 if (typeof this.suppliedDragMeta === 'object') { 11712 return parseDragMeta(this.suppliedDragMeta); 11713 } 11714 if (typeof this.suppliedDragMeta === 'function') { 11715 return parseDragMeta(this.suppliedDragMeta(subjectEl)); 11716 } 11717 return getDragMetaFromEl(subjectEl); 11718 } 11719 displayDrag(nextContext, state) { 11720 let prevContext = this.receivingContext; 11721 if (prevContext && prevContext !== nextContext) { 11722 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11723 } 11724 if (nextContext) { 11725 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); 11726 } 11727 } 11728 clearDrag() { 11729 if (this.receivingContext) { 11730 this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11731 } 11732 } 11733 canDropElOnCalendar(el, receivingContext) { 11734 let dropAccept = receivingContext.options.dropAccept; 11735 if (typeof dropAccept === 'function') { 11736 return dropAccept.call(receivingContext.calendarApi, el); 11737 } 11738 if (typeof dropAccept === 'string' && dropAccept) { 11739 return Boolean(elementMatches(el, dropAccept)); 11740 } 11741 return true; 11742 } 11743 } 11744 // Utils for computing event store from the DragMeta 11745 // ---------------------------------------------------------------------------------------------------- 11746 function computeEventForDateSpan(dateSpan, dragMeta, context) { 11747 let defProps = Object.assign({}, dragMeta.leftoverProps); 11748 for (let transform of context.pluginHooks.externalDefTransforms) { 11749 Object.assign(defProps, transform(dateSpan, dragMeta)); 11750 } 11751 let { refined, extra } = refineEventDef(defProps, context); 11752 let def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd 11753 context); 11754 let start = dateSpan.range.start; 11755 // only rely on time info if drop zone is all-day, 11756 // otherwise, we already know the time 11757 if (dateSpan.allDay && dragMeta.startTime) { 11758 start = context.dateEnv.add(start, dragMeta.startTime); 11759 } 11760 let end = dragMeta.duration ? 11761 context.dateEnv.add(start, dragMeta.duration) : 11762 getDefaultEventEnd(dateSpan.allDay, start, context); 11763 let instance = createEventInstance(def.defId, { start, end }); 11764 return { def, instance }; 11765 } 11766 // Utils for extracting data from element 11767 // ---------------------------------------------------------------------------------------------------- 11768 function getDragMetaFromEl(el) { 11769 let str = getEmbeddedElData(el, 'event'); 11770 let obj = str ? 11771 JSON.parse(str) : 11772 { create: false }; // if no embedded data, assume no event creation 11773 return parseDragMeta(obj); 11774 } 11775 config.dataAttrPrefix = ''; 11776 function getEmbeddedElData(el, name) { 11777 let prefix = config.dataAttrPrefix; 11778 let prefixedName = (prefix ? prefix + '-' : '') + name; 11779 return el.getAttribute('data-' + prefixedName) || ''; 11780 } 11781 11782 /* 11783 Makes an element (that is *external* to any calendar) draggable. 11784 Can pass in data that determines how an event will be created when dropped onto a calendar. 11785 Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. 11786 */ 11787 class ExternalDraggable { 11788 constructor(el, settings = {}) { 11789 this.handlePointerDown = (ev) => { 11790 let { dragging } = this; 11791 let { minDistance, longPressDelay } = this.settings; 11792 dragging.minDistance = 11793 minDistance != null ? 11794 minDistance : 11795 (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance); 11796 dragging.delay = 11797 ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv 11798 (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : 11799 0; 11800 }; 11801 this.handleDragStart = (ev) => { 11802 if (ev.isTouch && 11803 this.dragging.delay && 11804 ev.subjectEl.classList.contains('fc-event')) { 11805 this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected'); 11806 } 11807 }; 11808 this.settings = settings; 11809 let dragging = this.dragging = new FeaturefulElementDragging(el); 11810 dragging.touchScrollAllowed = false;
vendor: 6,348 bytes, lines 11811-11963
11811 if (settings.itemSelector != null) { 11812 dragging.pointer.selector = settings.itemSelector; 11813 } 11814 if (settings.appendTo != null) { 11815 dragging.mirror.parentNode = settings.appendTo; // TODO: write tests 11816 } 11817 dragging.emitter.on('pointerdown', this.handlePointerDown); 11818 dragging.emitter.on('dragstart', this.handleDragStart); 11819 new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new 11820 } 11821 destroy() { 11822 this.dragging.destroy(); 11823 } 11824 } 11825 11826 /* 11827 Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. 11828 The third-party system is responsible for drawing the visuals effects of the drag. 11829 This class simply monitors for pointer movements and fires events. 11830 It also has the ability to hide the moving element (the "mirror") during the drag. 11831 */ 11832 class InferredElementDragging extends ElementDragging { 11833 constructor(containerEl) { 11834 super(containerEl); 11835 this.shouldIgnoreMove = false; 11836 this.mirrorSelector = ''; 11837 this.currentMirrorEl = null; 11838 this.handlePointerDown = (ev) => { 11839 this.emitter.trigger('pointerdown', ev); 11840 if (!this.shouldIgnoreMove) { 11841 // fire dragstart right away. does not support delay or min-distance 11842 this.emitter.trigger('dragstart', ev); 11843 } 11844 }; 11845 this.handlePointerMove = (ev) => { 11846 if (!this.shouldIgnoreMove) { 11847 this.emitter.trigger('dragmove', ev); 11848 } 11849 }; 11850 this.handlePointerUp = (ev) => { 11851 this.emitter.trigger('pointerup', ev); 11852 if (!this.shouldIgnoreMove) { 11853 // fire dragend right away. does not support a revert animation 11854 this.emitter.trigger('dragend', ev); 11855 } 11856 }; 11857 let pointer = this.pointer = new PointerDragging(containerEl); 11858 pointer.emitter.on('pointerdown', this.handlePointerDown); 11859 pointer.emitter.on('pointermove', this.handlePointerMove); 11860 pointer.emitter.on('pointerup', this.handlePointerUp); 11861 } 11862 destroy() { 11863 this.pointer.destroy(); 11864 } 11865 setIgnoreMove(bool) { 11866 this.shouldIgnoreMove = bool; 11867 } 11868 setMirrorIsVisible(bool) { 11869 if (bool) { 11870 // restore a previously hidden element. 11871 // use the reference in case the selector class has already been removed. 11872 if (this.currentMirrorEl) { 11873 this.currentMirrorEl.style.visibility = ''; 11874 this.currentMirrorEl = null; 11875 } 11876 } 11877 else { 11878 let mirrorEl = this.mirrorSelector 11879 // TODO: somehow query FullCalendars WITHIN shadow-roots 11880 ? document.querySelector(this.mirrorSelector) 11881 : null; 11882 if (mirrorEl) { 11883 this.currentMirrorEl = mirrorEl; 11884 mirrorEl.style.visibility = 'hidden'; 11885 } 11886 } 11887 } 11888 } 11889 11890 /* 11891 Bridges third-party drag-n-drop systems with FullCalendar. 11892 Must be instantiated and destroyed by caller. 11893 */ 11894 class ThirdPartyDraggable { 11895 constructor(containerOrSettings, settings) { 11896 let containerEl = document; 11897 if ( 11898 // wish we could just test instanceof EventTarget, but doesn't work in IE11 11899 containerOrSettings === document || 11900 containerOrSettings instanceof Element) { 11901 containerEl = containerOrSettings; 11902 settings = settings || {}; 11903 } 11904 else { 11905 settings = (containerOrSettings || {}); 11906 } 11907 let dragging = this.dragging = new InferredElementDragging(containerEl); 11908 if (typeof settings.itemSelector === 'string') { 11909 dragging.pointer.selector = settings.itemSelector; 11910 } 11911 else if (containerEl === document) { 11912 dragging.pointer.selector = '[data-event]'; 11913 } 11914 if (typeof settings.mirrorSelector === 'string') { 11915 dragging.mirrorSelector = settings.mirrorSelector; 11916 } 11917 new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new 11918 } 11919 destroy() { 11920 this.dragging.destroy(); 11921 } 11922 } 11923 11924 var index$4 = createPlugin({ 11925 name: '@fullcalendar/interaction', 11926 componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], 11927 calendarInteractions: [UnselectAuto], 11928 elementDraggingImpl: FeaturefulElementDragging, 11929 optionRefiners: OPTION_REFINERS$3, 11930 listenerRefiners: LISTENER_REFINERS, 11931 }); 11932 11933 /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. 11934 ----------------------------------------------------------------------------------------------------------------------*/ 11935 // It is a manager for a Table subcomponent, which does most of the heavy lifting. 11936 // It is responsible for managing width/height. 11937 class TableView extends DateComponent { 11938 constructor() { 11939 super(...arguments); 11940 this.headerElRef = d(); 11941 } 11942 renderSimpleLayout(headerRowContent, bodyContent) { 11943 let { props, context } = this; 11944 let sections = []; 11945 let stickyHeaderDates = getStickyHeaderDates(context.options); 11946 if (headerRowContent) { 11947 sections.push({ 11948 type: 'header', 11949 key: 'header', 11950 isSticky: stickyHeaderDates, 11951 chunk: { 11952 elRef: this.headerElRef, 11953 tableClassName: 'fc-col-header', 11954 rowContent: headerRowContent, 11955 }, 11956 }); 11957 } 11958 sections.push({ 11959 type: 'body', 11960 key: 'body', 11961 liquid: true, 11962 chunk: { content: bodyContent }, 11963 });
vendor: 28,031 bytes, lines 11964-12511
11964 return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec }, 11965 y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); 11966 } 11967 renderHScrollLayout(headerRowContent, bodyContent, colCnt, dayMinWidth) { 11968 let ScrollGrid = this.context.pluginHooks.scrollGridImpl; 11969 if (!ScrollGrid) { 11970 throw new Error('No ScrollGrid implementation'); 11971 } 11972 let { props, context } = this; 11973 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); 11974 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); 11975 let sections = []; 11976 if (headerRowContent) { 11977 sections.push({ 11978 type: 'header', 11979 key: 'header', 11980 isSticky: stickyHeaderDates, 11981 chunks: [{ 11982 key: 'main', 11983 elRef: this.headerElRef, 11984 tableClassName: 'fc-col-header', 11985 rowContent: headerRowContent, 11986 }], 11987 }); 11988 } 11989 sections.push({ 11990 type: 'body', 11991 key: 'body', 11992 liquid: true, 11993 chunks: [{ 11994 key: 'main', 11995 content: bodyContent, 11996 }], 11997 }); 11998 if (stickyFooterScrollbar) { 11999 sections.push({ 12000 type: 'footer', 12001 key: 'footer', 12002 isSticky: true, 12003 chunks: [{ 12004 key: 'main', 12005 content: renderScrollShim, 12006 }], 12007 }); 12008 } 12009 return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec }, 12010 y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); 12011 } 12012 } 12013 12014 function splitSegsByRow(segs, rowCnt) { 12015 let byRow = []; 12016 for (let i = 0; i < rowCnt; i += 1) { 12017 byRow[i] = []; 12018 } 12019 for (let seg of segs) { 12020 byRow[seg.row].push(seg); 12021 } 12022 return byRow; 12023 } 12024 function splitSegsByFirstCol(segs, colCnt) { 12025 let byCol = []; 12026 for (let i = 0; i < colCnt; i += 1) { 12027 byCol[i] = []; 12028 } 12029 for (let seg of segs) { 12030 byCol[seg.firstCol].push(seg); 12031 } 12032 return byCol; 12033 } 12034 function splitInteractionByRow(ui, rowCnt) { 12035 let byRow = []; 12036 if (!ui) { 12037 for (let i = 0; i < rowCnt; i += 1) { 12038 byRow[i] = null; 12039 } 12040 } 12041 else { 12042 for (let i = 0; i < rowCnt; i += 1) { 12043 byRow[i] = { 12044 affectedInstances: ui.affectedInstances, 12045 isEvent: ui.isEvent, 12046 segs: [], 12047 }; 12048 } 12049 for (let seg of ui.segs) { 12050 byRow[seg.row].segs.push(seg); 12051 } 12052 } 12053 return byRow; 12054 } 12055 12056 const DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({ 12057 hour: 'numeric', 12058 minute: '2-digit', 12059 omitZeroMinute: true, 12060 meridiem: 'narrow', 12061 }); 12062 function hasListItemDisplay(seg) { 12063 let { display } = seg.eventRange.ui; 12064 return display === 'list-item' || (display === 'auto' && 12065 !seg.eventRange.def.allDay && 12066 seg.firstCol === seg.lastCol && // can't be multi-day 12067 seg.isStart && // " 12068 seg.isEnd // " 12069 ); 12070 } 12071 12072 class TableBlockEvent extends BaseComponent { 12073 render() { 12074 let { props } = this; 12075 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 }))); 12076 } 12077 } 12078 12079 class TableListItemEvent extends BaseComponent { 12080 render() { 12081 let { props, context } = this; 12082 let { options } = context; 12083 let { seg } = props; 12084 let timeFormat = options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT; 12085 let timeText = buildSegTimeText(seg, timeFormat, context, true, props.defaultDisplayEventEnd); 12086 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 }))); 12087 } 12088 } 12089 function renderInnerContent$2(renderProps) { 12090 return (y(_, null, 12091 y("div", { className: "fc-daygrid-event-dot", style: { borderColor: renderProps.borderColor || renderProps.backgroundColor } }), 12092 renderProps.timeText && (y("div", { className: "fc-event-time" }, renderProps.timeText)), 12093 y("div", { className: "fc-event-title" }, renderProps.event.title || y(_, null, "\u00A0")))); 12094 } 12095 12096 class TableCellMoreLink extends BaseComponent { 12097 constructor() { 12098 super(...arguments); 12099 this.compileSegs = memoize(compileSegs); 12100 } 12101 render() { 12102 let { props } = this; 12103 let { allSegs, invisibleSegs } = this.compileSegs(props.singlePlacements); 12104 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: () => { 12105 let isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) || 12106 (props.eventResize ? props.eventResize.affectedInstances : null) || 12107 {}; 12108 return (y(_, null, allSegs.map((seg) => { 12109 let instanceId = seg.eventRange.instance.instanceId; 12110 return (y("div", { className: "fc-daygrid-event-harness", key: instanceId, style: { 12111 visibility: isForcedInvisible[instanceId] ? 'hidden' : '', 12112 } }, 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)))))); 12113 }))); 12114 } })); 12115 } 12116 } 12117 function compileSegs(singlePlacements) { 12118 let allSegs = []; 12119 let invisibleSegs = []; 12120 for (let placement of singlePlacements) { 12121 allSegs.push(placement.seg); 12122 if (!placement.isVisible) { 12123 invisibleSegs.push(placement.seg); 12124 } 12125 } 12126 return { allSegs, invisibleSegs }; 12127 } 12128 12129 const DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' }); 12130 class TableCell extends DateComponent { 12131 constructor() { 12132 super(...arguments); 12133 this.rootElRef = d(); 12134 this.state = { 12135 dayNumberId: getUniqueDomId(), 12136 }; 12137 this.handleRootEl = (el) => { 12138 setRef(this.rootElRef, el); 12139 setRef(this.props.elRef, el); 12140 }; 12141 } 12142 render() { 12143 let { context, props, state, rootElRef } = this; 12144 let { options, dateEnv } = context; 12145 let { date, dateProfile } = props; 12146 // TODO: memoize this? 12147 const isMonthStart = props.showDayNumber && 12148 shouldDisplayMonthStart(date, dateProfile.currentRange, dateEnv); 12149 return (y(DayCellContainer, { elTag: "td", elRef: this.handleRootEl, elClasses: [ 12150 'fc-daygrid-day', 12151 ...(props.extraClassNames || []), 12152 ], 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 } }, 12153 props.showWeekNumber && (y(WeekNumberContainer, { elTag: "a", elClasses: ['fc-daygrid-week-number'], elAttrs: buildNavLinkAttrs(context, date, 'week'), date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 })), 12154 !renderProps.isDisabled && 12155 (props.showDayNumber || hasCustomDayCellContent(options) || props.forceDayTop) ? (y("div", { className: "fc-daygrid-day-top" }, 12156 y(InnerContent, { elTag: "a", elClasses: [ 12157 'fc-daygrid-day-number', 12158 isMonthStart && 'fc-daygrid-month-start', 12159 ], elAttrs: Object.assign(Object.assign({}, buildNavLinkAttrs(context, date)), { id: state.dayNumberId }) }))) : props.showDayNumber ? ( 12160 // for creating correct amount of space (see issue #7162) 12161 y("div", { className: "fc-daygrid-day-top", style: { visibility: 'hidden' } }, 12162 y("a", { className: "fc-daygrid-day-number" }, "\u00A0"))) : undefined, 12163 y("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef }, 12164 props.fgContent, 12165 y("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } }, 12166 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 }))), 12167 y("div", { className: "fc-daygrid-day-bg" }, props.bgContent))))); 12168 } 12169 } 12170 function renderTopInner(props) { 12171 return props.dayNumberText || y(_, null, "\u00A0"); 12172 } 12173 function shouldDisplayMonthStart(date, currentRange, dateEnv) { 12174 const { start: currentStart, end: currentEnd } = currentRange; 12175 const currentEndIncl = addMs(currentEnd, -1); 12176 const currentFirstYear = dateEnv.getYear(currentStart); 12177 const currentFirstMonth = dateEnv.getMonth(currentStart); 12178 const currentLastYear = dateEnv.getYear(currentEndIncl); 12179 const currentLastMonth = dateEnv.getMonth(currentEndIncl); 12180 // spans more than one month? 12181 return !(currentFirstYear === currentLastYear && currentFirstMonth === currentLastMonth) && 12182 Boolean( 12183 // first date in current view? 12184 date.valueOf() === currentStart.valueOf() || 12185 // a month-start that's within the current range? 12186 (dateEnv.getDay(date) === 1 && date.valueOf() < currentEnd.valueOf())); 12187 } 12188 12189 function generateSegKey(seg) { 12190 return seg.eventRange.instance.instanceId + ':' + seg.firstCol; 12191 } 12192 function generateSegUid(seg) { 12193 return generateSegKey(seg) + ':' + seg.lastCol; 12194 } 12195 function computeFgSegPlacement(segs, // assumed already sorted 12196 dayMaxEvents, dayMaxEventRows, strictOrder, segHeights, maxContentHeight, cells) { 12197 let hierarchy = new DayGridSegHierarchy((segEntry) => { 12198 // TODO: more DRY with generateSegUid 12199 let segUid = segs[segEntry.index].eventRange.instance.instanceId + 12200 ':' + segEntry.span.start + 12201 ':' + (segEntry.span.end - 1); 12202 return segHeights[segUid]; 12203 }); 12204 hierarchy.allowReslicing = true; 12205 hierarchy.strictOrder = strictOrder; 12206 if (dayMaxEvents === true || dayMaxEventRows === true) { 12207 hierarchy.maxCoord = maxContentHeight; 12208 hierarchy.hiddenConsumes = true; 12209 } 12210 else if (typeof dayMaxEvents === 'number') { 12211 hierarchy.maxStackCnt = dayMaxEvents; 12212 } 12213 else if (typeof dayMaxEventRows === 'number') { 12214 hierarchy.maxStackCnt = dayMaxEventRows; 12215 hierarchy.hiddenConsumes = true; 12216 } 12217 // create segInputs only for segs with known heights 12218 let segInputs = []; 12219 let unknownHeightSegs = []; 12220 for (let i = 0; i < segs.length; i += 1) { 12221 let seg = segs[i]; 12222 let segUid = generateSegUid(seg); 12223 let eventHeight = segHeights[segUid]; 12224 if (eventHeight != null) { 12225 segInputs.push({ 12226 index: i, 12227 span: { 12228 start: seg.firstCol, 12229 end: seg.lastCol + 1, 12230 }, 12231 }); 12232 } 12233 else { 12234 unknownHeightSegs.push(seg); 12235 } 12236 } 12237 let hiddenEntries = hierarchy.addSegs(segInputs); 12238 let segRects = hierarchy.toRects(); 12239 let { singleColPlacements, multiColPlacements, leftoverMargins } = placeRects(segRects, segs, cells); 12240 let moreCnts = []; 12241 let moreMarginTops = []; 12242 // add segs with unknown heights 12243 for (let seg of unknownHeightSegs) { 12244 multiColPlacements[seg.firstCol].push({ 12245 seg, 12246 isVisible: false, 12247 isAbsolute: true, 12248 absoluteTop: 0, 12249 marginTop: 0, 12250 }); 12251 for (let col = seg.firstCol; col <= seg.lastCol; col += 1) { 12252 singleColPlacements[col].push({ 12253 seg: resliceSeg(seg, col, col + 1, cells), 12254 isVisible: false, 12255 isAbsolute: false, 12256 absoluteTop: 0, 12257 marginTop: 0, 12258 }); 12259 } 12260 } 12261 // add the hidden entries 12262 for (let col = 0; col < cells.length; col += 1) { 12263 moreCnts.push(0); 12264 } 12265 for (let hiddenEntry of hiddenEntries) { 12266 let seg = segs[hiddenEntry.index]; 12267 let hiddenSpan = hiddenEntry.span; 12268 multiColPlacements[hiddenSpan.start].push({ 12269 seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells), 12270 isVisible: false, 12271 isAbsolute: true, 12272 absoluteTop: 0, 12273 marginTop: 0, 12274 }); 12275 for (let col = hiddenSpan.start; col < hiddenSpan.end; col += 1) { 12276 moreCnts[col] += 1; 12277 singleColPlacements[col].push({ 12278 seg: resliceSeg(seg, col, col + 1, cells), 12279 isVisible: false, 12280 isAbsolute: false, 12281 absoluteTop: 0, 12282 marginTop: 0, 12283 }); 12284 } 12285 } 12286 // deal with leftover margins 12287 for (let col = 0; col < cells.length; col += 1) { 12288 moreMarginTops.push(leftoverMargins[col]); 12289 } 12290 return { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops }; 12291 } 12292 // rects ordered by top coord, then left 12293 function placeRects(allRects, segs, cells) { 12294 let rectsByEachCol = groupRectsByEachCol(allRects, cells.length); 12295 let singleColPlacements = []; 12296 let multiColPlacements = []; 12297 let leftoverMargins = []; 12298 for (let col = 0; col < cells.length; col += 1) { 12299 let rects = rectsByEachCol[col]; 12300 // compute all static segs in singlePlacements 12301 let singlePlacements = []; 12302 let currentHeight = 0; 12303 let currentMarginTop = 0; 12304 for (let rect of rects) { 12305 let seg = segs[rect.index]; 12306 singlePlacements.push({ 12307 seg: resliceSeg(seg, col, col + 1, cells), 12308 isVisible: true, 12309 isAbsolute: false, 12310 absoluteTop: rect.levelCoord, 12311 marginTop: rect.levelCoord - currentHeight, 12312 }); 12313 currentHeight = rect.levelCoord + rect.thickness; 12314 } 12315 // compute mixed static/absolute segs in multiPlacements 12316 let multiPlacements = []; 12317 currentHeight = 0; 12318 currentMarginTop = 0; 12319 for (let rect of rects) { 12320 let seg = segs[rect.index]; 12321 let isAbsolute = rect.span.end - rect.span.start > 1; // multi-column? 12322 let isFirstCol = rect.span.start === col; 12323 currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg 12324 currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg 12325 if (isAbsolute) { 12326 currentMarginTop += rect.thickness; 12327 if (isFirstCol) { 12328 multiPlacements.push({ 12329 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), 12330 isVisible: true, 12331 isAbsolute: true, 12332 absoluteTop: rect.levelCoord, 12333 marginTop: 0, 12334 }); 12335 } 12336 } 12337 else if (isFirstCol) { 12338 multiPlacements.push({ 12339 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), 12340 isVisible: true, 12341 isAbsolute: false, 12342 absoluteTop: rect.levelCoord, 12343 marginTop: currentMarginTop, // claim the margin 12344 }); 12345 currentMarginTop = 0; 12346 } 12347 } 12348 singleColPlacements.push(singlePlacements); 12349 multiColPlacements.push(multiPlacements); 12350 leftoverMargins.push(currentMarginTop); 12351 } 12352 return { singleColPlacements, multiColPlacements, leftoverMargins }; 12353 } 12354 function groupRectsByEachCol(rects, colCnt) { 12355 let rectsByEachCol = []; 12356 for (let col = 0; col < colCnt; col += 1) { 12357 rectsByEachCol.push([]); 12358 } 12359 for (let rect of rects) { 12360 for (let col = rect.span.start; col < rect.span.end; col += 1) { 12361 rectsByEachCol[col].push(rect); 12362 } 12363 } 12364 return rectsByEachCol; 12365 } 12366 function resliceSeg(seg, spanStart, spanEnd, cells) { 12367 if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) { 12368 return seg; 12369 } 12370 let eventRange = seg.eventRange; 12371 let origRange = eventRange.range; 12372 let slicedRange = intersectRanges(origRange, { 12373 start: cells[spanStart].date, 12374 end: addDays(cells[spanEnd - 1].date, 1), 12375 }); 12376 return Object.assign(Object.assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: { 12377 def: eventRange.def, 12378 ui: Object.assign(Object.assign({}, eventRange.ui), { durationEditable: false }), 12379 instance: eventRange.instance, 12380 range: slicedRange, 12381 }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() }); 12382 } 12383 class DayGridSegHierarchy extends SegHierarchy { 12384 constructor() { 12385 super(...arguments); 12386 // config 12387 this.hiddenConsumes = false; 12388 // allows us to keep hidden entries in the hierarchy so they take up space 12389 this.forceHidden = {}; 12390 } 12391 addSegs(segInputs) { 12392 const hiddenSegs = super.addSegs(segInputs); 12393 const { entriesByLevel } = this; 12394 const excludeHidden = (entry) => !this.forceHidden[buildEntryKey(entry)]; 12395 // remove the forced-hidden segs 12396 for (let level = 0; level < entriesByLevel.length; level += 1) { 12397 entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden); 12398 } 12399 return hiddenSegs; 12400 } 12401 handleInvalidInsertion(insertion, entry, hiddenEntries) { 12402 const { entriesByLevel, forceHidden } = this; 12403 const { touchingEntry, touchingLevel, touchingLateral } = insertion; 12404 if (this.hiddenConsumes && touchingEntry) { 12405 const touchingEntryId = buildEntryKey(touchingEntry); 12406 // if not already hidden 12407 if (!forceHidden[touchingEntryId]) { 12408 if (this.allowReslicing) { 12409 const placeholderEntry = Object.assign(Object.assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) }); 12410 const placeholderEntryId = buildEntryKey(placeholderEntry); 12411 forceHidden[placeholderEntryId] = true; 12412 entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder 12413 this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it 12414 } 12415 else { 12416 forceHidden[touchingEntryId] = true; 12417 hiddenEntries.push(touchingEntry); 12418 } 12419 } 12420 } 12421 return super.handleInvalidInsertion(insertion, entry, hiddenEntries); 12422 } 12423 } 12424 12425 class TableRow extends DateComponent { 12426 constructor() { 12427 super(...arguments); 12428 this.cellElRefs = new RefMap(); // the <td> 12429 this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame 12430 this.fgElRefs = new RefMap(); // the fc-daygrid-day-events 12431 this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol" 12432 this.rootElRef = d(); 12433 this.state = { 12434 framePositions: null, 12435 maxContentHeight: null, 12436 segHeights: {}, 12437 }; 12438 this.handleResize = (isForced) => { 12439 if (isForced) { 12440 this.updateSizing(true); // isExternal=true 12441 } 12442 }; 12443 } 12444 render() { 12445 let { props, state, context } = this; 12446 let { options } = context; 12447 let colCnt = props.cells.length; 12448 let businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt); 12449 let bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt); 12450 let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt); 12451 let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt); 12452 let { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops } = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.segHeights, state.maxContentHeight, props.cells); 12453 let isForcedInvisible = // TODO: messy way to compute this 12454 (props.eventDrag && props.eventDrag.affectedInstances) || 12455 (props.eventResize && props.eventResize.affectedInstances) || 12456 {}; 12457 return (y("tr", { ref: this.rootElRef, role: "row" }, 12458 props.renderIntro && props.renderIntro(), 12459 props.cells.map((cell, col) => { 12460 let normalFgNodes = this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible); 12461 let mirrorFgNodes = this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false); 12462 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 12463 y(_, null, 12464 y(_, null, normalFgNodes), 12465 y(_, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys 12466 y(_, null, 12467 this.renderFillSegs(highlightSegsByCol[col], 'highlight'), 12468 this.renderFillSegs(businessHoursByCol[col], 'non-business'), 12469 this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))), minHeight: props.cellMinHeight })); 12470 }))); 12471 } 12472 componentDidMount() { 12473 this.updateSizing(true); 12474 this.context.addResizeHandler(this.handleResize); 12475 } 12476 componentDidUpdate(prevProps, prevState) { 12477 let currentProps = this.props; 12478 this.updateSizing(!isPropsEqual(prevProps, currentProps)); 12479 } 12480 componentWillUnmount() { 12481 this.context.removeResizeHandler(this.handleResize); 12482 } 12483 getHighlightSegs() { 12484 let { props } = this; 12485 if (props.eventDrag && props.eventDrag.segs.length) { // messy check 12486 return props.eventDrag.segs; 12487 } 12488 if (props.eventResize && props.eventResize.segs.length) { // messy check 12489 return props.eventResize.segs; 12490 } 12491 return props.dateSelectionSegs; 12492 } 12493 getMirrorSegs() { 12494 let { props } = this; 12495 if (props.eventResize && props.eventResize.segs.length) { // messy check 12496 return props.eventResize.segs; 12497 } 12498 return []; 12499 } 12500 renderFgSegs(col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) { 12501 let { context } = this; 12502 let { eventSelection } = this.props; 12503 let { framePositions } = this.state; 12504 let defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1 12505 let isMirror = isDragging || isResizing || isDateSelecting; 12506 let nodes = []; 12507 if (framePositions) { 12508 for (let placement of segPlacements) { 12509 let { seg } = placement; 12510 let { instanceId } = seg.eventRange.instance; 12511 let isVisible = placement.isVisible && !isForcedInvisible[instanceId];
vendor: 14,062 bytes, lines 12512-12772
12512 let isAbsolute = placement.isAbsolute; 12513 let left = ''; 12514 let right = ''; 12515 if (isAbsolute) { 12516 if (context.isRtl) { 12517 right = 0; 12518 left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol]; 12519 } 12520 else { 12521 left = 0; 12522 right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol]; 12523 } 12524 } 12525 /* 12526 known bug: events that are force to be list-item but span multiple days still take up space in later columns 12527 todo: in print view, for multi-day events, don't display title within non-start/end segs 12528 */ 12529 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: { 12530 visibility: isVisible ? '' : 'hidden', 12531 marginTop: isAbsolute ? '' : placement.marginTop, 12532 top: isAbsolute ? placement.absoluteTop : '', 12533 left, 12534 right, 12535 } }, 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)))))); 12536 } 12537 } 12538 return nodes; 12539 } 12540 renderFillSegs(segs, fillType) { 12541 let { isRtl } = this.context; 12542 let { todayRange } = this.props; 12543 let { framePositions } = this.state; 12544 let nodes = []; 12545 if (framePositions) { 12546 for (let seg of segs) { 12547 let leftRightCss = isRtl ? { 12548 right: 0, 12549 left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol], 12550 } : { 12551 left: 0, 12552 right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol], 12553 }; 12554 nodes.push(y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ? 12555 y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, todayRange))) : 12556 renderFill(fillType))); 12557 } 12558 } 12559 return y(_, {}, ...nodes); 12560 } 12561 updateSizing(isExternalSizingChange) { 12562 let { props, state, frameElRefs } = this; 12563 if (!props.forPrint && 12564 props.clientWidth !== null // positioning ready? 12565 ) { 12566 if (isExternalSizingChange) { 12567 let frameEls = props.cells.map((cell) => frameElRefs.currentMap[cell.key]); 12568 if (frameEls.length) { 12569 let originEl = this.rootElRef.current; 12570 let newPositionCache = new PositionCache(originEl, frameEls, true, // isHorizontal 12571 false); 12572 if (!state.framePositions || !state.framePositions.similarTo(newPositionCache)) { 12573 this.setState({ 12574 framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal 12575 false), 12576 }); 12577 } 12578 } 12579 } 12580 const oldSegHeights = this.state.segHeights; 12581 const newSegHeights = this.querySegHeights(); 12582 const limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true; 12583 this.safeSetState({ 12584 // HACK to prevent oscillations of events being shown/hidden from max-event-rows 12585 // Essentially, once you compute an element's height, never null-out. 12586 // TODO: always display all events, as visibility:hidden? 12587 segHeights: Object.assign(Object.assign({}, oldSegHeights), newSegHeights), 12588 maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null, 12589 }); 12590 } 12591 } 12592 querySegHeights() { 12593 let segElMap = this.segHarnessRefs.currentMap; 12594 let segHeights = {}; 12595 // get the max height amongst instance segs 12596 for (let segUid in segElMap) { 12597 let height = Math.round(segElMap[segUid].getBoundingClientRect().height); 12598 segHeights[segUid] = Math.max(segHeights[segUid] || 0, height); 12599 } 12600 return segHeights; 12601 } 12602 computeMaxContentHeight() { 12603 let firstKey = this.props.cells[0].key; 12604 let cellEl = this.cellElRefs.currentMap[firstKey]; 12605 let fcContainerEl = this.fgElRefs.currentMap[firstKey]; 12606 return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top; 12607 } 12608 getCellEls() { 12609 let elMap = this.cellElRefs.currentMap; 12610 return this.props.cells.map((cell) => elMap[cell.key]); 12611 } 12612 } 12613 TableRow.addStateEquality({ 12614 segHeights: isPropsEqual, 12615 }); 12616 function buildMirrorPlacements(mirrorSegs, colPlacements) { 12617 if (!mirrorSegs.length) { 12618 return []; 12619 } 12620 let topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render? 12621 return mirrorSegs.map((seg) => ({ 12622 seg, 12623 isVisible: true, 12624 isAbsolute: true, 12625 absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId], 12626 marginTop: 0, 12627 })); 12628 } 12629 function buildAbsoluteTopHash(colPlacements) { 12630 let topsByInstanceId = {}; 12631 for (let placements of colPlacements) { 12632 for (let placement of placements) { 12633 topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop; 12634 } 12635 } 12636 return topsByInstanceId; 12637 } 12638 12639 class TableRows extends DateComponent { 12640 constructor() { 12641 super(...arguments); 12642 this.splitBusinessHourSegs = memoize(splitSegsByRow); 12643 this.splitBgEventSegs = memoize(splitSegsByRow); 12644 this.splitFgEventSegs = memoize(splitSegsByRow); 12645 this.splitDateSelectionSegs = memoize(splitSegsByRow); 12646 this.splitEventDrag = memoize(splitInteractionByRow); 12647 this.splitEventResize = memoize(splitInteractionByRow); 12648 this.rowRefs = new RefMap(); 12649 } 12650 render() { 12651 let { props, context } = this; 12652 let rowCnt = props.cells.length; 12653 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt); 12654 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt); 12655 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt); 12656 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt); 12657 let eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt); 12658 let eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt); 12659 // for DayGrid view with many rows, force a min-height on cells so doesn't appear squished 12660 // choose 7 because a month view will have max 6 rows 12661 let cellMinHeight = (rowCnt >= 7 && props.clientWidth) ? 12662 props.clientWidth / context.options.aspectRatio / 6 : 12663 null; 12664 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y(_, null, props.cells.map((cells, row) => (y(TableRow, { ref: this.rowRefs.createRef(row), key: cells.length 12665 ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */ 12666 : row // in case there are no cells (like when resource view is loading) 12667 , 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 }))))))); 12668 } 12669 componentDidMount() { 12670 this.registerInteractiveComponent(); 12671 } 12672 componentDidUpdate() { 12673 // for if started with zero cells 12674 this.registerInteractiveComponent(); 12675 } 12676 registerInteractiveComponent() { 12677 if (!this.rootEl) { 12678 // HACK: need a daygrid wrapper parent to do positioning 12679 // NOTE: a daygrid resource view w/o resources can have zero cells 12680 const firstCellEl = this.rowRefs.currentMap[0].getCellEls()[0]; 12681 const rootEl = firstCellEl ? firstCellEl.closest('.fc-daygrid-body') : null; 12682 if (rootEl) { 12683 this.rootEl = rootEl; 12684 this.context.registerInteractiveComponent(this, { 12685 el: rootEl, 12686 isHitComboAllowed: this.props.isHitComboAllowed, 12687 }); 12688 } 12689 } 12690 } 12691 componentWillUnmount() { 12692 if (this.rootEl) { 12693 this.context.unregisterInteractiveComponent(this); 12694 this.rootEl = null; 12695 } 12696 } 12697 // Hit System 12698 // ---------------------------------------------------------------------------------------------------- 12699 prepareHits() { 12700 this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map((rowObj) => rowObj.getCellEls()[0]), // first cell el in each row. TODO: not optimal 12701 false, true); 12702 this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row 12703 true, // horizontal 12704 false); 12705 } 12706 queryHit(positionLeft, positionTop) { 12707 let { colPositions, rowPositions } = this; 12708 let col = colPositions.leftToIndex(positionLeft); 12709 let row = rowPositions.topToIndex(positionTop); 12710 if (row != null && col != null) { 12711 let cell = this.props.cells[row][col]; 12712 return { 12713 dateProfile: this.props.dateProfile, 12714 dateSpan: Object.assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan), 12715 dayEl: this.getCellEl(row, col), 12716 rect: { 12717 left: colPositions.lefts[col], 12718 right: colPositions.rights[col], 12719 top: rowPositions.tops[row], 12720 bottom: rowPositions.bottoms[row], 12721 }, 12722 layer: 0, 12723 }; 12724 } 12725 return null; 12726 } 12727 getCellEl(row, col) { 12728 return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal 12729 } 12730 getCellRange(row, col) { 12731 let start = this.props.cells[row][col].date; 12732 let end = addDays(start, 1); 12733 return { start, end }; 12734 } 12735 } 12736 function isSegAllDay(seg) { 12737 return seg.eventRange.def.allDay; 12738 } 12739 12740 class Table extends DateComponent { 12741 constructor() { 12742 super(...arguments); 12743 this.elRef = d(); 12744 this.needsScrollReset = false; 12745 } 12746 render() { 12747 let { props } = this; 12748 let { dayMaxEventRows, dayMaxEvents, expandRows } = props; 12749 let limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true; 12750 // if rows can't expand to fill fixed height, can't do balanced-height event limit 12751 // TODO: best place to normalize these options? 12752 if (limitViaBalanced && !expandRows) { 12753 limitViaBalanced = false; 12754 dayMaxEventRows = null; 12755 dayMaxEvents = null; 12756 } 12757 let classNames = [ 12758 'fc-daygrid-body', 12759 limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced', 12760 expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others? 12761 ]; 12762 return (y("div", { ref: this.elRef, className: classNames.join(' '), style: { 12763 // these props are important to give this wrapper correct dimensions for interactions 12764 // TODO: if we set it here, can we avoid giving to inner tables? 12765 width: props.clientWidth, 12766 minWidth: props.tableMinWidth, 12767 } }, 12768 y("table", { role: "presentation", className: "fc-scrollgrid-sync-table", style: { 12769 width: props.clientWidth, 12770 minWidth: props.tableMinWidth, 12771 height: expandRows ? props.clientHeight : '', 12772 } },
vendor: 7,472 bytes, lines 12773-12905
12773 props.colGroupNode, 12774 y("tbody", { role: "presentation" }, 12775 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 }))))); 12776 } 12777 componentDidMount() { 12778 this.requestScrollReset(); 12779 } 12780 componentDidUpdate(prevProps) { 12781 if (prevProps.dateProfile !== this.props.dateProfile) { 12782 this.requestScrollReset(); 12783 } 12784 else { 12785 this.flushScrollReset(); 12786 } 12787 } 12788 requestScrollReset() { 12789 this.needsScrollReset = true; 12790 this.flushScrollReset(); 12791 } 12792 flushScrollReset() { 12793 if (this.needsScrollReset && 12794 this.props.clientWidth // sizes computed? 12795 ) { 12796 const subjectEl = getScrollSubjectEl(this.elRef.current, this.props.dateProfile); 12797 if (subjectEl) { 12798 const originEl = subjectEl.closest('.fc-daygrid-body'); 12799 const scrollEl = originEl.closest('.fc-scroller'); 12800 const scrollTop = subjectEl.getBoundingClientRect().top - 12801 originEl.getBoundingClientRect().top; 12802 scrollEl.scrollTop = scrollTop ? (scrollTop + 1) : 0; // overcome border 12803 } 12804 this.needsScrollReset = false; 12805 } 12806 } 12807 } 12808 function getScrollSubjectEl(containerEl, dateProfile) { 12809 let el; 12810 if (dateProfile.currentRangeUnit.match(/year|month/)) { 12811 el = containerEl.querySelector(`[data-date="${formatIsoMonthStr(dateProfile.currentDate)}-01"]`); 12812 // even if view is month-based, first-of-month might be hidden... 12813 } 12814 if (!el) { 12815 el = containerEl.querySelector(`[data-date="${formatDayString(dateProfile.currentDate)}"]`); 12816 // could still be hidden if an interior-view hidden day 12817 } 12818 return el; 12819 } 12820 12821 class DayTableSlicer extends Slicer { 12822 constructor() { 12823 super(...arguments); 12824 this.forceDayIfListItem = true; 12825 } 12826 sliceRange(dateRange, dayTableModel) { 12827 return dayTableModel.sliceRange(dateRange); 12828 } 12829 } 12830 12831 class DayTable extends DateComponent { 12832 constructor() { 12833 super(...arguments); 12834 this.slicer = new DayTableSlicer(); 12835 this.tableRef = d(); 12836 } 12837 render() { 12838 let { props, context } = this; 12839 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 }))); 12840 } 12841 } 12842 12843 class DayTableView extends TableView { 12844 constructor() { 12845 super(...arguments); 12846 this.buildDayTableModel = memoize(buildDayTableModel); 12847 this.headerRef = d(); 12848 this.tableRef = d(); 12849 // can't override any lifecycle methods from parent 12850 } 12851 render() { 12852 let { options, dateProfileGenerator } = this.context; 12853 let { props } = this; 12854 let dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator); 12855 let headerContent = options.dayHeaders && (y(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 })); 12856 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 })); 12857 return options.dayMinWidth 12858 ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth) 12859 : this.renderSimpleLayout(headerContent, bodyContent); 12860 } 12861 } 12862 function buildDayTableModel(dateProfile, dateProfileGenerator) { 12863 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); 12864 return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit)); 12865 } 12866 12867 class TableDateProfileGenerator extends DateProfileGenerator { 12868 // Computes the date range that will be rendered 12869 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { 12870 let renderRange = super.buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay); 12871 let { props } = this; 12872 return buildDayTableRenderRange({ 12873 currentRange: renderRange, 12874 snapToWeek: /^(year|month)$/.test(currentRangeUnit), 12875 fixedWeekCount: props.fixedWeekCount, 12876 dateEnv: props.dateEnv, 12877 }); 12878 } 12879 } 12880 function buildDayTableRenderRange(props) { 12881 let { dateEnv, currentRange } = props; 12882 let { start, end } = currentRange; 12883 let endOfWeek; 12884 // year and month views should be aligned with weeks. this is already done for week 12885 if (props.snapToWeek) { 12886 start = dateEnv.startOfWeek(start); 12887 // make end-of-week if not already 12888 endOfWeek = dateEnv.startOfWeek(end); 12889 if (endOfWeek.valueOf() !== end.valueOf()) { 12890 end = addWeeks(endOfWeek, 1); 12891 } 12892 } 12893 // ensure 6 weeks 12894 if (props.fixedWeekCount) { 12895 // TODO: instead of these date-math gymnastics (for multimonth view), 12896 // compute dateprofiles of all months, then use start of first and end of last. 12897 let lastMonthRenderStart = dateEnv.startOfWeek(dateEnv.startOfMonth(addDays(currentRange.end, -1))); 12898 let rowCnt = Math.ceil(// could be partial weeks due to hiddenDays 12899 diffWeeks(lastMonthRenderStart, end)); 12900 end = addWeeks(end, 6 - rowCnt); 12901 } 12902 return { start, end }; 12903 } 12904 12905 var css_248z$3
vendor: 3,531 bytes, line 12905
12905 = ":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}";
12906 injectStyles(css_248z$3); 12907 12908 var index$3 = createPlugin({ 12909 name: '@fullcalendar/daygrid', 12910 initialView: 'dayGridMonth', 12911 views: { 12912 dayGrid: { 12913 component: DayTableView, 12914 dateProfileGeneratorClass: TableDateProfileGenerator, 12915 }, 12916 dayGridDay: { 12917 type: 'dayGrid', 12918 duration: { days: 1 }, 12919 }, 12920 dayGridWeek: { 12921 type: 'dayGrid', 12922 duration: { weeks: 1 }, 12923 }, 12924 dayGridMonth: { 12925 type: 'dayGrid', 12926 duration: { months: 1 }, 12927 fixedWeekCount: true, 12928 }, 12929 dayGridYear: { 12930 type: 'dayGrid', 12931 duration: { years: 1 }, 12932 }, 12933 }, 12934 }); 12935 12936 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-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}"; 12937 injectStyles(css_248z$2); 12938 12939 class AllDaySplitter extends Splitter { 12940 getKeyInfo() { 12941 return { 12942 allDay: {}, 12943 timed: {}, 12944 }; 12945 }
vendor: 58,566 bytes, lines 12946-14070
12946 getKeysForDateSpan(dateSpan) { 12947 if (dateSpan.allDay) { 12948 return ['allDay']; 12949 } 12950 return ['timed']; 12951 } 12952 getKeysForEventDef(eventDef) { 12953 if (!eventDef.allDay) { 12954 return ['timed']; 12955 } 12956 if (hasBgRendering(eventDef)) { 12957 return ['timed', 'allDay']; 12958 } 12959 return ['allDay']; 12960 } 12961 } 12962 12963 const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({ 12964 hour: 'numeric', 12965 minute: '2-digit', 12966 omitZeroMinute: true, 12967 meridiem: 'short', 12968 }); 12969 function TimeColsAxisCell(props) { 12970 let classNames = [ 12971 'fc-timegrid-slot', 12972 'fc-timegrid-slot-label', 12973 props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor', 12974 ]; 12975 return (y(ViewContextType.Consumer, null, (context) => { 12976 if (!props.isLabeled) { 12977 return (y("td", { className: classNames.join(' '), "data-time": props.isoTimeStr })); 12978 } 12979 let { dateEnv, options, viewApi } = context; 12980 let labelFormat = // TODO: fully pre-parse 12981 options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT : 12982 Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) : 12983 createFormatter(options.slotLabelFormat); 12984 let renderProps = { 12985 level: 0, 12986 time: props.time, 12987 date: dateEnv.toDate(props.date), 12988 view: viewApi, 12989 text: dateEnv.format(props.date, labelFormat), 12990 }; 12991 return (y(ContentContainer, { elTag: "td", elClasses: classNames, elAttrs: { 12992 'data-time': props.isoTimeStr, 12993 }, 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" }, 12994 y(InnerContent, { elTag: "div", elClasses: [ 12995 'fc-timegrid-slot-label-cushion', 12996 'fc-scrollgrid-shrink-cushion', 12997 ] }))))); 12998 })); 12999 } 13000 function renderInnerContent$1(props) { 13001 return props.text; 13002 } 13003 13004 class TimeBodyAxis extends BaseComponent { 13005 render() { 13006 return this.props.slatMetas.map((slatMeta) => (y("tr", { key: slatMeta.key }, 13007 y(TimeColsAxisCell, Object.assign({}, slatMeta))))); 13008 } 13009 } 13010 13011 const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }); 13012 const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5; 13013 class TimeColsView extends DateComponent { 13014 constructor() { 13015 super(...arguments); 13016 this.allDaySplitter = new AllDaySplitter(); // for use by subclasses 13017 this.headerElRef = d(); 13018 this.rootElRef = d(); 13019 this.scrollerElRef = d(); 13020 this.state = { 13021 slatCoords: null, 13022 }; 13023 this.handleScrollTopRequest = (scrollTop) => { 13024 let scrollerEl = this.scrollerElRef.current; 13025 if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer 13026 scrollerEl.scrollTop = scrollTop; 13027 } 13028 }; 13029 /* Header Render Methods 13030 ------------------------------------------------------------------------------------------------------------------*/ 13031 this.renderHeadAxis = (rowKey, frameHeight = '') => { 13032 let { options } = this.context; 13033 let { dateProfile } = this.props; 13034 let range = dateProfile.renderRange; 13035 let dayCnt = diffDays(range.start, range.end); 13036 // only do in day views (to avoid doing in week views that dont need it) 13037 let navLinkAttrs = (dayCnt === 1) 13038 ? buildNavLinkAttrs(this.context, range.start, 'week') 13039 : {}; 13040 if (options.weekNumbers && rowKey === 'day') { 13041 return (y(WeekNumberContainer, { elTag: "th", elClasses: [ 13042 'fc-timegrid-axis', 13043 'fc-scrollgrid-shrink', 13044 ], elAttrs: { 13045 'aria-hidden': true, 13046 }, date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, (InnerContent) => (y("div", { className: [ 13047 'fc-timegrid-axis-frame', 13048 'fc-scrollgrid-shrink-frame', 13049 'fc-timegrid-axis-frame-liquid', 13050 ].join(' '), style: { height: frameHeight } }, 13051 y(InnerContent, { elTag: "a", elClasses: [ 13052 'fc-timegrid-axis-cushion', 13053 'fc-scrollgrid-shrink-cushion', 13054 'fc-scrollgrid-sync-inner', 13055 ], elAttrs: navLinkAttrs }))))); 13056 } 13057 return (y("th", { "aria-hidden": true, className: "fc-timegrid-axis" }, 13058 y("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } }))); 13059 }; 13060 /* Table Component Render Methods 13061 ------------------------------------------------------------------------------------------------------------------*/ 13062 // only a one-way height sync. we don't send the axis inner-content height to the DayGrid, 13063 // but DayGrid still needs to have classNames on inner elements in order to measure. 13064 this.renderTableRowAxis = (rowHeight) => { 13065 let { options, viewApi } = this.context; 13066 let renderProps = { 13067 text: options.allDayText, 13068 view: viewApi, 13069 }; 13070 return ( 13071 // TODO: make reusable hook. used in list view too 13072 y(ContentContainer, { elTag: "td", elClasses: [ 13073 'fc-timegrid-axis', 13074 'fc-scrollgrid-shrink', 13075 ], elAttrs: { 13076 'aria-hidden': true, 13077 }, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner$1, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, (InnerContent) => (y("div", { className: [ 13078 'fc-timegrid-axis-frame', 13079 'fc-scrollgrid-shrink-frame', 13080 rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : '', 13081 ].join(' '), style: { height: rowHeight } }, 13082 y(InnerContent, { elTag: "span", elClasses: [ 13083 'fc-timegrid-axis-cushion', 13084 'fc-scrollgrid-shrink-cushion', 13085 'fc-scrollgrid-sync-inner', 13086 ] }))))); 13087 }; 13088 this.handleSlatCoords = (slatCoords) => { 13089 this.setState({ slatCoords }); 13090 }; 13091 } 13092 // rendering 13093 // ---------------------------------------------------------------------------------------------------- 13094 renderSimpleLayout(headerRowContent, allDayContent, timeContent) { 13095 let { context, props } = this; 13096 let sections = []; 13097 let stickyHeaderDates = getStickyHeaderDates(context.options); 13098 if (headerRowContent) { 13099 sections.push({ 13100 type: 'header', 13101 key: 'header', 13102 isSticky: stickyHeaderDates, 13103 chunk: { 13104 elRef: this.headerElRef, 13105 tableClassName: 'fc-col-header', 13106 rowContent: headerRowContent, 13107 }, 13108 }); 13109 } 13110 if (allDayContent) { 13111 sections.push({ 13112 type: 'body', 13113 key: 'all-day', 13114 chunk: { content: allDayContent }, 13115 }); 13116 sections.push({ 13117 type: 'body', 13118 key: 'all-day-divider', 13119 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>? 13120 y("tr", { role: "presentation", className: "fc-scrollgrid-section" }, 13121 y("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), 13122 }); 13123 } 13124 sections.push({ 13125 type: 'body', 13126 key: 'body', 13127 liquid: true, 13128 expandRows: Boolean(context.options.expandRows), 13129 chunk: { 13130 scrollerElRef: this.scrollerElRef, 13131 content: timeContent, 13132 }, 13133 }); 13134 return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec }, 13135 y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); 13136 } 13137 renderHScrollLayout(headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { 13138 let ScrollGrid = this.context.pluginHooks.scrollGridImpl; 13139 if (!ScrollGrid) { 13140 throw new Error('No ScrollGrid implementation'); 13141 } 13142 let { context, props } = this; 13143 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); 13144 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); 13145 let sections = []; 13146 if (headerRowContent) { 13147 sections.push({ 13148 type: 'header', 13149 key: 'header', 13150 isSticky: stickyHeaderDates, 13151 syncRowHeights: true, 13152 chunks: [ 13153 { 13154 key: 'axis', 13155 rowContent: (arg) => (y("tr", { role: "presentation" }, this.renderHeadAxis('day', arg.rowSyncHeights[0]))), 13156 }, 13157 { 13158 key: 'cols', 13159 elRef: this.headerElRef, 13160 tableClassName: 'fc-col-header', 13161 rowContent: headerRowContent, 13162 }, 13163 ], 13164 }); 13165 } 13166 if (allDayContent) { 13167 sections.push({ 13168 type: 'body', 13169 key: 'all-day', 13170 syncRowHeights: true, 13171 chunks: [ 13172 { 13173 key: 'axis', 13174 rowContent: (contentArg) => (y("tr", { role: "presentation" }, this.renderTableRowAxis(contentArg.rowSyncHeights[0]))), 13175 }, 13176 { 13177 key: 'cols', 13178 content: allDayContent, 13179 }, 13180 ], 13181 }); 13182 sections.push({ 13183 key: 'all-day-divider', 13184 type: 'body', 13185 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>? 13186 y("tr", { role: "presentation", className: "fc-scrollgrid-section" }, 13187 y("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), 13188 }); 13189 } 13190 let isNowIndicator = context.options.nowIndicator; 13191 sections.push({ 13192 type: 'body', 13193 key: 'body', 13194 liquid: true, 13195 expandRows: Boolean(context.options.expandRows), 13196 chunks: [ 13197 { 13198 key: 'axis', 13199 content: (arg) => ( 13200 // TODO: make this now-indicator arrow more DRY with TimeColsContent 13201 y("div", { className: "fc-timegrid-axis-chunk" }, 13202 y("table", { "aria-hidden": true, style: { height: arg.expandRows ? arg.clientHeight : '' } }, 13203 arg.tableColGroupNode, 13204 y("tbody", null, 13205 y(TimeBodyAxis, { slatMetas: slatMetas }))), 13206 y("div", { className: "fc-timegrid-now-indicator-container" }, 13207 y(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, (nowDate) => { 13208 let nowIndicatorTop = isNowIndicator && 13209 slatCoords && 13210 slatCoords.safeComputeTop(nowDate); // might return void 13211 if (typeof nowIndicatorTop === 'number') { 13212 return (y(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: nowDate })); 13213 } 13214 return null; 13215 })))), 13216 }, 13217 { 13218 key: 'cols', 13219 scrollerElRef: this.scrollerElRef, 13220 content: timeContent, 13221 }, 13222 ], 13223 }); 13224 if (stickyFooterScrollbar) { 13225 sections.push({ 13226 key: 'footer', 13227 type: 'footer', 13228 isSticky: true, 13229 chunks: [ 13230 { 13231 key: 'axis', 13232 content: renderScrollShim, 13233 }, 13234 { 13235 key: 'cols', 13236 content: renderScrollShim, 13237 }, 13238 ], 13239 }); 13240 } 13241 return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec }, 13242 y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: false, colGroups: [ 13243 { width: 'shrink', cols: [{ width: 'shrink' }] }, 13244 { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, 13245 ], sections: sections }))); 13246 } 13247 /* Dimensions 13248 ------------------------------------------------------------------------------------------------------------------*/ 13249 getAllDayMaxEventProps() { 13250 let { dayMaxEvents, dayMaxEventRows } = this.context.options; 13251 if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? 13252 dayMaxEvents = undefined; 13253 dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number 13254 } 13255 return { dayMaxEvents, dayMaxEventRows }; 13256 } 13257 } 13258 function renderAllDayInner$1(renderProps) { 13259 return renderProps.text; 13260 } 13261 13262 class TimeColsSlatsCoords { 13263 constructor(positions, dateProfile, slotDuration) { 13264 this.positions = positions; 13265 this.dateProfile = dateProfile; 13266 this.slotDuration = slotDuration; 13267 } 13268 safeComputeTop(date) { 13269 let { dateProfile } = this; 13270 if (rangeContainsMarker(dateProfile.currentRange, date)) { 13271 let startOfDayDate = startOfDay(date); 13272 let timeMs = date.valueOf() - startOfDayDate.valueOf(); 13273 if (timeMs >= asRoughMs(dateProfile.slotMinTime) && 13274 timeMs < asRoughMs(dateProfile.slotMaxTime)) { 13275 return this.computeTimeTop(createDuration(timeMs)); 13276 } 13277 } 13278 return null; 13279 } 13280 // Computes the top coordinate, relative to the bounds of the grid, of the given date. 13281 // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. 13282 computeDateTop(when, startOfDayDate) { 13283 if (!startOfDayDate) { 13284 startOfDayDate = startOfDay(when); 13285 } 13286 return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); 13287 } 13288 // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). 13289 // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. 13290 // Eventually allow computation with arbirary slat dates. 13291 computeTimeTop(duration) { 13292 let { positions, dateProfile } = this; 13293 let len = positions.els.length; 13294 // floating-point value of # of slots covered 13295 let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); 13296 let slatIndex; 13297 let slatRemainder; 13298 // compute a floating-point number for how many slats should be progressed through. 13299 // from 0 to number of slats (inclusive) 13300 // constrained because slotMinTime/slotMaxTime might be customized. 13301 slatCoverage = Math.max(0, slatCoverage); 13302 slatCoverage = Math.min(len, slatCoverage); 13303 // an integer index of the furthest whole slat 13304 // from 0 to number slats (*exclusive*, so len-1) 13305 slatIndex = Math.floor(slatCoverage); 13306 slatIndex = Math.min(slatIndex, len - 1); 13307 // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. 13308 // could be 1.0 if slatCoverage is covering *all* the slots 13309 slatRemainder = slatCoverage - slatIndex; 13310 return positions.tops[slatIndex] + 13311 positions.getHeight(slatIndex) * slatRemainder; 13312 } 13313 } 13314 13315 class TimeColsSlatsBody extends BaseComponent { 13316 render() { 13317 let { props, context } = this; 13318 let { options } = context; 13319 let { slatElRefs } = props; 13320 return (y("tbody", null, props.slatMetas.map((slatMeta, i) => { 13321 let renderProps = { 13322 time: slatMeta.time, 13323 date: context.dateEnv.toDate(slatMeta.date), 13324 view: context.viewApi, 13325 }; 13326 return (y("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, 13327 props.axis && (y(TimeColsAxisCell, Object.assign({}, slatMeta))), 13328 y(ContentContainer, { elTag: "td", elClasses: [ 13329 'fc-timegrid-slot', 13330 'fc-timegrid-slot-lane', 13331 !slatMeta.isLabeled && 'fc-timegrid-slot-minor', 13332 ], elAttrs: { 13333 'data-time': slatMeta.isoTimeStr, 13334 }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }))); 13335 }))); 13336 } 13337 } 13338 13339 /* 13340 for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. 13341 */ 13342 class TimeColsSlats extends BaseComponent { 13343 constructor() { 13344 super(...arguments); 13345 this.rootElRef = d(); 13346 this.slatElRefs = new RefMap(); 13347 } 13348 render() { 13349 let { props, context } = this; 13350 return (y("div", { ref: this.rootElRef, className: "fc-timegrid-slots" }, 13351 y("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: { 13352 minWidth: props.tableMinWidth, 13353 width: props.clientWidth, 13354 height: props.minHeight, 13355 } }, 13356 props.tableColGroupNode /* relies on there only being a single <col> for the axis */, 13357 y(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); 13358 } 13359 componentDidMount() { 13360 this.updateSizing(); 13361 } 13362 componentDidUpdate() { 13363 this.updateSizing(); 13364 } 13365 componentWillUnmount() { 13366 if (this.props.onCoords) { 13367 this.props.onCoords(null); 13368 } 13369 } 13370 updateSizing() { 13371 let { context, props } = this; 13372 if (props.onCoords && 13373 props.clientWidth !== null // means sizing has stabilized 13374 ) { 13375 let rootEl = this.rootElRef.current; 13376 if (rootEl.offsetHeight) { // not hidden by css 13377 props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); 13378 } 13379 } 13380 } 13381 } 13382 function collectSlatEls(elMap, slatMetas) { 13383 return slatMetas.map((slatMeta) => elMap[slatMeta.key]); 13384 } 13385 13386 function splitSegsByCol(segs, colCnt) { 13387 let segsByCol = []; 13388 let i; 13389 for (i = 0; i < colCnt; i += 1) { 13390 segsByCol.push([]); 13391 } 13392 if (segs) { 13393 for (i = 0; i < segs.length; i += 1) { 13394 segsByCol[segs[i].col].push(segs[i]); 13395 } 13396 } 13397 return segsByCol; 13398 } 13399 function splitInteractionByCol(ui, colCnt) { 13400 let byRow = []; 13401 if (!ui) { 13402 for (let i = 0; i < colCnt; i += 1) { 13403 byRow[i] = null; 13404 } 13405 } 13406 else { 13407 for (let i = 0; i < colCnt; i += 1) { 13408 byRow[i] = { 13409 affectedInstances: ui.affectedInstances, 13410 isEvent: ui.isEvent, 13411 segs: [], 13412 }; 13413 } 13414 for (let seg of ui.segs) { 13415 byRow[seg.col].segs.push(seg); 13416 } 13417 } 13418 return byRow; 13419 } 13420 13421 class TimeColMoreLink extends BaseComponent { 13422 render() { 13423 let { props } = this; 13424 return (y(MoreLinkContainer, { elClasses: ['fc-timegrid-more-link'], elStyle: { 13425 top: props.top, 13426 bottom: props.bottom, 13427 }, 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'] })))); 13428 } 13429 } 13430 function renderMoreLinkInner(props) { 13431 return props.shortText; 13432 } 13433 13434 // segInputs assumed sorted 13435 function buildPositioning(segInputs, strictOrder, maxStackCnt) { 13436 let hierarchy = new SegHierarchy(); 13437 if (strictOrder != null) { 13438 hierarchy.strictOrder = strictOrder; 13439 } 13440 if (maxStackCnt != null) { 13441 hierarchy.maxStackCnt = maxStackCnt; 13442 } 13443 let hiddenEntries = hierarchy.addSegs(segInputs); 13444 let hiddenGroups = groupIntersectingEntries(hiddenEntries); 13445 let web = buildWeb(hierarchy); 13446 web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 13447 let segRects = webToRects(web); 13448 return { segRects, hiddenGroups }; 13449 } 13450 function buildWeb(hierarchy) { 13451 const { entriesByLevel } = hierarchy; 13452 const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => { 13453 let siblingRange = findNextLevelSegs(hierarchy, level, lateral); 13454 let nextLevelRes = buildNodes(siblingRange, buildNode); 13455 let entry = entriesByLevel[level][lateral]; 13456 return [ 13457 Object.assign(Object.assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), 13458 entry.thickness + nextLevelRes[1], // the pressure builds 13459 ]; 13460 }); 13461 return buildNodes(entriesByLevel.length 13462 ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } 13463 : null, buildNode)[0]; 13464 } 13465 function buildNodes(siblingRange, buildNode) { 13466 if (!siblingRange) { 13467 return [[], 0]; 13468 } 13469 let { level, lateralStart, lateralEnd } = siblingRange; 13470 let lateral = lateralStart; 13471 let pairs = []; 13472 while (lateral < lateralEnd) { 13473 pairs.push(buildNode(level, lateral)); 13474 lateral += 1; 13475 } 13476 pairs.sort(cmpDescPressures); 13477 return [ 13478 pairs.map(extractNode), 13479 pairs[0][1], // first item's pressure 13480 ]; 13481 } 13482 function cmpDescPressures(a, b) { 13483 return b[1] - a[1]; 13484 } 13485 function extractNode(a) { 13486 return a[0]; 13487 } 13488 function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { 13489 let { levelCoords, entriesByLevel } = hierarchy; 13490 let subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; 13491 let afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; 13492 let levelCnt = levelCoords.length; 13493 let level = subjectLevel; 13494 // skip past levels that are too high up 13495 for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) 13496 ; // do nothing 13497 for (; level < levelCnt; level += 1) { 13498 let entries = entriesByLevel[level]; 13499 let entry; 13500 let searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); 13501 let lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one 13502 let lateralEnd = lateralStart; 13503 while ( // loop through entries that horizontally intersect 13504 (entry = entries[lateralEnd]) && // but not past the whole seg list 13505 entry.span.start < subjectEntry.span.end) { 13506 lateralEnd += 1; 13507 } 13508 if (lateralStart < lateralEnd) { 13509 return { level, lateralStart, lateralEnd }; 13510 } 13511 } 13512 return null; 13513 } 13514 function stretchWeb(topLevelNodes, totalThickness) { 13515 const stretchNode = cacheable((node, startCoord, prevThickness) => buildEntryKey(node), (node, startCoord, prevThickness) => { 13516 let { nextLevelNodes, thickness } = node; 13517 let allThickness = thickness + prevThickness; 13518 let thicknessFraction = thickness / allThickness; 13519 let endCoord; 13520 let newChildren = []; 13521 if (!nextLevelNodes.length) { 13522 endCoord = totalThickness; 13523 } 13524 else { 13525 for (let childNode of nextLevelNodes) { 13526 if (endCoord === undefined) { 13527 let res = stretchNode(childNode, startCoord, allThickness); 13528 endCoord = res[0]; 13529 newChildren.push(res[1]); 13530 } 13531 else { 13532 let res = stretchNode(childNode, endCoord, 0); 13533 newChildren.push(res[1]); 13534 } 13535 } 13536 } 13537 let newThickness = (endCoord - startCoord) * thicknessFraction; 13538 return [endCoord - newThickness, Object.assign(Object.assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; 13539 }); 13540 return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]); 13541 } 13542 // not sorted in any particular order 13543 function webToRects(topLevelNodes) { 13544 let rects = []; 13545 const processNode = cacheable((node, levelCoord, stackDepth) => buildEntryKey(node), (node, levelCoord, stackDepth) => { 13546 let rect = Object.assign(Object.assign({}, node), { levelCoord, 13547 stackDepth, stackForward: 0 }); 13548 rects.push(rect); 13549 return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); 13550 }); 13551 function processNodes(nodes, levelCoord, stackDepth) { 13552 let stackForward = 0; 13553 for (let node of nodes) { 13554 stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); 13555 } 13556 return stackForward; 13557 } 13558 processNodes(topLevelNodes, 0, 0); 13559 return rects; // TODO: sort rects by levelCoord to be consistent with toRects? 13560 } 13561 // TODO: move to general util 13562 function cacheable(keyFunc, workFunc) { 13563 const cache = {}; 13564 return (...args) => { 13565 let key = keyFunc(...args); 13566 return (key in cache) 13567 ? cache[key] 13568 : (cache[key] = workFunc(...args)); 13569 }; 13570 } 13571 13572 function computeSegVCoords(segs, colDate, slatCoords = null, eventMinHeight = 0) { 13573 let vcoords = []; 13574 if (slatCoords) { 13575 for (let i = 0; i < segs.length; i += 1) { 13576 let seg = segs[i]; 13577 let spanStart = slatCoords.computeDateTop(seg.start, colDate); 13578 let spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( 13579 slatCoords.computeDateTop(seg.end, colDate)); 13580 vcoords.push({ 13581 start: Math.round(spanStart), 13582 end: Math.round(spanEnd), // 13583 }); 13584 } 13585 } 13586 return vcoords; 13587 } 13588 function computeFgSegPlacements(segs, segVCoords, // might not have for every seg 13589 eventOrderStrict, eventMaxStack) { 13590 let segInputs = []; 13591 let dumbSegs = []; // segs without coords 13592 for (let i = 0; i < segs.length; i += 1) { 13593 let vcoords = segVCoords[i]; 13594 if (vcoords) { 13595 segInputs.push({ 13596 index: i, 13597 thickness: 1, 13598 span: vcoords, 13599 }); 13600 } 13601 else { 13602 dumbSegs.push(segs[i]); 13603 } 13604 } 13605 let { segRects, hiddenGroups } = buildPositioning(segInputs, eventOrderStrict, eventMaxStack); 13606 let segPlacements = []; 13607 for (let segRect of segRects) { 13608 segPlacements.push({ 13609 seg: segs[segRect.index], 13610 rect: segRect, 13611 }); 13612 } 13613 for (let dumbSeg of dumbSegs) { 13614 segPlacements.push({ seg: dumbSeg, rect: null }); 13615 } 13616 return { segPlacements, hiddenGroups }; 13617 } 13618 13619 const DEFAULT_TIME_FORMAT$1 = createFormatter({ 13620 hour: 'numeric', 13621 minute: '2-digit', 13622 meridiem: false, 13623 }); 13624 class TimeColEvent extends BaseComponent { 13625 render() { 13626 return (y(StandardEvent, Object.assign({}, this.props, { elClasses: [ 13627 'fc-timegrid-event', 13628 'fc-v-event', 13629 this.props.isShort && 'fc-timegrid-event-short', 13630 ], defaultTimeFormat: DEFAULT_TIME_FORMAT$1 }))); 13631 } 13632 } 13633 13634 class TimeCol extends BaseComponent { 13635 constructor() { 13636 super(...arguments); 13637 this.sortEventSegs = memoize(sortEventSegs); 13638 } 13639 // TODO: memoize event-placement? 13640 render() { 13641 let { props, context } = this; 13642 let { options } = context; 13643 let isSelectMirror = options.selectMirror; 13644 let mirrorSegs = // yuck 13645 (props.eventDrag && props.eventDrag.segs) || 13646 (props.eventResize && props.eventResize.segs) || 13647 (isSelectMirror && props.dateSelectionSegs) || 13648 []; 13649 let interactionAffectedInstances = // TODO: messy way to compute this 13650 (props.eventDrag && props.eventDrag.affectedInstances) || 13651 (props.eventResize && props.eventResize.affectedInstances) || 13652 {}; 13653 let sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder); 13654 return (y(DayCellContainer, { elTag: "td", elRef: props.elRef, elClasses: [ 13655 'fc-timegrid-col', 13656 ...(props.extraClassNames || []), 13657 ], 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" }, 13658 y("div", { className: "fc-timegrid-col-bg" }, 13659 this.renderFillSegs(props.businessHourSegs, 'non-business'), 13660 this.renderFillSegs(props.bgEventSegs, 'bg-event'), 13661 this.renderFillSegs(props.dateSelectionSegs, 'highlight')), 13662 y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), 13663 y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror), 'mirror')), 13664 y("div", { className: "fc-timegrid-now-indicator-container" }, this.renderNowIndicator(props.nowIndicatorSegs)), 13665 hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-col-misc'] })))))); 13666 } 13667 renderFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) { 13668 let { props } = this; 13669 if (props.forPrint) { 13670 return renderPlainFgSegs(sortedFgSegs, props); 13671 } 13672 return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey); 13673 } 13674 renderPositionedFgSegs(segs, // if not mirror, needs to be sorted 13675 segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) { 13676 let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = this.context.options; 13677 let { date, slatCoords, eventSelection, todayRange, nowDate } = this.props; 13678 let isMirror = isDragging || isResizing || isDateSelecting; 13679 let segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); 13680 let { segPlacements, hiddenGroups } = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack); 13681 return (y(_, null, 13682 this.renderHiddenGroups(hiddenGroups, segs), 13683 segPlacements.map((segPlacement) => { 13684 let { seg, rect } = segPlacement; 13685 let instanceId = seg.eventRange.instance.instanceId; 13686 let isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); 13687 let vStyle = computeSegVStyle(rect && rect.span); 13688 let hStyle = (!isMirror && rect) ? this.computeSegHStyle(rect) : { left: 0, right: 0 }; 13689 let isInset = Boolean(rect) && rect.stackForward > 0; 13690 let isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem 13691 return (y("div", { className: 'fc-timegrid-event-harness' + 13692 (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: forcedKey || instanceId, style: Object.assign(Object.assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, 13693 y(TimeColEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); 13694 }))); 13695 } 13696 // will already have eventMinHeight applied because segInputs already had it 13697 renderHiddenGroups(hiddenGroups, segs) { 13698 let { extraDateSpan, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize } = this.props; 13699 return (y(_, null, hiddenGroups.map((hiddenGroup) => { 13700 let positionCss = computeSegVStyle(hiddenGroup.span); 13701 let hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); 13702 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 })); 13703 }))); 13704 } 13705 renderFillSegs(segs, fillType) { 13706 let { props, context } = this; 13707 let segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated 13708 let children = segVCoords.map((vcoords, i) => { 13709 let seg = segs[i]; 13710 return (y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? 13711 y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : 13712 renderFill(fillType))); 13713 }); 13714 return y(_, null, children); 13715 } 13716 renderNowIndicator(segs) { 13717 let { slatCoords, date } = this.props; 13718 if (!slatCoords) { 13719 return null; 13720 } 13721 return segs.map((seg, i) => (y(NowIndicatorContainer 13722 // key doesn't matter. will only ever be one 13723 , { 13724 // key doesn't matter. will only ever be one 13725 key: i, elClasses: ['fc-timegrid-now-indicator-line'], elStyle: { 13726 top: slatCoords.computeDateTop(seg.start, date), 13727 }, isAxis: false, date: date }))); 13728 } 13729 computeSegHStyle(segHCoords) { 13730 let { isRtl, options } = this.context; 13731 let shouldOverlap = options.slotEventOverlap; 13732 let nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point 13733 let farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point 13734 let left; // amount of space from left edge, a fraction of the total width 13735 let right; // amount of space from right edge, a fraction of the total width 13736 if (shouldOverlap) { 13737 // double the width, but don't go beyond the maximum forward coordinate (1.0) 13738 farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); 13739 } 13740 if (isRtl) { 13741 left = 1 - farCoord; 13742 right = nearCoord; 13743 } 13744 else { 13745 left = nearCoord; 13746 right = 1 - farCoord; 13747 } 13748 let props = { 13749 zIndex: segHCoords.stackDepth + 1, 13750 left: left * 100 + '%', 13751 right: right * 100 + '%', 13752 }; 13753 if (shouldOverlap && !segHCoords.stackForward) { 13754 // add padding to the edge so that forward stacked events don't cover the resizer's icon 13755 props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width 13756 } 13757 return props; 13758 } 13759 } 13760 function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }) { 13761 let hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || 13762 (eventResize ? eventResize.affectedInstances : null) || 13763 {}; 13764 return (y(_, null, sortedFgSegs.map((seg) => { 13765 let instanceId = seg.eventRange.instance.instanceId; 13766 return (y("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, 13767 y(TimeColEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); 13768 }))); 13769 } 13770 function computeSegVStyle(segVCoords) { 13771 if (!segVCoords) { 13772 return { top: '', bottom: '' }; 13773 } 13774 return { 13775 top: segVCoords.start, 13776 bottom: -segVCoords.end, 13777 }; 13778 } 13779 function compileSegsFromEntries(segEntries, allSegs) { 13780 return segEntries.map((segEntry) => allSegs[segEntry.index]); 13781 } 13782 13783 class TimeColsContent extends BaseComponent { 13784 constructor() { 13785 super(...arguments); 13786 this.splitFgEventSegs = memoize(splitSegsByCol); 13787 this.splitBgEventSegs = memoize(splitSegsByCol); 13788 this.splitBusinessHourSegs = memoize(splitSegsByCol); 13789 this.splitNowIndicatorSegs = memoize(splitSegsByCol); 13790 this.splitDateSelectionSegs = memoize(splitSegsByCol); 13791 this.splitEventDrag = memoize(splitInteractionByCol); 13792 this.splitEventResize = memoize(splitInteractionByCol); 13793 this.rootElRef = d(); 13794 this.cellElRefs = new RefMap(); 13795 } 13796 render() { 13797 let { props, context } = this; 13798 let nowIndicatorTop = context.options.nowIndicator && 13799 props.slatCoords && 13800 props.slatCoords.safeComputeTop(props.nowDate); // might return void 13801 let colCnt = props.cells.length; 13802 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); 13803 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); 13804 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); 13805 let nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); 13806 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); 13807 let eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); 13808 let eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); 13809 return (y("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, 13810 y("table", { role: "presentation", style: { 13811 minWidth: props.tableMinWidth, 13812 width: props.clientWidth, 13813 } }, 13814 props.tableColGroupNode, 13815 y("tbody", { role: "presentation" }, 13816 y("tr", { role: "row" }, 13817 props.axis && (y("td", { "aria-hidden": true, className: "fc-timegrid-col fc-timegrid-axis" }, 13818 y("div", { className: "fc-timegrid-col-frame" }, 13819 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 })))))), 13820 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 })))))))); 13821 } 13822 componentDidMount() { 13823 this.updateCoords(); 13824 } 13825 componentDidUpdate() { 13826 this.updateCoords(); 13827 } 13828 updateCoords() { 13829 let { props } = this; 13830 if (props.onColCoords && 13831 props.clientWidth !== null // means sizing has stabilized 13832 ) { 13833 props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal 13834 false)); 13835 } 13836 } 13837 } 13838 function collectCellEls(elMap, cells) { 13839 return cells.map((cell) => elMap[cell.key]); 13840 } 13841 13842 /* A component that renders one or more columns of vertical time slots 13843 ----------------------------------------------------------------------------------------------------------------------*/ 13844 class TimeCols extends DateComponent { 13845 constructor() { 13846 super(...arguments); 13847 this.processSlotOptions = memoize(processSlotOptions); 13848 this.state = { 13849 slatCoords: null, 13850 }; 13851 this.handleRootEl = (el) => { 13852 if (el) { 13853 this.context.registerInteractiveComponent(this, { 13854 el, 13855 isHitComboAllowed: this.props.isHitComboAllowed, 13856 }); 13857 } 13858 else { 13859 this.context.unregisterInteractiveComponent(this); 13860 } 13861 }; 13862 this.handleScrollRequest = (request) => { 13863 let { onScrollTopRequest } = this.props; 13864 let { slatCoords } = this.state; 13865 if (onScrollTopRequest && slatCoords) { 13866 if (request.time) { 13867 let top = slatCoords.computeTimeTop(request.time); 13868 top = Math.ceil(top); // zoom can give weird floating-point values. rather scroll a little bit further 13869 if (top) { 13870 top += 1; // to overcome top border that slots beyond the first have. looks better 13871 } 13872 onScrollTopRequest(top); 13873 } 13874 return true; 13875 } 13876 return false; 13877 }; 13878 this.handleColCoords = (colCoords) => { 13879 this.colCoords = colCoords; 13880 }; 13881 this.handleSlatCoords = (slatCoords) => { 13882 this.setState({ slatCoords }); 13883 if (this.props.onSlatCoords) { 13884 this.props.onSlatCoords(slatCoords); 13885 } 13886 }; 13887 } 13888 render() { 13889 let { props, state } = this; 13890 return (y("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: { 13891 // these props are important to give this wrapper correct dimensions for interactions 13892 // TODO: if we set it here, can we avoid giving to inner tables? 13893 width: props.clientWidth, 13894 minWidth: props.tableMinWidth, 13895 } }, 13896 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 }), 13897 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 }))); 13898 } 13899 componentDidMount() { 13900 this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); 13901 } 13902 componentDidUpdate(prevProps) { 13903 this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); 13904 } 13905 componentWillUnmount() { 13906 this.scrollResponder.detach(); 13907 } 13908 queryHit(positionLeft, positionTop) { 13909 let { dateEnv, options } = this.context; 13910 let { colCoords } = this; 13911 let { dateProfile } = this.props; 13912 let { slatCoords } = this.state; 13913 let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration); 13914 let colIndex = colCoords.leftToIndex(positionLeft); 13915 let slatIndex = slatCoords.positions.topToIndex(positionTop); 13916 if (colIndex != null && slatIndex != null) { 13917 let cell = this.props.cells[colIndex]; 13918 let slatTop = slatCoords.positions.tops[slatIndex]; 13919 let slatHeight = slatCoords.positions.getHeight(slatIndex); 13920 let partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 13921 let localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat 13922 let snapIndex = slatIndex * snapsPerSlot + localSnapIndex; 13923 let dayDate = this.props.cells[colIndex].date; 13924 let time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); 13925 let start = dateEnv.add(dayDate, time); 13926 let end = dateEnv.add(start, snapDuration); 13927 return { 13928 dateProfile, 13929 dateSpan: Object.assign({ range: { start, end }, allDay: false }, cell.extraDateSpan), 13930 dayEl: colCoords.els[colIndex], 13931 rect: { 13932 left: colCoords.lefts[colIndex], 13933 right: colCoords.rights[colIndex], 13934 top: slatTop, 13935 bottom: slatTop + slatHeight, 13936 }, 13937 layer: 0, 13938 }; 13939 } 13940 return null; 13941 } 13942 } 13943 function processSlotOptions(slotDuration, snapDurationOverride) { 13944 let snapDuration = snapDurationOverride || slotDuration; 13945 let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); 13946 if (snapsPerSlot === null) { 13947 snapDuration = slotDuration; 13948 snapsPerSlot = 1; 13949 // TODO: say warning? 13950 } 13951 return { snapDuration, snapsPerSlot }; 13952 } 13953 13954 class DayTimeColsSlicer extends Slicer { 13955 sliceRange(range, dayRanges) { 13956 let segs = []; 13957 for (let col = 0; col < dayRanges.length; col += 1) { 13958 let segRange = intersectRanges(range, dayRanges[col]); 13959 if (segRange) { 13960 segs.push({ 13961 start: segRange.start, 13962 end: segRange.end, 13963 isStart: segRange.start.valueOf() === range.start.valueOf(), 13964 isEnd: segRange.end.valueOf() === range.end.valueOf(), 13965 col, 13966 }); 13967 } 13968 } 13969 return segs; 13970 } 13971 } 13972 13973 class DayTimeCols extends DateComponent { 13974 constructor() { 13975 super(...arguments); 13976 this.buildDayRanges = memoize(buildDayRanges); 13977 this.slicer = new DayTimeColsSlicer(); 13978 this.timeColsRef = d(); 13979 } 13980 render() { 13981 let { props, context } = this; 13982 let { dateProfile, dayTableModel } = props; 13983 let { nowIndicator, nextDayThreshold } = context.options; 13984 let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); 13985 // give it the first row of cells 13986 // TODO: would move this further down hierarchy, but sliceNowDate needs it 13987 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 }))))); 13988 } 13989 } 13990 function buildDayRanges(dayTableModel, dateProfile, dateEnv) { 13991 let ranges = []; 13992 for (let date of dayTableModel.headerDates) { 13993 ranges.push({ 13994 start: dateEnv.add(date, dateProfile.slotMinTime), 13995 end: dateEnv.add(date, dateProfile.slotMaxTime), 13996 }); 13997 } 13998 return ranges; 13999 } 14000 14001 // potential nice values for the slot-duration and interval-duration 14002 // from largest to smallest 14003 const STOCK_SUB_DURATIONS = [ 14004 { hours: 1 }, 14005 { minutes: 30 }, 14006 { minutes: 15 }, 14007 { seconds: 30 }, 14008 { seconds: 15 }, 14009 ]; 14010 function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { 14011 let dayStart = new Date(0); 14012 let slatTime = slotMinTime; 14013 let slatIterator = createDuration(0); 14014 let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); 14015 let metas = []; 14016 while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { 14017 let date = dateEnv.add(dayStart, slatTime); 14018 let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; 14019 metas.push({ 14020 date, 14021 time: slatTime, 14022 key: date.toISOString(), 14023 isoTimeStr: formatIsoTimeString(date), 14024 isLabeled, 14025 }); 14026 slatTime = addDurations(slatTime, slotDuration); 14027 slatIterator = addDurations(slatIterator, slotDuration); 14028 } 14029 return metas; 14030 } 14031 // Computes an automatic value for slotLabelInterval 14032 function computeLabelInterval(slotDuration) { 14033 let i; 14034 let labelInterval; 14035 let slotsPerLabel; 14036 // find the smallest stock label interval that results in more than one slots-per-label 14037 for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { 14038 labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); 14039 slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); 14040 if (slotsPerLabel !== null && slotsPerLabel > 1) { 14041 return labelInterval; 14042 } 14043 } 14044 return slotDuration; // fall back 14045 } 14046 14047 class DayTimeColsView extends TimeColsView { 14048 constructor() { 14049 super(...arguments); 14050 this.buildTimeColsModel = memoize(buildTimeColsModel); 14051 this.buildSlatMetas = memoize(buildSlatMetas); 14052 } 14053 render() { 14054 let { options, dateEnv, dateProfileGenerator } = this.context; 14055 let { props } = this; 14056 let { dateProfile } = props; 14057 let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); 14058 let splitProps = this.allDaySplitter.splitProps(props); 14059 let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); 14060 let { dayMinWidth } = options; 14061 let hasAttachedAxis = !dayMinWidth; 14062 let hasDetachedAxis = dayMinWidth; 14063 let headerContent = options.dayHeaders && (y(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); 14064 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())))); 14065 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 }))); 14066 return hasDetachedAxis 14067 ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) 14068 : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); 14069 } 14070 }
vendor: 9,797 bytes, lines 14071-14261
14071 function buildTimeColsModel(dateProfile, dateProfileGenerator) { 14072 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); 14073 return new DayTableModel(daySeries, false); 14074 } 14075 14076 const OPTION_REFINERS$2 = { 14077 allDaySlot: Boolean, 14078 }; 14079 14080 var index$2 = createPlugin({ 14081 name: '@fullcalendar/timegrid', 14082 initialView: 'timeGridWeek', 14083 optionRefiners: OPTION_REFINERS$2, 14084 views: { 14085 timeGrid: { 14086 component: DayTimeColsView, 14087 usesMinMaxTime: true, 14088 allDaySlot: true, 14089 slotDuration: '00:30:00', 14090 slotEventOverlap: true, // a bad name. confused with overlap/constraint system 14091 }, 14092 timeGridDay: { 14093 type: 'timeGrid', 14094 duration: { days: 1 }, 14095 }, 14096 timeGridWeek: { 14097 type: 'timeGrid', 14098 duration: { weeks: 1 }, 14099 }, 14100 }, 14101 }); 14102 14103 class ListViewHeaderRow extends BaseComponent { 14104 constructor() { 14105 super(...arguments); 14106 this.state = { 14107 textId: getUniqueDomId(), 14108 }; 14109 } 14110 render() { 14111 let { theme, dateEnv, options, viewApi } = this.context; 14112 let { cellId, dayDate, todayRange } = this.props; 14113 let { textId } = this.state; 14114 let dayMeta = getDateMeta(dayDate, todayRange); 14115 // will ever be falsy? 14116 let text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; 14117 // will ever be falsy? also, BAD NAME "alt" 14118 let sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; 14119 let renderProps = Object.assign({ date: dateEnv.toDate(dayDate), view: viewApi, textId, 14120 text, 14121 sideText, navLinkAttrs: buildNavLinkAttrs(this.context, dayDate), sideNavLinkAttrs: buildNavLinkAttrs(this.context, dayDate, 'day', false) }, dayMeta); 14122 // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) 14123 return (y(ContentContainer, { elTag: "tr", elClasses: [ 14124 'fc-list-day', 14125 ...getDayClassNames(dayMeta, theme), 14126 ], elAttrs: { 14127 'data-date': formatDayString(dayDate), 14128 }, 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 14129 y("th", { scope: "colgroup", colSpan: 3, id: cellId, "aria-labelledby": textId }, 14130 y(InnerContent, { elTag: "div", elClasses: [ 14131 'fc-list-day-cushion', 14132 theme.getClass('tableCellShaded'), 14133 ] }))))); 14134 } 14135 } 14136 function renderInnerContent(props) { 14137 return (y(_, null, 14138 props.text && (y("a", Object.assign({ id: props.textId, className: "fc-list-day-text" }, props.navLinkAttrs), props.text)), 14139 props.sideText && ( /* not keyboard tabbable */y("a", Object.assign({ "aria-hidden": true, className: "fc-list-day-side-text" }, props.sideNavLinkAttrs), props.sideText)))); 14140 } 14141 14142 const DEFAULT_TIME_FORMAT = createFormatter({ 14143 hour: 'numeric', 14144 minute: '2-digit', 14145 meridiem: 'short', 14146 }); 14147 class ListViewEventRow extends BaseComponent { 14148 render() { 14149 let { props, context } = this; 14150 let { options } = context; 14151 let { seg, timeHeaderId, eventHeaderId, dateHeaderId } = props; 14152 let timeFormat = options.eventTimeFormat || DEFAULT_TIME_FORMAT; 14153 return (y(EventContainer, Object.assign({}, props, { elTag: "tr", elClasses: [ 14154 'fc-list-event', 14155 seg.eventRange.def.url && 'fc-event-forced-url', 14156 ], defaultGenerator: () => renderEventInnerContent(seg, context) /* weird */, seg: seg, timeText: "", disableDragging: true, disableResizing: true }), (InnerContent, eventContentArg) => (y(_, null, 14157 buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId), 14158 y("td", { "aria-hidden": true, className: "fc-list-event-graphic" }, 14159 y("span", { className: "fc-list-event-dot", style: { 14160 borderColor: eventContentArg.borderColor || eventContentArg.backgroundColor, 14161 } })), 14162 y(InnerContent, { elTag: "td", elClasses: ['fc-list-event-title'], elAttrs: { headers: `${eventHeaderId} ${dateHeaderId}` } }))))); 14163 } 14164 } 14165 function renderEventInnerContent(seg, context) { 14166 let interactiveAttrs = getSegAnchorAttrs(seg, context); 14167 return (y("a", Object.assign({}, interactiveAttrs), seg.eventRange.def.title)); 14168 } 14169 function buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId) { 14170 let { options } = context; 14171 if (options.displayEventTime !== false) { 14172 let eventDef = seg.eventRange.def; 14173 let eventInstance = seg.eventRange.instance; 14174 let doAllDay = false; 14175 let timeText; 14176 if (eventDef.allDay) { 14177 doAllDay = true; 14178 } 14179 else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? 14180 if (seg.isStart) { 14181 timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); 14182 } 14183 else if (seg.isEnd) { 14184 timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); 14185 } 14186 else { 14187 doAllDay = true; 14188 } 14189 } 14190 else { 14191 timeText = buildSegTimeText(seg, timeFormat, context); 14192 } 14193 if (doAllDay) { 14194 let renderProps = { 14195 text: context.options.allDayText, 14196 view: context.viewApi, 14197 }; 14198 return (y(ContentContainer, { elTag: "td", elClasses: ['fc-list-event-time'], elAttrs: { 14199 headers: `${timeHeaderId} ${dateHeaderId}`, 14200 }, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount })); 14201 } 14202 return (y("td", { className: "fc-list-event-time" }, timeText)); 14203 } 14204 return null; 14205 } 14206 function renderAllDayInner(renderProps) { 14207 return renderProps.text; 14208 } 14209 14210 /* 14211 Responsible for the scroller, and forwarding event-related actions into the "grid". 14212 */ 14213 class ListView extends DateComponent { 14214 constructor() { 14215 super(...arguments); 14216 this.computeDateVars = memoize(computeDateVars); 14217 this.eventStoreToSegs = memoize(this._eventStoreToSegs); 14218 this.state = { 14219 timeHeaderId: getUniqueDomId(), 14220 eventHeaderId: getUniqueDomId(), 14221 dateHeaderIdRoot: getUniqueDomId(), 14222 }; 14223 this.setRootEl = (rootEl) => { 14224 if (rootEl) { 14225 this.context.registerInteractiveComponent(this, { 14226 el: rootEl, 14227 }); 14228 } 14229 else { 14230 this.context.unregisterInteractiveComponent(this); 14231 } 14232 }; 14233 } 14234 render() { 14235 let { props, context } = this; 14236 let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile); 14237 let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); 14238 return (y(ViewContainer, { elRef: this.setRootEl, elClasses: [ 14239 'fc-list', 14240 context.theme.getClass('table'), 14241 context.options.stickyHeaderDates !== false ? 14242 'fc-list-sticky' : 14243 '', 14244 ], viewSpec: context.viewSpec }, 14245 y(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? 14246 this.renderSegList(eventSegs, dayDates) : 14247 this.renderEmptyMessage()))); 14248 } 14249 renderEmptyMessage() { 14250 let { options, viewApi } = this.context; 14251 let renderProps = { 14252 text: options.noEventsText, 14253 view: viewApi, 14254 }; 14255 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'] })))); 14256 } 14257 renderSegList(allSegs, dayDates) { 14258 let { theme, options } = this.context; 14259 let { timeHeaderId, eventHeaderId, dateHeaderIdRoot } = this.state; 14260 let segsByDay = groupSegsByDay(allSegs); // sparse array 14261 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => {
14262 let innerNodes = []; 14263 for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { 14264 let daySegs = segsByDay[dayIndex]; 14265 if (daySegs) { // sparse array, so might be undefined 14266 let dayStr = formatDayString(dayDates[dayIndex]); 14267 let dateHeaderId = dateHeaderIdRoot + '-' + dayStr; 14268 // append a day header 14269 innerNodes.push(y(ListViewHeaderRow, { key: dayStr, cellId: dateHeaderId, dayDate: dayDates[dayIndex], todayRange: todayRange })); 14270 daySegs = sortEventSegs(daySegs, options.eventOrder); 14271 for (let seg of daySegs) { 14272 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)))); 14273 } 14274 } 14275 } 14276 return (y("table", { className: 'fc-list-table ' + theme.getClass('table') }, 14277 y("thead", null, 14278 y("tr", null, 14279 y("th", { scope: "col", id: timeHeaderId }, options.timeHint), 14280 y("th", { scope: "col", "aria-hidden": true }), 14281 y("th", { scope: "col", id: eventHeaderId }, options.eventHint))), 14282 y("tbody", null, innerNodes))); 14283 })); 14284 } 14285 _eventStoreToSegs(eventStore, eventUiBases, dayRanges) { 14286 return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); 14287 } 14288 eventRangesToSegs(eventRanges, dayRanges) { 14289 let segs = []; 14290 for (let eventRange of eventRanges) { 14291 segs.push(...this.eventRangeToSegs(eventRange, dayRanges)); 14292 } 14293 return segs; 14294 } 14295 eventRangeToSegs(eventRange, dayRanges) { 14296 let { dateEnv } = this.context; 14297 let { nextDayThreshold } = this.context.options; 14298 let range = eventRange.range; 14299 let allDay = eventRange.def.allDay; 14300 let dayIndex; 14301 let segRange; 14302 let seg; 14303 let segs = []; 14304 for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { 14305 segRange = intersectRanges(range, dayRanges[dayIndex]); 14306 if (segRange) { 14307 seg = { 14308 component: this, 14309 eventRange, 14310 start: segRange.start, 14311 end: segRange.end, 14312 isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), 14313 isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), 14314 dayIndex, 14315 }; 14316 segs.push(seg); 14317 // detect when range won't go fully into the next day, 14318 // and mutate the latest seg to the be the end. 14319 if (!seg.isEnd && !allDay && 14320 dayIndex + 1 < dayRanges.length && 14321 range.end < 14322 dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { 14323 seg.end = range.end; 14324 seg.isEnd = true; 14325 break; 14326 } 14327 } 14328 } 14329 return segs; 14330 } 14331 } 14332 function renderNoEventsInner(renderProps) { 14333 return renderProps.text; 14334 } 14335 function computeDateVars(dateProfile) { 14336 let dayStart = startOfDay(dateProfile.renderRange.start); 14337 let viewEnd = dateProfile.renderRange.end; 14338 let dayDates = []; 14339 let dayRanges = []; 14340 while (dayStart < viewEnd) { 14341 dayDates.push(dayStart); 14342 dayRanges.push({ 14343 start: dayStart, 14344 end: addDays(dayStart, 1), 14345 }); 14346 dayStart = addDays(dayStart, 1); 14347 } 14348 return { dayDates, dayRanges }; 14349 } 14350 // Returns a sparse array of arrays, segs grouped by their dayIndex 14351 function groupSegsByDay(segs) { 14352 let segsByDay = []; // sparse array 14353 let i; 14354 let seg; 14355 for (i = 0; i < segs.length; i += 1) { 14356 seg = segs[i]; 14357 (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) 14358 .push(seg); 14359 } 14360 return segsByDay; 14361 } 14362 14363 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}"; 14364 injectStyles(css_248z$1); 14365 14366 const OPTION_REFINERS$1 = { 14367 listDayFormat: createFalsableFormatter, 14368 listDaySideFormat: createFalsableFormatter, 14369 noEventsClassNames: identity, 14370 noEventsContent: identity, 14371 noEventsDidMount: identity, 14372 noEventsWillUnmount: identity, 14373 // noEventsText is defined in base options 14374 }; 14375 function createFalsableFormatter(input) { 14376 return input === false ? null : createFormatter(input); 14377 } 14378 14379 var index$1 = createPlugin({ 14380 name: '@fullcalendar/list', 14381 optionRefiners: OPTION_REFINERS$1, 14382 views: { 14383 list: { 14384 component: ListView, 14385 buttonTextKey: 'list', 14386 listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" 14387 }, 14388 listDay: { 14389 type: 'list', 14390 duration: { days: 1 }, 14391 listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar 14392 }, 14393 listWeek: { 14394 type: 'list', 14395 duration: { weeks: 1 }, 14396 listDayFormat: { weekday: 'long' }, 14397 listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, 14398 }, 14399 listMonth: { 14400 type: 'list', 14401 duration: { month: 1 }, 14402 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have 14403 }, 14404 listYear: { 14405 type: 'list', 14406 duration: { year: 1 }, 14407 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have 14408 }, 14409 }, 14410 }); 14411 14412 class SingleMonth extends DateComponent { 14413 constructor() { 14414 super(...arguments); 14415 this.buildDayTableModel = memoize(buildDayTableModel); 14416 this.slicer = new DayTableSlicer(); 14417 this.state = { 14418 labelId: getUniqueDomId(), 14419 }; 14420 } 14421 render() { 14422 const { props, state, context } = this; 14423 const { dateProfile, forPrint } = props; 14424 const { options } = context; 14425 const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator); 14426 const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel); 14427 // ensure single-month has aspect ratio 14428 const tableHeight = props.tableWidth != null ? props.tableWidth / options.aspectRatio : null; 14429 const rowCnt = dayTableModel.cells.length; 14430 const rowHeight = tableHeight != null ? tableHeight / rowCnt : null; 14431 return (y("div", { ref: props.elRef, "data-date": props.isoDateStr, className: "fc-multimonth-month", style: { width: props.width }, role: "grid", "aria-labelledby": state.labelId }, 14432 y("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" }, 14433 y("div", { className: "fc-multimonth-title", id: state.labelId }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)), 14434 y("table", { className: [ 14435 'fc-multimonth-header-table', 14436 context.theme.getClass('table'), 14437 ].join(' '), role: "presentation" }, 14438 y("thead", { role: "rowgroup" }, 14439 y(DayHeader, { dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: false })))), 14440 y("div", { className: [ 14441 'fc-multimonth-daygrid',
vendor: 9,907 bytes, lines 14442-14621
14442 'fc-daygrid', 14443 'fc-daygrid-body', 14444 !forPrint && 'fc-daygrid-body-balanced', 14445 forPrint && 'fc-daygrid-body-unbalanced', 14446 forPrint && 'fc-daygrid-body-natural', 14447 ].join(' '), style: { marginTop: -rowHeight } }, 14448 y("table", { className: [ 14449 'fc-multimonth-daygrid-table', 14450 context.theme.getClass('table'), 14451 ].join(' '), style: { height: forPrint ? '' : tableHeight }, role: "presentation" }, 14452 y("tbody", { role: "rowgroup" }, 14453 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 }))))))); 14454 } 14455 } 14456 14457 class MultiMonthView extends DateComponent { 14458 constructor() { 14459 super(...arguments); 14460 this.splitDateProfileByMonth = memoize(splitDateProfileByMonth); 14461 this.buildMonthFormat = memoize(buildMonthFormat); 14462 this.scrollElRef = d(); 14463 this.firstMonthElRef = d(); 14464 this.needsScrollReset = false; 14465 this.handleSizing = (isForced) => { 14466 if (isForced) { 14467 this.updateSize(); 14468 } 14469 }; 14470 } 14471 render() { 14472 const { context, props, state } = this; 14473 const { options } = context; 14474 const { clientWidth, clientHeight } = state; 14475 const monthHPadding = state.monthHPadding || 0; 14476 const colCount = Math.min(clientWidth != null ? 14477 Math.floor(clientWidth / (options.multiMonthMinWidth + monthHPadding)) : 14478 1, options.multiMonthMaxColumns) || 1; 14479 const monthWidthPct = (100 / colCount) + '%'; 14480 const monthTableWidth = clientWidth == null ? null : 14481 (clientWidth / colCount) - monthHPadding; 14482 const isLegitSingleCol = clientWidth != null && colCount === 1; 14483 const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, isLegitSingleCol ? false : options.fixedWeekCount, options.showNonCurrentDates); 14484 const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles); 14485 const rootClassNames = [ 14486 'fc-multimonth', 14487 isLegitSingleCol ? 14488 'fc-multimonth-singlecol' : 14489 'fc-multimonth-multicol', 14490 (monthTableWidth != null && monthTableWidth < 400) ? 14491 'fc-multimonth-compact' : 14492 '', 14493 ]; 14494 return (y(ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => { 14495 const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start); 14496 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 }))); 14497 }))); 14498 } 14499 componentDidMount() { 14500 this.updateSize(); 14501 this.context.addResizeHandler(this.handleSizing); 14502 this.requestScrollReset(); 14503 } 14504 componentDidUpdate(prevProps) { 14505 if (!isPropsEqual(prevProps, this.props)) { // an external change? 14506 this.handleSizing(false); 14507 } 14508 if (prevProps.dateProfile !== this.props.dateProfile) { 14509 this.requestScrollReset(); 14510 } 14511 else { 14512 this.flushScrollReset(); 14513 } 14514 } 14515 componentWillUnmount() { 14516 this.context.removeResizeHandler(this.handleSizing); 14517 } 14518 updateSize() { 14519 const scrollEl = this.scrollElRef.current; 14520 const firstMonthEl = this.firstMonthElRef.current; 14521 if (scrollEl) { 14522 this.setState({ 14523 clientWidth: scrollEl.clientWidth, 14524 clientHeight: scrollEl.clientHeight, 14525 }); 14526 } 14527 if (firstMonthEl && scrollEl) { 14528 if (this.state.monthHPadding == null) { // always remember initial non-zero value 14529 this.setState({ 14530 monthHPadding: scrollEl.clientWidth - // go within padding 14531 firstMonthEl.firstChild.offsetWidth, 14532 }); 14533 } 14534 } 14535 } 14536 requestScrollReset() { 14537 this.needsScrollReset = true; 14538 this.flushScrollReset(); 14539 } 14540 flushScrollReset() { 14541 if (this.needsScrollReset && 14542 this.state.monthHPadding != null // indicates sizing already happened 14543 ) { 14544 const { currentDate } = this.props.dateProfile; 14545 const scrollEl = this.scrollElRef.current; 14546 const monthEl = scrollEl.querySelector(`[data-date="${formatIsoMonthStr(currentDate)}"]`); 14547 scrollEl.scrollTop = monthEl.getBoundingClientRect().top - 14548 this.firstMonthElRef.current.getBoundingClientRect().top; 14549 this.needsScrollReset = false; 14550 } 14551 } 14552 // workaround for when queued setState render (w/ clientWidth) gets cancelled because 14553 // subsequent update and shouldComponentUpdate says not to render :( 14554 shouldComponentUpdate() { 14555 return true; 14556 } 14557 } 14558 // date profile 14559 // ------------------------------------------------------------------------------------------------- 14560 const oneMonthDuration = createDuration(1, 'month'); 14561 function splitDateProfileByMonth(dateProfileGenerator, dateProfile, dateEnv, fixedWeekCount, showNonCurrentDates) { 14562 const { start, end } = dateProfile.currentRange; 14563 let monthStart = start; 14564 const monthDateProfiles = []; 14565 while (monthStart.valueOf() < end.valueOf()) { 14566 const monthEnd = dateEnv.add(monthStart, oneMonthDuration); 14567 const currentRange = { 14568 // yuck 14569 start: dateProfileGenerator.skipHiddenDays(monthStart), 14570 end: dateProfileGenerator.skipHiddenDays(monthEnd, -1, true), 14571 }; 14572 let renderRange = buildDayTableRenderRange({ 14573 currentRange, 14574 snapToWeek: true, 14575 fixedWeekCount, 14576 dateEnv, 14577 }); 14578 renderRange = { 14579 // yuck 14580 start: dateProfileGenerator.skipHiddenDays(renderRange.start), 14581 end: dateProfileGenerator.skipHiddenDays(renderRange.end, -1, true), 14582 }; 14583 const activeRange = dateProfile.activeRange ? 14584 intersectRanges(dateProfile.activeRange, showNonCurrentDates ? renderRange : currentRange) : 14585 null; 14586 monthDateProfiles.push({ 14587 currentDate: dateProfile.currentDate, 14588 isValid: dateProfile.isValid, 14589 validRange: dateProfile.validRange, 14590 renderRange, 14591 activeRange, 14592 currentRange, 14593 currentRangeUnit: 'month', 14594 isRangeAllDay: true, 14595 dateIncrement: dateProfile.dateIncrement, 14596 slotMinTime: dateProfile.slotMaxTime, 14597 slotMaxTime: dateProfile.slotMinTime, 14598 }); 14599 monthStart = monthEnd; 14600 } 14601 return monthDateProfiles; 14602 } 14603 // date formatting 14604 // ------------------------------------------------------------------------------------------------- 14605 const YEAR_MONTH_FORMATTER = createFormatter({ year: 'numeric', month: 'long' }); 14606 const YEAR_FORMATTER = createFormatter({ month: 'long' }); 14607 function buildMonthFormat(formatOverride, monthDateProfiles) { 14608 return formatOverride || 14609 ((monthDateProfiles[0].currentRange.start.getUTCFullYear() !== 14610 monthDateProfiles[monthDateProfiles.length - 1].currentRange.start.getUTCFullYear()) 14611 ? YEAR_MONTH_FORMATTER 14612 : YEAR_FORMATTER); 14613 } 14614 14615 const OPTION_REFINERS = { 14616 multiMonthTitleFormat: createFormatter, 14617 multiMonthMaxColumns: Number, 14618 multiMonthMinWidth: Number, 14619 }; 14620 14621 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}
14621.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}"; 14622 injectStyles(css_248z); 14623 14624 var index = createPlugin({ 14625 name: '@fullcalendar/multimonth', 14626 initialView: 'multiMonthYear', 14627 optionRefiners: OPTION_REFINERS, 14628 views: { 14629 multiMonth: { 14630 component: MultiMonthView, 14631 dateProfileGeneratorClass: TableDateProfileGenerator, 14632 multiMonthMinWidth: 350, 14633 multiMonthMaxColumns: 3, 14634 }, 14635 multiMonthYear: { 14636 type: 'multiMonth', 14637 duration: { years: 1 }, 14638 fixedWeekCount: true, 14639 showNonCurrentDates: false, 14640 }, 14641 }, 14642 }); 14643 14644 globalPlugins.push(index$4, index$3, index$2, index$1, index); 14645 14646 exports.Calendar = Calendar; 14647 exports.Draggable = ExternalDraggable; 14648 exports.Internal = internal; 14649 exports.JsonRequestError = JsonRequestError; 14650 exports.Preact = preact; 14651 exports.ThirdPartyDraggable = ThirdPartyDraggable; 14652 exports.createPlugin = createPlugin; 14653 exports.formatDate = formatDate; 14654 exports.formatRange = formatRange; 14655 exports.globalLocales = globalLocales; 14656 exports.globalPlugins = globalPlugins; 14657 exports.sliceEvents = sliceEvents; 14658 exports.version = version; 14659 14660 Object.defineProperty(exports, '__esModule', { value: true }); 14661 14662 return exports; 14663 14664})({});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.