vendor: 5,217 bytes, lines 1-9
1/*! 2FullCalendar Standard Bundle v6.0.0 3Docs & License: https://fullcalendar.io/docs/initialize-globals 4(c) 2022 Adam Shaw 5*/ 6var FullCalendar = (function (exports) { 7 'use strict'; 8 9 var n,l$1,u$1,i$1,t,o,r$1,f$1={},e$1=[],c$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function a$1(n){var l=n.parentNode;l&&l.removeChild(n);}function h(l,u,i){var t,o,r,f={};for(r in u)"key"==r?t=u[r]:"ref"==r?o=u[r]:f[r]=u[r];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),"function"==typeof l&&null!=l.defaultProps)for(r in l.defaultProps)void 0===f[r]&&(f[r]=l.defaultProps[r]);return v$1(l,f,t,o,null)}function v$1(n,i,t,o,r){var f={type:n,props:i,key:t,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==r?++u$1:r};return null==r&&null!=l$1.vnode&&l$1.vnode(f),f}function y(){return {current:null}}function p(n){return n.children}function d(n,l){this.props=n,this.context=l;}function _(n,l){if(null==l)return n.__?_(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?_(n):null}function k$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 k$1(n)}}function b$1(n){(!n.__d&&(n.__d=!0)&&t.push(n)&&!g$2.__r++||o!==l$1.debounceRendering)&&((o=l$1.debounceRendering)||setTimeout)(g$2);}function g$2(){for(var n;g$2.__r=t.length;)n=t.sort(function(n,l){return n.__v.__b-l.__v.__b}),t=[],n.some(function(n){var l,u,i,t,o,r;n.__d&&(o=(t=(l=n).__v).__e,(r=l.__P)&&(u=[],(i=s({},t)).__v=t.__v+1,j$2(r,t,i,l.__n,void 0!==r.ownerSVGElement,null!=t.__h?[o]:null,u,null==o?_(t):o,t.__h),z$1(u,t),t.__e!=o&&k$1(t)));});}function w$2(n,l,u,i,t,o,r,c,s,a){var h,y,d,k,b,g,w,x=i&&i.__k||e$1,C=x.length;for(u.__k=[],h=0;h<l.length;h++)if(null!=(k=u.__k[h]=null==(k=l[h])||"boolean"==typeof k?null:"string"==typeof k||"number"==typeof k||"bigint"==typeof k?v$1(null,k,null,null,k):Array.isArray(k)?v$1(p,{children:k},null,null,null):k.__b>0?v$1(k.type,k.props,k.key,k.ref?k.ref:null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(d=x[h])||d&&k.key==d.key&&k.type===d.type)x[h]=void 0;else for(y=0;y<C;y++){if((d=x[y])&&k.key==d.key&&k.type===d.type){x[y]=void 0;break}d=null;}j$2(n,k,d=d||f$1,t,o,r,c,s,a),b=k.__e,(y=k.ref)&&d.ref!=y&&(w||(w=[]),d.ref&&w.push(d.ref,null,k),w.push(y,k.__c||b,k)),null!=b?(null==g&&(g=b),"function"==typeof k.type&&k.__k===d.__k?k.__d=s=m$1(k,s,n):s=A(n,k,d,x,b,s),"function"==typeof u.type&&(u.__d=s)):s&&d.__e==s&&s.parentNode!=n&&(s=_(d));}for(u.__e=g,h=C;h--;)null!=x[h]&&N(x[h],x[h]);if(w)for(h=0;h<w.length;h++)M(w[h],w[++h],w[++h]);}function m$1(n,l,u){for(var i,t=n.__k,o=0;t&&o<t.length;o++)(i=t[o])&&(i.__=n,l="function"==typeof i.type?m$1(i,l,u):A(u,i,i,t,i.__e,l));return l}function x$1(n,l){return l=l||[],null==n||"boolean"==typeof n||(Array.isArray(n)?n.some(function(n){x$1(n,l);}):l.push(n)),l}function A(n,l,u,i,t,o){var r,f,e;if(void 0!==l.__d)r=l.__d,l.__d=void 0;else if(null==u||t!=o||null==t.parentNode)n:if(null==o||o.parentNode!==n)n.appendChild(t),r=null;else {for(f=o,e=0;(f=f.nextSibling)&&e<i.length;e+=1)if(f==t)break n;n.insertBefore(t,o),r=o;}return void 0!==r?r:t.nextSibling}function C$1(n,l,u,i,t){var o;for(o in u)"children"===o||"key"===o||o in l||H$1(n,o,null,u[o],i);for(o in l)t&&"function"!=typeof l[o]||"children"===o||"key"===o||"value"===o||"checked"===o||u[o]===l[o]||H$1(n,o,l[o],u[o],i);}function $$1(n,l,u){"-"===l[0]?n.setProperty(l,u):n[l]=null==u?"":"number"!=typeof u||c$1.test(l)?u:u+"px";}function H$1(n,l,u,i,t){var o;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||$$1(n.style,l,"");if(u)for(l in u)i&&u[l]===i[l]||$$1(n.style,l,u[l]);}else if("o"===l[0]&&"n"===l[1])o=l!==(l=l.replace(/Capture$/,"")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+o]=u,u?i||n.addEventListener(l,o?T$1:I$1,o):n.removeEventListener(l,o?T$1:I$1,o);else if("dangerouslySetInnerHTML"!==l){if(t)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("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 I$1(n){this.l[n.type+!1](l$1.event?l$1.event(n):n);}function T$1(n){this.l[n.type+!0](l$1.event?l$1.event(n):n);}function j$2(n,u,i,t,o,r,f,e,c){var a,h,v,y,_,k,b,g,m,x,A,C,$,H,I,T=u.type;if(void 0!==u.constructor)return null;null!=i.__h&&(c=i.__h,e=u.__e=i.__e,u.__h=null,r=[e]),(a=l$1.__b)&&a(u);try{n:if("function"==typeof T){if(g=u.props,m=(a=T.contextType)&&t[a.__c],x=a?m?m.props.value:a.__:t,i.__c?b=(h=u.__c=i.__c).__=h.__E:("prototype"in T&&T.prototype.render?u.__c=h=new T(g,x):(u.__c=h=new d(g,x),h.constructor=T,h.render=O),m&&m.sub(h),h.props=g,h.state||(h.state={}),h.context=x,h.__n=t,v=h.__d=!0,h.__h=[],h._sb=[]),null==h.__s&&(h.__s=h.state),null!=T.getDerivedStateFromProps&&(h.__s==h.state&&(h.__s=s({}
vendor: 17,552 bytes, lines 9-179
9,h.__s)),s(h.__s,T.getDerivedStateFromProps(g,h.__s))),y=h.props,_=h.state,v)null==T.getDerivedStateFromProps&&null!=h.componentWillMount&&h.componentWillMount(),null!=h.componentDidMount&&h.__h.push(h.componentDidMount);else {if(null==T.getDerivedStateFromProps&&g!==y&&null!=h.componentWillReceiveProps&&h.componentWillReceiveProps(g,x),!h.__e&&null!=h.shouldComponentUpdate&&!1===h.shouldComponentUpdate(g,h.__s,x)||u.__v===i.__v){for(h.props=g,h.state=h.__s,u.__v!==i.__v&&(h.__d=!1),h.__v=u,u.__e=i.__e,u.__k=i.__k,u.__k.forEach(function(n){n&&(n.__=u);}),A=0;A<h._sb.length;A++)h.__h.push(h._sb[A]);h._sb=[],h.__h.length&&f.push(h);break n}null!=h.componentWillUpdate&&h.componentWillUpdate(g,h.__s,x),null!=h.componentDidUpdate&&h.__h.push(function(){h.componentDidUpdate(y,_,k);});}if(h.context=x,h.props=g,h.__v=u,h.__P=n,C=l$1.__r,$=0,"prototype"in T&&T.prototype.render){for(h.state=h.__s,h.__d=!1,C&&C(u),a=h.render(h.props,h.state,h.context),H=0;H<h._sb.length;H++)h.__h.push(h._sb[H]);h._sb=[];}else do{h.__d=!1,C&&C(u),a=h.render(h.props,h.state,h.context),h.state=h.__s;}while(h.__d&&++$<25);h.state=h.__s,null!=h.getChildContext&&(t=s(s({},t),h.getChildContext())),v||null==h.getSnapshotBeforeUpdate||(k=h.getSnapshotBeforeUpdate(y,_)),I=null!=a&&a.type===p&&null==a.key?a.props.children:a,w$2(n,Array.isArray(I)?I:[I],u,i,t,o,r,f,e,c),h.base=u.__e,u.__h=null,h.__h.length&&f.push(h),b&&(h.__E=h.__=null),h.__e=!1;}else null==r&&u.__v===i.__v?(u.__k=i.__k,u.__e=i.__e):u.__e=L$1(i.__e,u,i,t,o,r,f,c);(a=l$1.diffed)&&a(u);}catch(n){u.__v=null,(c||null!=r)&&(u.__e=e,u.__h=!!c,r[r.indexOf(e)]=null),l$1.__e(n,u,i);}}function z$1(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 L$1(l,u,i,t,o,r,e,c){var s,h,v,y=i.props,p=u.props,d=u.type,k=0;if("svg"===d&&(o=!0),null!=r)for(;k<r.length;k++)if((s=r[k])&&"setAttribute"in s==!!d&&(d?s.localName===d:3===s.nodeType)){l=s,r[k]=null;break}if(null==l){if(null===d)return document.createTextNode(p);l=o?document.createElementNS("http://www.w3.org/2000/svg",d):document.createElement(d,p.is&&p),r=null,c=!1;}if(null===d)y===p||c&&l.data===p||(l.data=p);else {if(r=r&&n.call(l.childNodes),h=(y=i.props||f$1).dangerouslySetInnerHTML,v=p.dangerouslySetInnerHTML,!c){if(null!=r)for(y={},k=0;k<l.attributes.length;k++)y[l.attributes[k].name]=l.attributes[k].value;(v||h)&&(v&&(h&&v.__html==h.__html||v.__html===l.innerHTML)||(l.innerHTML=v&&v.__html||""));}if(C$1(l,p,y,o,c),v)u.__k=[];else if(k=u.props.children,w$2(l,Array.isArray(k)?k:[k],u,i,t,o&&"foreignObject"!==d,r,e,r?r[0]:i.__k&&_(i,0),c),null!=r)for(k=r.length;k--;)null!=r[k]&&a$1(r[k]);c||("value"in p&&void 0!==(k=p.value)&&(k!==l.value||"progress"===d&&!k||"option"===d&&k!==y.value)&&H$1(l,"value",k,y.value,!1),"checked"in p&&void 0!==(k=p.checked)&&k!==l.checked&&H$1(l,"checked",k,y.checked,!1));}return l}function M(n,u,i){try{"function"==typeof n?n(u):n.current=u;}catch(n){l$1.__e(n,i);}}function N(n,u,i){var t,o;if(l$1.unmount&&l$1.unmount(n),(t=n.ref)&&(t.current&&t.current!==n.__e||M(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(o=0;o<t.length;o++)t[o]&&N(t[o],u,i||"function"!=typeof n.type);i||null==n.__e||a$1(n.__e),n.__=n.__e=n.__d=void 0;}function O(n,l,u){return this.constructor(n,u)}function P$1(u,i,t){var o,r,e;l$1.__&&l$1.__(u,i),r=(o="function"==typeof t)?null:t&&t.__k||i.__k,e=[],j$2(i,u=(!o&&t||i).__k=h(p,null,[u]),r||f$1,f$1,void 0!==i.ownerSVGElement,!o&&t?[t]:r?null:i.firstChild?n.call(i.childNodes):null,e,!o&&t?t:r?r.__e:i.firstChild,o),z$1(e,u);}function B$1(n,l){var u={__c:l="__cC"+r$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(b$1);},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=e$1.slice,l$1={__e:function(n,l,u,i){for(var t,o,r;l=l.__;)if((t=l.__c)&&!t.__)try{if((o=t.constructor)&&null!=o.getDerivedStateFromError&&(t.setState(o.getDerivedStateFromError(n)),r=t.__d),null!=t.componentDidCatch&&(t.componentDidCatch(n,i||{}),r=t.__d),r)return t.__E=t}catch(l){n=l;}throw n}},u$1=0,i$1=function(n){return null!=n&&void 0===n.constructor},d.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=s({},this.state),"function"==typeof n&&(n=n(s({},u),this.props)),n&&s(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),b$1(this));},d.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),b$1(this));},d.prototype.render=p,t=[],g$2.__r=0,r$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 d).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 d).__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&&h(p,null,n.fallback);return i&&(i.__h=null),[h(p,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(){P$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);}}),P$1(h(P,{context:e.context},n.__v),e.l)):e.l&&e.componentWillUnmount();}function j(n,e){var r=h($,{__v:n,i:e});return r.containerInfo=e,r}(V.prototype=new d).__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=x$1(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)};d.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(d.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=x$1(e.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value);})),"select"==t&&null!=u.defaultValue&&(u.value=x$1(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 function injectStyles(css) { 16 if (!css || typeof document === 'undefined') { 17 return; 18 } 19 const head = document.head || document.getElementsByTagName('head')[0]; 20 const style = document.createElement('style'); 21 style.type = 'text/css'; 22 head.appendChild(style); 23 if (style.styleSheet) { 24 style.styleSheet.cssText = css; 25 } 26 else { 27 style.appendChild(document.createTextNode(css)); 28 } 29 } 30 31 class DelayedRunner { 32 constructor(drainedOption) { 33 this.drainedOption = drainedOption; 34 this.isRunning = false; 35 this.isDirty = false; 36 this.pauseDepths = {}; 37 this.timeoutId = 0; 38 } 39 request(delay) { 40 this.isDirty = true; 41 if (!this.isPaused()) { 42 this.clearTimeout(); 43 if (delay == null) { 44 this.tryDrain(); 45 } 46 else { 47 this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce 48 this.tryDrain.bind(this), delay); 49 } 50 } 51 } 52 pause(scope = '') { 53 let { pauseDepths } = this; 54 pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; 55 this.clearTimeout(); 56 } 57 resume(scope = '', force) { 58 let { pauseDepths } = this; 59 if (scope in pauseDepths) { 60 if (force) { 61 delete pauseDepths[scope]; 62 } 63 else { 64 pauseDepths[scope] -= 1; 65 let depth = pauseDepths[scope]; 66 if (depth <= 0) { 67 delete pauseDepths[scope]; 68 } 69 } 70 this.tryDrain(); 71 } 72 } 73 isPaused() { 74 return Object.keys(this.pauseDepths).length; 75 } 76 tryDrain() { 77 if (!this.isRunning && !this.isPaused()) { 78 this.isRunning = true; 79 while (this.isDirty) { 80 this.isDirty = false; 81 this.drained(); // might set isDirty to true again 82 } 83 this.isRunning = false; 84 } 85 } 86 clear() { 87 this.clearTimeout(); 88 this.isDirty = false; 89 this.pauseDepths = {}; 90 } 91 clearTimeout() { 92 if (this.timeoutId) { 93 clearTimeout(this.timeoutId); 94 this.timeoutId = 0; 95 } 96 } 97 drained() { 98 if (this.drainedOption) { 99 this.drainedOption(); 100 } 101 } 102 } 103 104 const { hasOwnProperty } = Object.prototype; 105 // Merges an array of objects into a single object. 106 // The second argument allows for an array of property names who's object values will be merged together. 107 function mergeProps(propObjs, complexPropsMap) { 108 let dest = {}; 109 if (complexPropsMap) { 110 for (let name in complexPropsMap) { 111 let complexObjs = []; 112 // collect the trailing object values, stopping when a non-object is discovered 113 for (let i = propObjs.length - 1; i >= 0; i -= 1) { 114 let val = propObjs[i][name]; 115 if (typeof val === 'object' && val) { // non-null object 116 complexObjs.unshift(val); 117 } 118 else if (val !== undefined) { 119 dest[name] = val; // if there were no objects, this value will be used 120 break; 121 } 122 } 123 // if the trailing values were objects, use the merged value 124 if (complexObjs.length) { 125 dest[name] = mergeProps(complexObjs); 126 } 127 } 128 } 129 // copy values into the destination, going from last to first 130 for (let i = propObjs.length - 1; i >= 0; i -= 1) { 131 let props = propObjs[i]; 132 for (let name in props) { 133 if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign 134 dest[name] = props[name]; 135 } 136 } 137 } 138 return dest; 139 } 140 function filterHash(hash, func) { 141 let filtered = {}; 142 for (let key in hash) { 143 if (func(hash[key], key)) { 144 filtered[key] = hash[key]; 145 } 146 } 147 return filtered; 148 } 149 function mapHash(hash, func) { 150 let newHash = {}; 151 for (let key in hash) { 152 newHash[key] = func(hash[key], key); 153 } 154 return newHash; 155 } 156 function arrayToHash(a) { 157 let hash = {}; 158 for (let item of a) { 159 hash[item] = true; 160 } 161 return hash; 162 } 163 // TODO: reassess browser support 164 // https://caniuse.com/?search=object.values 165 function hashValuesToArray(obj) { 166 let a = []; 167 for (let key in obj) { 168 a.push(obj[key]); 169 } 170 return a; 171 } 172 function isPropsEqual(obj0, obj1) { 173 if (obj0 === obj1) { 174 return true; 175 } 176 for (let key in obj0) { 177 if (hasOwnProperty.call(obj0, key)) { 178 if (!(key in obj1)) { 179 return false;
vendor: 11,027 bytes, lines 180-493
180 } 181 } 182 } 183 for (let key in obj1) { 184 if (hasOwnProperty.call(obj1, key)) { 185 if (obj0[key] !== obj1[key]) { 186 return false; 187 } 188 } 189 } 190 return true; 191 } 192 function getUnequalProps(obj0, obj1) { 193 let keys = []; 194 for (let key in obj0) { 195 if (hasOwnProperty.call(obj0, key)) { 196 if (!(key in obj1)) { 197 keys.push(key); 198 } 199 } 200 } 201 for (let key in obj1) { 202 if (hasOwnProperty.call(obj1, key)) { 203 if (obj0[key] !== obj1[key]) { 204 keys.push(key); 205 } 206 } 207 } 208 return keys; 209 } 210 function compareObjs(oldProps, newProps, equalityFuncs = {}) { 211 if (oldProps === newProps) { 212 return true; 213 } 214 for (let key in newProps) { 215 if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; 216 else { 217 return false; 218 } 219 } 220 // check for props that were omitted in the new 221 for (let key in oldProps) { 222 if (!(key in newProps)) { 223 return false; 224 } 225 } 226 return true; 227 } 228 /* 229 assumed "true" equality for handler names like "onReceiveSomething" 230 */ 231 function isObjValsEqual(val0, val1, comparator) { 232 if (val0 === val1 || comparator === true) { 233 return true; 234 } 235 if (comparator) { 236 return comparator(val0, val1); 237 } 238 return false; 239 } 240 function collectFromHash(hash, startIndex = 0, endIndex, step = 1) { 241 let res = []; 242 if (endIndex == null) { 243 endIndex = Object.keys(hash).length; 244 } 245 for (let i = startIndex; i < endIndex; i += step) { 246 let val = hash[i]; 247 if (val !== undefined) { // will disregard undefined for sparse arrays 248 res.push(val); 249 } 250 } 251 return res; 252 } 253 254 const globalLocales = []; 255 256 // TODO: new util arrayify? 257 function removeExact(array, exactVal) { 258 let removeCnt = 0; 259 let i = 0; 260 while (i < array.length) { 261 if (array[i] === exactVal) { 262 array.splice(i, 1); 263 removeCnt += 1; 264 } 265 else { 266 i += 1; 267 } 268 } 269 return removeCnt; 270 } 271 function isArraysEqual(a0, a1, equalityFunc) { 272 if (a0 === a1) { 273 return true; 274 } 275 let len = a0.length; 276 let i; 277 if (len !== a1.length) { // not array? or not same length? 278 return false; 279 } 280 for (i = 0; i < len; i += 1) { 281 if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { 282 return false; 283 } 284 } 285 return true; 286 } 287 288 function memoize(workerFunc, resEquality, teardownFunc) { 289 let currentArgs; 290 let currentRes; 291 return function (...newArgs) { 292 if (!currentArgs) { 293 currentRes = workerFunc.apply(this, newArgs); 294 } 295 else if (!isArraysEqual(currentArgs, newArgs)) { 296 if (teardownFunc) { 297 teardownFunc(currentRes); 298 } 299 let res = workerFunc.apply(this, newArgs); 300 if (!resEquality || !resEquality(res, currentRes)) { 301 currentRes = res; 302 } 303 } 304 currentArgs = newArgs; 305 return currentRes; 306 }; 307 } 308 function memoizeObjArg(workerFunc, resEquality, teardownFunc) { 309 let currentArg; 310 let currentRes; 311 return (newArg) => { 312 if (!currentArg) { 313 currentRes = workerFunc.call(this, newArg); 314 } 315 else if (!isPropsEqual(currentArg, newArg)) { 316 if (teardownFunc) { 317 teardownFunc(currentRes); 318 } 319 let res = workerFunc.call(this, newArg); 320 if (!resEquality || !resEquality(res, currentRes)) { 321 currentRes = res; 322 } 323 } 324 currentArg = newArg; 325 return currentRes; 326 }; 327 } 328 function memoizeArraylike(// used at all? 329 workerFunc, resEquality, teardownFunc) { 330 let currentArgSets = []; 331 let currentResults = []; 332 return (newArgSets) => { 333 let currentLen = currentArgSets.length; 334 let newLen = newArgSets.length; 335 let i = 0; 336 for (; i < currentLen; i += 1) { 337 if (!newArgSets[i]) { // one of the old sets no longer exists 338 if (teardownFunc) { 339 teardownFunc(currentResults[i]); 340 } 341 } 342 else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { 343 if (teardownFunc) { 344 teardownFunc(currentResults[i]); 345 } 346 let res = workerFunc.apply(this, newArgSets[i]); 347 if (!resEquality || !resEquality(res, currentResults[i])) { 348 currentResults[i] = res; 349 } 350 } 351 } 352 for (; i < newLen; i += 1) { 353 currentResults[i] = workerFunc.apply(this, newArgSets[i]); 354 } 355 currentArgSets = newArgSets; 356 currentResults.splice(newLen); // remove excess 357 return currentResults; 358 }; 359 } 360 function memoizeHashlike(workerFunc, resEquality, teardownFunc) { 361 let currentArgHash = {}; 362 let currentResHash = {}; 363 return (newArgHash) => { 364 let newResHash = {}; 365 for (let key in newArgHash) { 366 if (!currentResHash[key]) { 367 newResHash[key] = workerFunc.apply(this, newArgHash[key]); 368 } 369 else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { 370 if (teardownFunc) { 371 teardownFunc(currentResHash[key]); 372 } 373 let res = workerFunc.apply(this, newArgHash[key]); 374 newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) 375 ? currentResHash[key] 376 : res; 377 } 378 else { 379 newResHash[key] = currentResHash[key]; 380 } 381 } 382 currentArgHash = newArgHash; 383 currentResHash = newResHash; 384 return newResHash; 385 }; 386 } 387 388 function removeElement(el) { 389 if (el.parentNode) { 390 el.parentNode.removeChild(el); 391 } 392 } 393 // Querying 394 // ---------------------------------------------------------------------------------------------------------------- 395 function elementClosest(el, selector) { 396 if (el.closest) { 397 return el.closest(selector); 398 // really bad fallback for IE 399 // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 400 } 401 if (!document.documentElement.contains(el)) { 402 return null; 403 } 404 do { 405 if (elementMatches(el, selector)) { 406 return el; 407 } 408 el = (el.parentElement || el.parentNode); 409 } while (el !== null && el.nodeType === 1); 410 return null; 411 } 412 function elementMatches(el, selector) { 413 let method = el.matches || el.matchesSelector || el.msMatchesSelector; 414 return method.call(el, selector); 415 } 416 // accepts multiple subject els 417 // returns a real array. good for methods like forEach 418 // TODO: accept the document 419 function findElements(container, selector) { 420 let containers = container instanceof HTMLElement ? [container] : container; 421 let allMatches = []; 422 for (let i = 0; i < containers.length; i += 1) { 423 let matches = containers[i].querySelectorAll(selector); 424 for (let j = 0; j < matches.length; j += 1) { 425 allMatches.push(matches[j]); 426 } 427 } 428 return allMatches; 429 } 430 // accepts multiple subject els 431 // only queries direct child elements // TODO: rename to findDirectChildren! 432 function findDirectChildren(parent, selector) { 433 let parents = parent instanceof HTMLElement ? [parent] : parent; 434 let allMatches = []; 435 for (let i = 0; i < parents.length; i += 1) { 436 let childNodes = parents[i].children; // only ever elements 437 for (let j = 0; j < childNodes.length; j += 1) { 438 let childNode = childNodes[j]; 439 if (!selector || elementMatches(childNode, selector)) { 440 allMatches.push(childNode); 441 } 442 } 443 } 444 return allMatches; 445 } 446 // Style 447 // ---------------------------------------------------------------------------------------------------------------- 448 const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; 449 function applyStyle(el, props) { 450 for (let propName in props) { 451 applyStyleProp(el, propName, props[propName]); 452 } 453 } 454 function applyStyleProp(el, name, val) { 455 if (val == null) { 456 el.style[name] = ''; 457 } 458 else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { 459 el.style[name] = `${val}px`; 460 } 461 else { 462 el.style[name] = val; 463 } 464 } 465 // Event Handling 466 // ---------------------------------------------------------------------------------------------------------------- 467 // if intercepting bubbled events at the document/window/body level, 468 // and want to see originating element (the 'target'), use this util instead 469 // of `ev.target` because it goes within web-component boundaries. 470 function getEventTargetViaRoot(ev) { 471 var _a, _b; 472 return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; 473 } 474 // Shadow DOM consuderations 475 // ---------------------------------------------------------------------------------------------------------------- 476 function getElRoot(el) { 477 return el.getRootNode ? el.getRootNode() : document; 478 } 479 // Unique ID for DOM attribute 480 let guid$1 = 0; 481 function getUniqueDomId() { 482 guid$1 += 1; 483 return 'fc-dom-' + guid$1; 484 } 485 486 // Stops a mouse/touch event from doing it's native browser action 487 function preventDefault(ev) { 488 ev.preventDefault(); 489 } 490 // Event Delegation 491 // ---------------------------------------------------------------------------------------------------------------- 492 function buildDelegationHandler(selector, handler) { 493 return (ev) => {
vendor: 14,727 bytes, lines 494-826
494 let matchedChild = elementClosest(ev.target, selector); 495 if (matchedChild) { 496 handler.call(matchedChild, ev, matchedChild); 497 } 498 }; 499 } 500 function listenBySelector(container, eventType, selector, handler) { 501 let attachedHandler = buildDelegationHandler(selector, handler); 502 container.addEventListener(eventType, attachedHandler); 503 return () => { 504 container.removeEventListener(eventType, attachedHandler); 505 }; 506 } 507 function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { 508 let currentMatchedChild; 509 return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => { 510 if (matchedChild !== currentMatchedChild) { 511 currentMatchedChild = matchedChild; 512 onMouseEnter(mouseOverEv, matchedChild); 513 let realOnMouseLeave = (mouseLeaveEv) => { 514 currentMatchedChild = null; 515 onMouseLeave(mouseLeaveEv, matchedChild); 516 matchedChild.removeEventListener('mouseleave', realOnMouseLeave); 517 }; 518 // listen to the next mouseleave, and then unattach 519 matchedChild.addEventListener('mouseleave', realOnMouseLeave); 520 } 521 }); 522 } 523 // Animation 524 // ---------------------------------------------------------------------------------------------------------------- 525 const transitionEventNames = [ 526 'webkitTransitionEnd', 527 'otransitionend', 528 'oTransitionEnd', 529 'msTransitionEnd', 530 'transitionend', 531 ]; 532 // triggered only when the next single subsequent transition finishes 533 function whenTransitionDone(el, callback) { 534 let realCallback = (ev) => { 535 callback(ev); 536 transitionEventNames.forEach((eventName) => { 537 el.removeEventListener(eventName, realCallback); 538 }); 539 }; 540 transitionEventNames.forEach((eventName) => { 541 el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes 542 }); 543 } 544 // ARIA workarounds 545 // ---------------------------------------------------------------------------------------------------------------- 546 function createAriaClickAttrs(handler) { 547 return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler)); 548 } 549 function createAriaKeyboardAttrs(handler) { 550 return { 551 tabIndex: 0, 552 onKeyDown(ev) { 553 if (ev.key === 'Enter' || ev.key === ' ') { 554 handler(ev); 555 ev.preventDefault(); // if space, don't scroll down page 556 } 557 }, 558 }; 559 } 560 561 let guidNumber = 0; 562 function guid() { 563 guidNumber += 1; 564 return String(guidNumber); 565 } 566 /* FullCalendar-specific DOM Utilities 567 ----------------------------------------------------------------------------------------------------------------------*/ 568 // Make the mouse cursor express that an event is not allowed in the current area 569 function disableCursor() { 570 document.body.classList.add('fc-not-allowed'); 571 } 572 // Returns the mouse cursor to its original look 573 function enableCursor() { 574 document.body.classList.remove('fc-not-allowed'); 575 } 576 /* Selection 577 ----------------------------------------------------------------------------------------------------------------------*/ 578 function preventSelection(el) { 579 el.classList.add('fc-unselectable'); 580 el.addEventListener('selectstart', preventDefault); 581 } 582 function allowSelection(el) { 583 el.classList.remove('fc-unselectable'); 584 el.removeEventListener('selectstart', preventDefault); 585 } 586 /* Context Menu 587 ----------------------------------------------------------------------------------------------------------------------*/ 588 function preventContextMenu(el) { 589 el.addEventListener('contextmenu', preventDefault); 590 } 591 function allowContextMenu(el) { 592 el.removeEventListener('contextmenu', preventDefault); 593 } 594 function parseFieldSpecs(input) { 595 let specs = []; 596 let tokens = []; 597 let i; 598 let token; 599 if (typeof input === 'string') { 600 tokens = input.split(/\s*,\s*/); 601 } 602 else if (typeof input === 'function') { 603 tokens = [input]; 604 } 605 else if (Array.isArray(input)) { 606 tokens = input; 607 } 608 for (i = 0; i < tokens.length; i += 1) { 609 token = tokens[i]; 610 if (typeof token === 'string') { 611 specs.push(token.charAt(0) === '-' ? 612 { field: token.substring(1), order: -1 } : 613 { field: token, order: 1 }); 614 } 615 else if (typeof token === 'function') { 616 specs.push({ func: token }); 617 } 618 } 619 return specs; 620 } 621 function compareByFieldSpecs(obj0, obj1, fieldSpecs) { 622 let i; 623 let cmp; 624 for (i = 0; i < fieldSpecs.length; i += 1) { 625 cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); 626 if (cmp) { 627 return cmp; 628 } 629 } 630 return 0; 631 } 632 function compareByFieldSpec(obj0, obj1, fieldSpec) { 633 if (fieldSpec.func) { 634 return fieldSpec.func(obj0, obj1); 635 } 636 return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) 637 * (fieldSpec.order || 1); 638 } 639 function flexibleCompare(a, b) { 640 if (!a && !b) { 641 return 0; 642 } 643 if (b == null) { 644 return -1; 645 } 646 if (a == null) { 647 return 1; 648 } 649 if (typeof a === 'string' || typeof b === 'string') { 650 return String(a).localeCompare(String(b)); 651 } 652 return a - b; 653 } 654 /* String Utilities 655 ----------------------------------------------------------------------------------------------------------------------*/ 656 function padStart(val, len) { 657 let s = String(val); 658 return '000'.substr(0, len - s.length) + s; 659 } 660 function formatWithOrdinals(formatter, args, fallbackText) { 661 if (typeof formatter === 'function') { 662 return formatter(...args); 663 } 664 if (typeof formatter === 'string') { // non-blank string 665 return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter); 666 } 667 return fallbackText; 668 } 669 /* Number Utilities 670 ----------------------------------------------------------------------------------------------------------------------*/ 671 function compareNumbers(a, b) { 672 return a - b; 673 } 674 function isInt(n) { 675 return n % 1 === 0; 676 } 677 /* FC-specific DOM dimension stuff 678 ----------------------------------------------------------------------------------------------------------------------*/ 679 function computeSmallestCellWidth(cellEl) { 680 let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); 681 let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); 682 if (!allWidthEl) { 683 throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const 684 } 685 if (!contentWidthEl) { 686 throw new Error('needs fc-scrollgrid-shrink-cushion className'); 687 } 688 return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border 689 contentWidthEl.getBoundingClientRect().width; 690 } 691 692 // TODO: easier way to add new hooks? need to update a million things 693 function createPlugin(input) { 694 return { 695 id: guid(), 696 name: input.name, 697 premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined, 698 deps: input.deps || [], 699 reducers: input.reducers || [], 700 isLoadingFuncs: input.isLoadingFuncs || [], 701 contextInit: [].concat(input.contextInit || []), 702 eventRefiners: input.eventRefiners || {}, 703 eventDefMemberAdders: input.eventDefMemberAdders || [], 704 eventSourceRefiners: input.eventSourceRefiners || {}, 705 isDraggableTransformers: input.isDraggableTransformers || [], 706 eventDragMutationMassagers: input.eventDragMutationMassagers || [], 707 eventDefMutationAppliers: input.eventDefMutationAppliers || [], 708 dateSelectionTransformers: input.dateSelectionTransformers || [], 709 datePointTransforms: input.datePointTransforms || [], 710 dateSpanTransforms: input.dateSpanTransforms || [], 711 views: input.views || {}, 712 viewPropsTransformers: input.viewPropsTransformers || [], 713 isPropsValid: input.isPropsValid || null, 714 externalDefTransforms: input.externalDefTransforms || [], 715 viewContainerAppends: input.viewContainerAppends || [], 716 eventDropTransformers: input.eventDropTransformers || [], 717 componentInteractions: input.componentInteractions || [], 718 calendarInteractions: input.calendarInteractions || [], 719 themeClasses: input.themeClasses || {}, 720 eventSourceDefs: input.eventSourceDefs || [], 721 cmdFormatter: input.cmdFormatter, 722 recurringTypes: input.recurringTypes || [], 723 namedTimeZonedImpl: input.namedTimeZonedImpl, 724 initialView: input.initialView || '', 725 elementDraggingImpl: input.elementDraggingImpl, 726 optionChangeHandlers: input.optionChangeHandlers || {}, 727 scrollGridImpl: input.scrollGridImpl || null, 728 listenerRefiners: input.listenerRefiners || {}, 729 optionRefiners: input.optionRefiners || {}, 730 propSetHandlers: input.propSetHandlers || {}, 731 }; 732 } 733 function buildPluginHooks(pluginDefs, globalDefs) { 734 let currentPluginIds = {}; 735 let hooks = { 736 premiumReleaseDate: undefined, 737 reducers: [], 738 isLoadingFuncs: [], 739 contextInit: [], 740 eventRefiners: {}, 741 eventDefMemberAdders: [], 742 eventSourceRefiners: {}, 743 isDraggableTransformers: [], 744 eventDragMutationMassagers: [], 745 eventDefMutationAppliers: [], 746 dateSelectionTransformers: [], 747 datePointTransforms: [], 748 dateSpanTransforms: [], 749 views: {}, 750 viewPropsTransformers: [], 751 isPropsValid: null, 752 externalDefTransforms: [], 753 viewContainerAppends: [], 754 eventDropTransformers: [], 755 componentInteractions: [], 756 calendarInteractions: [], 757 themeClasses: {}, 758 eventSourceDefs: [], 759 cmdFormatter: null, 760 recurringTypes: [], 761 namedTimeZonedImpl: null, 762 initialView: '', 763 elementDraggingImpl: null, 764 optionChangeHandlers: {}, 765 scrollGridImpl: null, 766 listenerRefiners: {}, 767 optionRefiners: {}, 768 propSetHandlers: {}, 769 }; 770 function addDefs(defs) { 771 for (let def of defs) { 772 const pluginName = def.name; 773 const currentId = currentPluginIds[pluginName]; 774 if (currentId === undefined) { 775 currentPluginIds[pluginName] = def.id; 776 addDefs(def.deps); 777 hooks = combineHooks(hooks, def); 778 } 779 else if (currentId !== def.id) { 780 // different ID than the one already added 781 console.warn(`Duplicate plugin '${pluginName}'`); 782 } 783 } 784 } 785 if (pluginDefs) { 786 addDefs(pluginDefs); 787 } 788 addDefs(globalDefs); 789 return hooks; 790 } 791 function buildBuildPluginHooks() { 792 let currentOverrideDefs = []; 793 let currentGlobalDefs = []; 794 let currentHooks; 795 return (overrideDefs, globalDefs) => { 796 if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { 797 currentHooks = buildPluginHooks(overrideDefs, globalDefs); 798 } 799 currentOverrideDefs = overrideDefs; 800 currentGlobalDefs = globalDefs; 801 return currentHooks; 802 }; 803 } 804 function combineHooks(hooks0, hooks1) { 805 return { 806 premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate), 807 reducers: hooks0.reducers.concat(hooks1.reducers), 808 isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), 809 contextInit: hooks0.contextInit.concat(hooks1.contextInit), 810 eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners), 811 eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), 812 eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), 813 isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), 814 eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), 815 eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), 816 dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), 817 datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), 818 dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), 819 views: Object.assign(Object.assign({}, hooks0.views), hooks1.views), 820 viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), 821 isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, 822 externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), 823 viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), 824 eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), 825 calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), 826 componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
vendor: 52,447 bytes, lines 827-2246
827 themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses), 828 eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), 829 cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, 830 recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), 831 namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, 832 initialView: hooks0.initialView || hooks1.initialView, 833 elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, 834 optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), 835 scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, 836 listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), 837 optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners), 838 propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), 839 }; 840 } 841 function compareOptionalDates(date0, date1) { 842 if (date0 === undefined) { 843 return date1; 844 } 845 if (date1 === undefined) { 846 return date0; 847 } 848 return new Date(Math.max(date0.valueOf(), date1.valueOf())); 849 } 850 851 const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; 852 // Adding 853 function addWeeks(m, n) { 854 let a = dateToUtcArray(m); 855 a[2] += n * 7; 856 return arrayToUtcDate(a); 857 } 858 function addDays(m, n) { 859 let a = dateToUtcArray(m); 860 a[2] += n; 861 return arrayToUtcDate(a); 862 } 863 function addMs(m, n) { 864 let a = dateToUtcArray(m); 865 a[6] += n; 866 return arrayToUtcDate(a); 867 } 868 // Diffing (all return floats) 869 // TODO: why not use ranges? 870 function diffWeeks(m0, m1) { 871 return diffDays(m0, m1) / 7; 872 } 873 function diffDays(m0, m1) { 874 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); 875 } 876 function diffHours(m0, m1) { 877 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); 878 } 879 function diffMinutes(m0, m1) { 880 return (m1.valueOf() - m0.valueOf()) / (1000 * 60); 881 } 882 function diffSeconds(m0, m1) { 883 return (m1.valueOf() - m0.valueOf()) / 1000; 884 } 885 function diffDayAndTime(m0, m1) { 886 let m0day = startOfDay(m0); 887 let m1day = startOfDay(m1); 888 return { 889 years: 0, 890 months: 0, 891 days: Math.round(diffDays(m0day, m1day)), 892 milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), 893 }; 894 } 895 // Diffing Whole Units 896 function diffWholeWeeks(m0, m1) { 897 let d = diffWholeDays(m0, m1); 898 if (d !== null && d % 7 === 0) { 899 return d / 7; 900 } 901 return null; 902 } 903 function diffWholeDays(m0, m1) { 904 if (timeAsMs(m0) === timeAsMs(m1)) { 905 return Math.round(diffDays(m0, m1)); 906 } 907 return null; 908 } 909 // Start-Of 910 function startOfDay(m) { 911 return arrayToUtcDate([ 912 m.getUTCFullYear(), 913 m.getUTCMonth(), 914 m.getUTCDate(), 915 ]); 916 } 917 function startOfHour(m) { 918 return arrayToUtcDate([ 919 m.getUTCFullYear(), 920 m.getUTCMonth(), 921 m.getUTCDate(), 922 m.getUTCHours(), 923 ]); 924 } 925 function startOfMinute(m) { 926 return arrayToUtcDate([ 927 m.getUTCFullYear(), 928 m.getUTCMonth(), 929 m.getUTCDate(), 930 m.getUTCHours(), 931 m.getUTCMinutes(), 932 ]); 933 } 934 function startOfSecond(m) { 935 return arrayToUtcDate([ 936 m.getUTCFullYear(), 937 m.getUTCMonth(), 938 m.getUTCDate(), 939 m.getUTCHours(), 940 m.getUTCMinutes(), 941 m.getUTCSeconds(), 942 ]); 943 } 944 // Week Computation 945 function weekOfYear(marker, dow, doy) { 946 let y = marker.getUTCFullYear(); 947 let w = weekOfGivenYear(marker, y, dow, doy); 948 if (w < 1) { 949 return weekOfGivenYear(marker, y - 1, dow, doy); 950 } 951 let nextW = weekOfGivenYear(marker, y + 1, dow, doy); 952 if (nextW >= 1) { 953 return Math.min(w, nextW); 954 } 955 return w; 956 } 957 function weekOfGivenYear(marker, year, dow, doy) { 958 let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); 959 let dayStart = startOfDay(marker); 960 let days = Math.round(diffDays(firstWeekStart, dayStart)); 961 return Math.floor(days / 7) + 1; // zero-indexed 962 } 963 // start-of-first-week - start-of-year 964 function firstWeekOffset(year, dow, doy) { 965 // first-week day -- which january is always in the first week (4 for iso, 1 for other) 966 let fwd = 7 + dow - doy; 967 // first-week day local weekday -- which local weekday is fwd 968 let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; 969 return -fwdlw + fwd - 1; 970 } 971 // Array Conversion 972 function dateToLocalArray(date) { 973 return [ 974 date.getFullYear(), 975 date.getMonth(), 976 date.getDate(), 977 date.getHours(), 978 date.getMinutes(), 979 date.getSeconds(), 980 date.getMilliseconds(), 981 ]; 982 } 983 function arrayToLocalDate(a) { 984 return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month 985 a[3] || 0, a[4] || 0, a[5] || 0); 986 } 987 function dateToUtcArray(date) { 988 return [ 989 date.getUTCFullYear(), 990 date.getUTCMonth(), 991 date.getUTCDate(), 992 date.getUTCHours(), 993 date.getUTCMinutes(), 994 date.getUTCSeconds(), 995 date.getUTCMilliseconds(), 996 ]; 997 } 998 function arrayToUtcDate(a) { 999 // according to web standards (and Safari), a month index is required. 1000 // massage if only given a year. 1001 if (a.length === 1) { 1002 a = a.concat([0]); 1003 } 1004 return new Date(Date.UTC(...a)); 1005 } 1006 // Other Utils 1007 function isValidDate(m) { 1008 return !isNaN(m.valueOf()); 1009 } 1010 function timeAsMs(m) { 1011 return m.getUTCHours() * 1000 * 60 * 60 + 1012 m.getUTCMinutes() * 1000 * 60 + 1013 m.getUTCSeconds() * 1000 + 1014 m.getUTCMilliseconds(); 1015 } 1016 1017 let calendarSystemClassMap = {}; 1018 function registerCalendarSystem(name, theClass) { 1019 calendarSystemClassMap[name] = theClass; 1020 } 1021 function createCalendarSystem(name) { 1022 return new calendarSystemClassMap[name](); 1023 } 1024 class GregorianCalendarSystem { 1025 getMarkerYear(d) { 1026 return d.getUTCFullYear(); 1027 } 1028 getMarkerMonth(d) { 1029 return d.getUTCMonth(); 1030 } 1031 getMarkerDay(d) { 1032 return d.getUTCDate(); 1033 } 1034 arrayToMarker(arr) { 1035 return arrayToUtcDate(arr); 1036 } 1037 markerToArray(marker) { 1038 return dateToUtcArray(marker); 1039 } 1040 } 1041 registerCalendarSystem('gregory', GregorianCalendarSystem); 1042 1043 const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; 1044 const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; 1045 // Parsing and Creation 1046 function createDuration(input, unit) { 1047 if (typeof input === 'string') { 1048 return parseString(input); 1049 } 1050 if (typeof input === 'object' && input) { // non-null object 1051 return parseObject(input); 1052 } 1053 if (typeof input === 'number') { 1054 return parseObject({ [unit || 'milliseconds']: input }); 1055 } 1056 return null; 1057 } 1058 function parseString(s) { 1059 let m = PARSE_RE.exec(s); 1060 if (m) { 1061 let sign = m[1] ? -1 : 1; 1062 return { 1063 years: 0, 1064 months: 0, 1065 days: sign * (m[2] ? parseInt(m[2], 10) : 0), 1066 milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours 1067 (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes 1068 (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds 1069 (m[6] ? parseInt(m[6], 10) : 0) // ms 1070 ), 1071 }; 1072 } 1073 return null; 1074 } 1075 function parseObject(obj) { 1076 let duration = { 1077 years: obj.years || obj.year || 0, 1078 months: obj.months || obj.month || 0, 1079 days: obj.days || obj.day || 0, 1080 milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours 1081 (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes 1082 (obj.seconds || obj.second || 0) * 1000 + // seconds 1083 (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms 1084 }; 1085 let weeks = obj.weeks || obj.week; 1086 if (weeks) { 1087 duration.days += weeks * 7; 1088 duration.specifiedWeeks = true; 1089 } 1090 return duration; 1091 } 1092 // Equality 1093 function durationsEqual(d0, d1) { 1094 return d0.years === d1.years && 1095 d0.months === d1.months && 1096 d0.days === d1.days && 1097 d0.milliseconds === d1.milliseconds; 1098 } 1099 function asCleanDays(dur) { 1100 if (!dur.years && !dur.months && !dur.milliseconds) { 1101 return dur.days; 1102 } 1103 return 0; 1104 } 1105 // Simple Math 1106 function addDurations(d0, d1) { 1107 return { 1108 years: d0.years + d1.years, 1109 months: d0.months + d1.months, 1110 days: d0.days + d1.days, 1111 milliseconds: d0.milliseconds + d1.milliseconds, 1112 }; 1113 } 1114 function subtractDurations(d1, d0) { 1115 return { 1116 years: d1.years - d0.years, 1117 months: d1.months - d0.months, 1118 days: d1.days - d0.days, 1119 milliseconds: d1.milliseconds - d0.milliseconds, 1120 }; 1121 } 1122 function multiplyDuration(d, n) { 1123 return { 1124 years: d.years * n, 1125 months: d.months * n, 1126 days: d.days * n, 1127 milliseconds: d.milliseconds * n, 1128 }; 1129 } 1130 // Conversions 1131 // "Rough" because they are based on average-case Gregorian months/years 1132 function asRoughYears(dur) { 1133 return asRoughDays(dur) / 365; 1134 } 1135 function asRoughMonths(dur) { 1136 return asRoughDays(dur) / 30; 1137 } 1138 function asRoughDays(dur) { 1139 return asRoughMs(dur) / 864e5; 1140 } 1141 function asRoughMinutes(dur) { 1142 return asRoughMs(dur) / (1000 * 60); 1143 } 1144 function asRoughSeconds(dur) { 1145 return asRoughMs(dur) / 1000; 1146 } 1147 function asRoughMs(dur) { 1148 return dur.years * (365 * 864e5) + 1149 dur.months * (30 * 864e5) + 1150 dur.days * 864e5 + 1151 dur.milliseconds; 1152 } 1153 // Advanced Math 1154 function wholeDivideDurations(numerator, denominator) { 1155 let res = null; 1156 for (let i = 0; i < INTERNAL_UNITS.length; i += 1) { 1157 let unit = INTERNAL_UNITS[i]; 1158 if (denominator[unit]) { 1159 let localRes = numerator[unit] / denominator[unit]; 1160 if (!isInt(localRes) || (res !== null && res !== localRes)) { 1161 return null; 1162 } 1163 res = localRes; 1164 } 1165 else if (numerator[unit]) { 1166 // needs to divide by something but can't! 1167 return null; 1168 } 1169 } 1170 return res; 1171 } 1172 function greatestDurationDenominator(dur) { 1173 let ms = dur.milliseconds; 1174 if (ms) { 1175 if (ms % 1000 !== 0) { 1176 return { unit: 'millisecond', value: ms }; 1177 } 1178 if (ms % (1000 * 60) !== 0) { 1179 return { unit: 'second', value: ms / 1000 }; 1180 } 1181 if (ms % (1000 * 60 * 60) !== 0) { 1182 return { unit: 'minute', value: ms / (1000 * 60) }; 1183 } 1184 if (ms) { 1185 return { unit: 'hour', value: ms / (1000 * 60 * 60) }; 1186 } 1187 } 1188 if (dur.days) { 1189 if (dur.specifiedWeeks && dur.days % 7 === 0) { 1190 return { unit: 'week', value: dur.days / 7 }; 1191 } 1192 return { unit: 'day', value: dur.days }; 1193 } 1194 if (dur.months) { 1195 return { unit: 'month', value: dur.months }; 1196 } 1197 if (dur.years) { 1198 return { unit: 'year', value: dur.years }; 1199 } 1200 return { unit: 'millisecond', value: 0 }; 1201 } 1202 1203 // timeZoneOffset is in minutes 1204 function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) { 1205 let s = marker.toISOString(); 1206 s = s.replace('.000', ''); 1207 if (stripZeroTime) { 1208 s = s.replace('T00:00:00Z', ''); 1209 } 1210 if (s.length > 10) { // time part wasn't stripped, can add timezone info 1211 if (timeZoneOffset == null) { 1212 s = s.replace('Z', ''); 1213 } 1214 else if (timeZoneOffset !== 0) { 1215 s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); 1216 } 1217 // otherwise, its UTC-0 and we want to keep the Z 1218 } 1219 return s; 1220 } 1221 // formats the date, but with no time part 1222 // TODO: somehow merge with buildIsoString and stripZeroTime 1223 // TODO: rename. omit "string" 1224 function formatDayString(marker) { 1225 return marker.toISOString().replace(/T.*$/, ''); 1226 } 1227 // TODO: use Date::toISOString and use everything after the T? 1228 function formatIsoTimeString(marker) { 1229 return padStart(marker.getUTCHours(), 2) + ':' + 1230 padStart(marker.getUTCMinutes(), 2) + ':' + 1231 padStart(marker.getUTCSeconds(), 2); 1232 } 1233 function formatTimeZoneOffset(minutes, doIso = false) { 1234 let sign = minutes < 0 ? '-' : '+'; 1235 let abs = Math.abs(minutes); 1236 let hours = Math.floor(abs / 60); 1237 let mins = Math.round(abs % 60); 1238 if (doIso) { 1239 return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`; 1240 } 1241 return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`; 1242 } 1243 1244 const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; 1245 function parse(str) { 1246 let m = ISO_RE.exec(str); 1247 if (m) { 1248 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)); 1249 if (isValidDate(marker)) { 1250 let timeZoneOffset = null; 1251 if (m[13]) { 1252 timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + 1253 Number(m[18] || 0)); 1254 } 1255 return { 1256 marker, 1257 isTimeUnspecified: !m[6], 1258 timeZoneOffset, 1259 }; 1260 } 1261 } 1262 return null; 1263 } 1264 1265 class DateEnv { 1266 constructor(settings) { 1267 let timeZone = this.timeZone = settings.timeZone; 1268 let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; 1269 if (settings.namedTimeZoneImpl && isNamedTimeZone) { 1270 this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); 1271 } 1272 this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); 1273 this.calendarSystem = createCalendarSystem(settings.calendarSystem); 1274 this.locale = settings.locale; 1275 this.weekDow = settings.locale.week.dow; 1276 this.weekDoy = settings.locale.week.doy; 1277 if (settings.weekNumberCalculation === 'ISO') { 1278 this.weekDow = 1; 1279 this.weekDoy = 4; 1280 } 1281 if (typeof settings.firstDay === 'number') { 1282 this.weekDow = settings.firstDay; 1283 } 1284 if (typeof settings.weekNumberCalculation === 'function') { 1285 this.weekNumberFunc = settings.weekNumberCalculation; 1286 } 1287 this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; 1288 this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText; 1289 this.cmdFormatter = settings.cmdFormatter; 1290 this.defaultSeparator = settings.defaultSeparator; 1291 } 1292 // Creating / Parsing 1293 createMarker(input) { 1294 let meta = this.createMarkerMeta(input); 1295 if (meta === null) { 1296 return null; 1297 } 1298 return meta.marker; 1299 } 1300 createNowMarker() { 1301 if (this.canComputeOffset) { 1302 return this.timestampToMarker(new Date().valueOf()); 1303 } 1304 // if we can't compute the current date val for a timezone, 1305 // better to give the current local date vals than UTC 1306 return arrayToUtcDate(dateToLocalArray(new Date())); 1307 } 1308 createMarkerMeta(input) { 1309 if (typeof input === 'string') { 1310 return this.parse(input); 1311 } 1312 let marker = null; 1313 if (typeof input === 'number') { 1314 marker = this.timestampToMarker(input); 1315 } 1316 else if (input instanceof Date) { 1317 input = input.valueOf(); 1318 if (!isNaN(input)) { 1319 marker = this.timestampToMarker(input); 1320 } 1321 } 1322 else if (Array.isArray(input)) { 1323 marker = arrayToUtcDate(input); 1324 } 1325 if (marker === null || !isValidDate(marker)) { 1326 return null; 1327 } 1328 return { marker, isTimeUnspecified: false, forcedTzo: null }; 1329 } 1330 parse(s) { 1331 let parts = parse(s); 1332 if (parts === null) { 1333 return null; 1334 } 1335 let { marker } = parts; 1336 let forcedTzo = null; 1337 if (parts.timeZoneOffset !== null) { 1338 if (this.canComputeOffset) { 1339 marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); 1340 } 1341 else { 1342 forcedTzo = parts.timeZoneOffset; 1343 } 1344 } 1345 return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo }; 1346 } 1347 // Accessors 1348 getYear(marker) { 1349 return this.calendarSystem.getMarkerYear(marker); 1350 } 1351 getMonth(marker) { 1352 return this.calendarSystem.getMarkerMonth(marker); 1353 } 1354 // Adding / Subtracting 1355 add(marker, dur) { 1356 let a = this.calendarSystem.markerToArray(marker); 1357 a[0] += dur.years; 1358 a[1] += dur.months; 1359 a[2] += dur.days; 1360 a[6] += dur.milliseconds; 1361 return this.calendarSystem.arrayToMarker(a); 1362 } 1363 subtract(marker, dur) { 1364 let a = this.calendarSystem.markerToArray(marker); 1365 a[0] -= dur.years; 1366 a[1] -= dur.months; 1367 a[2] -= dur.days; 1368 a[6] -= dur.milliseconds; 1369 return this.calendarSystem.arrayToMarker(a); 1370 } 1371 addYears(marker, n) { 1372 let a = this.calendarSystem.markerToArray(marker); 1373 a[0] += n; 1374 return this.calendarSystem.arrayToMarker(a); 1375 } 1376 addMonths(marker, n) { 1377 let a = this.calendarSystem.markerToArray(marker); 1378 a[1] += n; 1379 return this.calendarSystem.arrayToMarker(a); 1380 } 1381 // Diffing Whole Units 1382 diffWholeYears(m0, m1) { 1383 let { calendarSystem } = this; 1384 if (timeAsMs(m0) === timeAsMs(m1) && 1385 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && 1386 calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { 1387 return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); 1388 } 1389 return null; 1390 } 1391 diffWholeMonths(m0, m1) { 1392 let { calendarSystem } = this; 1393 if (timeAsMs(m0) === timeAsMs(m1) && 1394 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { 1395 return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + 1396 (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; 1397 } 1398 return null; 1399 } 1400 // Range / Duration 1401 greatestWholeUnit(m0, m1) { 1402 let n = this.diffWholeYears(m0, m1); 1403 if (n !== null) { 1404 return { unit: 'year', value: n }; 1405 } 1406 n = this.diffWholeMonths(m0, m1); 1407 if (n !== null) { 1408 return { unit: 'month', value: n }; 1409 } 1410 n = diffWholeWeeks(m0, m1); 1411 if (n !== null) { 1412 return { unit: 'week', value: n }; 1413 } 1414 n = diffWholeDays(m0, m1); 1415 if (n !== null) { 1416 return { unit: 'day', value: n }; 1417 } 1418 n = diffHours(m0, m1); 1419 if (isInt(n)) { 1420 return { unit: 'hour', value: n }; 1421 } 1422 n = diffMinutes(m0, m1); 1423 if (isInt(n)) { 1424 return { unit: 'minute', value: n }; 1425 } 1426 n = diffSeconds(m0, m1); 1427 if (isInt(n)) { 1428 return { unit: 'second', value: n }; 1429 } 1430 return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; 1431 } 1432 countDurationsBetween(m0, m1, d) { 1433 // TODO: can use greatestWholeUnit 1434 let diff; 1435 if (d.years) { 1436 diff = this.diffWholeYears(m0, m1); 1437 if (diff !== null) { 1438 return diff / asRoughYears(d); 1439 } 1440 } 1441 if (d.months) { 1442 diff = this.diffWholeMonths(m0, m1); 1443 if (diff !== null) { 1444 return diff / asRoughMonths(d); 1445 } 1446 } 1447 if (d.days) { 1448 diff = diffWholeDays(m0, m1); 1449 if (diff !== null) { 1450 return diff / asRoughDays(d); 1451 } 1452 } 1453 return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); 1454 } 1455 // Start-Of 1456 // these DON'T return zoned-dates. only UTC start-of dates 1457 startOf(m, unit) { 1458 if (unit === 'year') { 1459 return this.startOfYear(m); 1460 } 1461 if (unit === 'month') { 1462 return this.startOfMonth(m); 1463 } 1464 if (unit === 'week') { 1465 return this.startOfWeek(m); 1466 } 1467 if (unit === 'day') { 1468 return startOfDay(m); 1469 } 1470 if (unit === 'hour') { 1471 return startOfHour(m); 1472 } 1473 if (unit === 'minute') { 1474 return startOfMinute(m); 1475 } 1476 if (unit === 'second') { 1477 return startOfSecond(m); 1478 } 1479 return null; 1480 } 1481 startOfYear(m) { 1482 return this.calendarSystem.arrayToMarker([ 1483 this.calendarSystem.getMarkerYear(m), 1484 ]); 1485 } 1486 startOfMonth(m) { 1487 return this.calendarSystem.arrayToMarker([ 1488 this.calendarSystem.getMarkerYear(m), 1489 this.calendarSystem.getMarkerMonth(m), 1490 ]); 1491 } 1492 startOfWeek(m) { 1493 return this.calendarSystem.arrayToMarker([ 1494 this.calendarSystem.getMarkerYear(m), 1495 this.calendarSystem.getMarkerMonth(m), 1496 m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), 1497 ]); 1498 } 1499 // Week Number 1500 computeWeekNumber(marker) { 1501 if (this.weekNumberFunc) { 1502 return this.weekNumberFunc(this.toDate(marker)); 1503 } 1504 return weekOfYear(marker, this.weekDow, this.weekDoy); 1505 } 1506 // TODO: choke on timeZoneName: long 1507 format(marker, formatter, dateOptions = {}) { 1508 return formatter.format({ 1509 marker, 1510 timeZoneOffset: dateOptions.forcedTzo != null ? 1511 dateOptions.forcedTzo : 1512 this.offsetForMarker(marker), 1513 }, this); 1514 } 1515 formatRange(start, end, formatter, dateOptions = {}) { 1516 if (dateOptions.isEndExclusive) { 1517 end = addMs(end, -1); 1518 } 1519 return formatter.formatRange({ 1520 marker: start, 1521 timeZoneOffset: dateOptions.forcedStartTzo != null ? 1522 dateOptions.forcedStartTzo : 1523 this.offsetForMarker(start), 1524 }, { 1525 marker: end, 1526 timeZoneOffset: dateOptions.forcedEndTzo != null ? 1527 dateOptions.forcedEndTzo : 1528 this.offsetForMarker(end), 1529 }, this, dateOptions.defaultSeparator); 1530 } 1531 /* 1532 DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, 1533 might as well use buildIsoString or some other util directly 1534 */ 1535 formatIso(marker, extraOptions = {}) { 1536 let timeZoneOffset = null; 1537 if (!extraOptions.omitTimeZoneOffset) { 1538 if (extraOptions.forcedTzo != null) { 1539 timeZoneOffset = extraOptions.forcedTzo; 1540 } 1541 else { 1542 timeZoneOffset = this.offsetForMarker(marker); 1543 } 1544 } 1545 return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); 1546 } 1547 // TimeZone 1548 timestampToMarker(ms) { 1549 if (this.timeZone === 'local') { 1550 return arrayToUtcDate(dateToLocalArray(new Date(ms))); 1551 } 1552 if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { 1553 return new Date(ms); 1554 } 1555 return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); 1556 } 1557 offsetForMarker(m) { 1558 if (this.timeZone === 'local') { 1559 return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset 1560 } 1561 if (this.timeZone === 'UTC') { 1562 return 0; 1563 } 1564 if (this.namedTimeZoneImpl) { 1565 return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); 1566 } 1567 return null; 1568 } 1569 // Conversion 1570 toDate(m, forcedTzo) { 1571 if (this.timeZone === 'local') { 1572 return arrayToLocalDate(dateToUtcArray(m)); 1573 } 1574 if (this.timeZone === 'UTC') { 1575 return new Date(m.valueOf()); // make sure it's a copy 1576 } 1577 if (!this.namedTimeZoneImpl) { 1578 return new Date(m.valueOf() - (forcedTzo || 0)); 1579 } 1580 return new Date(m.valueOf() - 1581 this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); 1582 } 1583 } 1584 1585 class Theme { 1586 constructor(calendarOptions) { 1587 if (this.iconOverrideOption) { 1588 this.setIconOverride(calendarOptions[this.iconOverrideOption]); 1589 } 1590 } 1591 setIconOverride(iconOverrideHash) { 1592 let iconClassesCopy; 1593 let buttonName; 1594 if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object 1595 iconClassesCopy = Object.assign({}, this.iconClasses); 1596 for (buttonName in iconOverrideHash) { 1597 iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); 1598 } 1599 this.iconClasses = iconClassesCopy; 1600 } 1601 else if (iconOverrideHash === false) { 1602 this.iconClasses = {}; 1603 } 1604 } 1605 applyIconOverridePrefix(className) { 1606 let prefix = this.iconOverridePrefix; 1607 if (prefix && className.indexOf(prefix) !== 0) { // if not already present 1608 className = prefix + className; 1609 } 1610 return className; 1611 } 1612 getClass(key) { 1613 return this.classes[key] || ''; 1614 } 1615 getIconClass(buttonName, isRtl) { 1616 let className; 1617 if (isRtl && this.rtlIconClasses) { 1618 className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; 1619 } 1620 else { 1621 className = this.iconClasses[buttonName]; 1622 } 1623 if (className) { 1624 return `${this.baseIconClass} ${className}`; 1625 } 1626 return ''; 1627 } 1628 getCustomButtonIconClass(customButtonProps) { 1629 let className; 1630 if (this.iconOverrideCustomButtonOption) { 1631 className = customButtonProps[this.iconOverrideCustomButtonOption]; 1632 if (className) { 1633 return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`; 1634 } 1635 } 1636 return ''; 1637 } 1638 } 1639 Theme.prototype.classes = {}; 1640 Theme.prototype.iconClasses = {}; 1641 Theme.prototype.baseIconClass = ''; 1642 Theme.prototype.iconOverridePrefix = ''; 1643 1644 const EXTENDED_SETTINGS_AND_SEVERITIES = { 1645 week: 3, 1646 separator: 0, 1647 omitZeroMinute: 0, 1648 meridiem: 0, 1649 omitCommas: 0, 1650 }; 1651 const STANDARD_DATE_PROP_SEVERITIES = { 1652 timeZoneName: 7, 1653 era: 6, 1654 year: 5, 1655 month: 4, 1656 day: 2, 1657 weekday: 2, 1658 hour: 1, 1659 minute: 1, 1660 second: 1, 1661 }; 1662 const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too 1663 const COMMA_RE = /,/g; // we need re for globalness 1664 const MULTI_SPACE_RE = /\s+/g; 1665 const LTR_RE = /\u200e/g; // control character 1666 const UTC_RE = /UTC|GMT/; 1667 class NativeFormatter { 1668 constructor(formatSettings) { 1669 let standardDateProps = {}; 1670 let extendedSettings = {}; 1671 let severity = 0; 1672 for (let name in formatSettings) { 1673 if (name in EXTENDED_SETTINGS_AND_SEVERITIES) { 1674 extendedSettings[name] = formatSettings[name]; 1675 severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name], severity); 1676 } 1677 else { 1678 standardDateProps[name] = formatSettings[name]; 1679 if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity 1680 severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name], severity); 1681 } 1682 } 1683 } 1684 this.standardDateProps = standardDateProps; 1685 this.extendedSettings = extendedSettings; 1686 this.severity = severity; 1687 this.buildFormattingFunc = memoize(buildFormattingFunc); 1688 } 1689 format(date, context) { 1690 return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); 1691 } 1692 formatRange(start, end, context, betterDefaultSeparator) { 1693 let { standardDateProps, extendedSettings } = this; 1694 let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); 1695 if (!diffSeverity) { 1696 return this.format(start, context); 1697 } 1698 let biggestUnitForPartial = diffSeverity; 1699 if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time 1700 (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && 1701 (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && 1702 (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { 1703 biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time 1704 } 1705 let full0 = this.format(start, context); 1706 let full1 = this.format(end, context); 1707 if (full0 === full1) { 1708 return full0; 1709 } 1710 let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); 1711 let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); 1712 let partial0 = partialFormattingFunc(start); 1713 let partial1 = partialFormattingFunc(end); 1714 let insertion = findCommonInsertion(full0, partial0, full1, partial1); 1715 let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; 1716 if (insertion) { 1717 return insertion.before + partial0 + separator + partial1 + insertion.after; 1718 } 1719 return full0 + separator + full1; 1720 } 1721 getLargestUnit() { 1722 switch (this.severity) { 1723 case 7: 1724 case 6: 1725 case 5: 1726 return 'year'; 1727 case 4: 1728 return 'month'; 1729 case 3: 1730 return 'week'; 1731 case 2: 1732 return 'day'; 1733 default: 1734 return 'time'; // really? 1735 } 1736 } 1737 } 1738 function buildFormattingFunc(standardDateProps, extendedSettings, context) { 1739 let standardDatePropCnt = Object.keys(standardDateProps).length; 1740 if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { 1741 return (date) => (formatTimeZoneOffset(date.timeZoneOffset)); 1742 } 1743 if (standardDatePropCnt === 0 && extendedSettings.week) { 1744 return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week)); 1745 } 1746 return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); 1747 } 1748 function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { 1749 standardDateProps = Object.assign({}, standardDateProps); // copy 1750 extendedSettings = Object.assign({}, extendedSettings); // copy 1751 sanitizeSettings(standardDateProps, extendedSettings); 1752 standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers 1753 let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); 1754 let zeroFormat; // needed? 1755 if (extendedSettings.omitZeroMinute) { 1756 let zeroProps = Object.assign({}, standardDateProps); 1757 delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings 1758 zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); 1759 } 1760 return (date) => { 1761 let { marker } = date; 1762 let format; 1763 if (zeroFormat && !marker.getUTCMinutes()) { 1764 format = zeroFormat; 1765 } 1766 else { 1767 format = normalFormat; 1768 } 1769 let s = format.format(marker); 1770 return postProcess(s, date, standardDateProps, extendedSettings, context); 1771 }; 1772 } 1773 function sanitizeSettings(standardDateProps, extendedSettings) { 1774 // deal with a browser inconsistency where formatting the timezone 1775 // requires that the hour/minute be present. 1776 if (standardDateProps.timeZoneName) { 1777 if (!standardDateProps.hour) { 1778 standardDateProps.hour = '2-digit'; 1779 } 1780 if (!standardDateProps.minute) { 1781 standardDateProps.minute = '2-digit'; 1782 } 1783 } 1784 // only support short timezone names 1785 if (standardDateProps.timeZoneName === 'long') { 1786 standardDateProps.timeZoneName = 'short'; 1787 } 1788 // if requesting to display seconds, MUST display minutes 1789 if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { 1790 delete extendedSettings.omitZeroMinute; 1791 } 1792 } 1793 function postProcess(s, date, standardDateProps, extendedSettings, context) { 1794 s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes 1795 if (standardDateProps.timeZoneName === 'short') { 1796 s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? 1797 'UTC' : // important to normalize for IE, which does "GMT" 1798 formatTimeZoneOffset(date.timeZoneOffset)); 1799 } 1800 if (extendedSettings.omitCommas) { 1801 s = s.replace(COMMA_RE, '').trim(); 1802 } 1803 if (extendedSettings.omitZeroMinute) { 1804 s = s.replace(':00', ''); // zeroFormat doesn't always achieve this 1805 } 1806 // ^ do anything that might create adjacent spaces before this point, 1807 // because MERIDIEM_RE likes to eat up loading spaces 1808 if (extendedSettings.meridiem === false) { 1809 s = s.replace(MERIDIEM_RE, '').trim(); 1810 } 1811 else if (extendedSettings.meridiem === 'narrow') { // a/p 1812 s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase()); 1813 } 1814 else if (extendedSettings.meridiem === 'short') { // am/pm 1815 s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`); 1816 } 1817 else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase 1818 s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase()); 1819 } 1820 s = s.replace(MULTI_SPACE_RE, ' '); 1821 s = s.trim(); 1822 return s; 1823 } 1824 function injectTzoStr(s, tzoStr) { 1825 let replaced = false; 1826 s = s.replace(UTC_RE, () => { 1827 replaced = true; 1828 return tzoStr; 1829 }); 1830 // IE11 doesn't include UTC/GMT in the original string, so append to end 1831 if (!replaced) { 1832 s += ` ${tzoStr}`; 1833 } 1834 return s; 1835 } 1836 function formatWeekNumber(num, weekText, weekTextLong, locale, display) { 1837 let parts = []; 1838 if (display === 'long') { 1839 parts.push(weekTextLong); 1840 } 1841 else if (display === 'short' || display === 'narrow') { 1842 parts.push(weekText); 1843 } 1844 if (display === 'long' || display === 'short') { 1845 parts.push(' '); 1846 } 1847 parts.push(locale.simpleNumberFormat.format(num)); 1848 if (locale.options.direction === 'rtl') { // TODO: use control characters instead? 1849 parts.reverse(); 1850 } 1851 return parts.join(''); 1852 } 1853 // Range Formatting Utils 1854 // 0 = exactly the same 1855 // 1 = different by time 1856 // and bigger 1857 function computeMarkerDiffSeverity(d0, d1, ca) { 1858 if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { 1859 return 5; 1860 } 1861 if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { 1862 return 4; 1863 } 1864 if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { 1865 return 2; 1866 } 1867 if (timeAsMs(d0) !== timeAsMs(d1)) { 1868 return 1; 1869 } 1870 return 0; 1871 } 1872 function computePartialFormattingOptions(options, biggestUnit) { 1873 let partialOptions = {}; 1874 for (let name in options) { 1875 if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) 1876 STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) { 1877 partialOptions[name] = options[name]; 1878 } 1879 } 1880 return partialOptions; 1881 } 1882 function findCommonInsertion(full0, partial0, full1, partial1) { 1883 let i0 = 0; 1884 while (i0 < full0.length) { 1885 let found0 = full0.indexOf(partial0, i0); 1886 if (found0 === -1) { 1887 break; 1888 } 1889 let before0 = full0.substr(0, found0); 1890 i0 = found0 + partial0.length; 1891 let after0 = full0.substr(i0); 1892 let i1 = 0; 1893 while (i1 < full1.length) { 1894 let found1 = full1.indexOf(partial1, i1); 1895 if (found1 === -1) { 1896 break; 1897 } 1898 let before1 = full1.substr(0, found1); 1899 i1 = found1 + partial1.length; 1900 let after1 = full1.substr(i1); 1901 if (before0 === before1 && after0 === after1) { 1902 return { 1903 before: before0, 1904 after: after0, 1905 }; 1906 } 1907 } 1908 } 1909 return null; 1910 } 1911 1912 function expandZonedMarker(dateInfo, calendarSystem) { 1913 let a = calendarSystem.markerToArray(dateInfo.marker); 1914 return { 1915 marker: dateInfo.marker, 1916 timeZoneOffset: dateInfo.timeZoneOffset, 1917 array: a, 1918 year: a[0], 1919 month: a[1], 1920 day: a[2], 1921 hour: a[3], 1922 minute: a[4], 1923 second: a[5], 1924 millisecond: a[6], 1925 }; 1926 } 1927 1928 function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { 1929 let startInfo = expandZonedMarker(start, context.calendarSystem); 1930 let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; 1931 return { 1932 date: startInfo, 1933 start: startInfo, 1934 end: endInfo, 1935 timeZone: context.timeZone, 1936 localeCodes: context.locale.codes, 1937 defaultSeparator: betterDefaultSeparator || context.defaultSeparator, 1938 }; 1939 } 1940 1941 /* 1942 TODO: fix the terminology of "formatter" vs "formatting func" 1943 */ 1944 /* 1945 At the time of instantiation, this object does not know which cmd-formatting system it will use. 1946 It receives this at the time of formatting, as a setting. 1947 */ 1948 class CmdFormatter { 1949 constructor(cmdStr) { 1950 this.cmdStr = cmdStr; 1951 } 1952 format(date, context, betterDefaultSeparator) { 1953 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); 1954 } 1955 formatRange(start, end, context, betterDefaultSeparator) { 1956 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); 1957 } 1958 } 1959 1960 class FuncFormatter { 1961 constructor(func) { 1962 this.func = func; 1963 } 1964 format(date, context, betterDefaultSeparator) { 1965 return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); 1966 } 1967 formatRange(start, end, context, betterDefaultSeparator) { 1968 return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); 1969 } 1970 } 1971 1972 function createFormatter(input) { 1973 if (typeof input === 'object' && input) { // non-null object 1974 return new NativeFormatter(input); 1975 } 1976 if (typeof input === 'string') { 1977 return new CmdFormatter(input); 1978 } 1979 if (typeof input === 'function') { 1980 return new FuncFormatter(input); 1981 } 1982 return null; 1983 } 1984 1985 // base options 1986 // ------------ 1987 const BASE_OPTION_REFINERS = { 1988 navLinkDayClick: identity, 1989 navLinkWeekClick: identity, 1990 duration: createDuration, 1991 bootstrapFontAwesome: identity, 1992 buttonIcons: identity, 1993 customButtons: identity, 1994 defaultAllDayEventDuration: createDuration, 1995 defaultTimedEventDuration: createDuration, 1996 nextDayThreshold: createDuration, 1997 scrollTime: createDuration, 1998 scrollTimeReset: Boolean, 1999 slotMinTime: createDuration, 2000 slotMaxTime: createDuration, 2001 dayPopoverFormat: createFormatter, 2002 slotDuration: createDuration, 2003 snapDuration: createDuration, 2004 headerToolbar: identity, 2005 footerToolbar: identity, 2006 defaultRangeSeparator: String, 2007 titleRangeSeparator: String, 2008 forceEventDuration: Boolean, 2009 dayHeaders: Boolean, 2010 dayHeaderFormat: createFormatter, 2011 dayHeaderClassNames: identity, 2012 dayHeaderContent: identity, 2013 dayHeaderDidMount: identity, 2014 dayHeaderWillUnmount: identity, 2015 dayCellClassNames: identity, 2016 dayCellContent: identity, 2017 dayCellDidMount: identity, 2018 dayCellWillUnmount: identity, 2019 initialView: String, 2020 aspectRatio: Number, 2021 weekends: Boolean, 2022 weekNumberCalculation: identity, 2023 weekNumbers: Boolean, 2024 weekNumberClassNames: identity, 2025 weekNumberContent: identity, 2026 weekNumberDidMount: identity, 2027 weekNumberWillUnmount: identity, 2028 editable: Boolean, 2029 viewClassNames: identity, 2030 viewDidMount: identity, 2031 viewWillUnmount: identity, 2032 nowIndicator: Boolean, 2033 nowIndicatorClassNames: identity, 2034 nowIndicatorContent: identity, 2035 nowIndicatorDidMount: identity, 2036 nowIndicatorWillUnmount: identity, 2037 showNonCurrentDates: Boolean, 2038 lazyFetching: Boolean, 2039 startParam: String, 2040 endParam: String, 2041 timeZoneParam: String, 2042 timeZone: String, 2043 locales: identity, 2044 locale: identity, 2045 themeSystem: String, 2046 dragRevertDuration: Number, 2047 dragScroll: Boolean, 2048 allDayMaintainDuration: Boolean, 2049 unselectAuto: Boolean, 2050 dropAccept: identity, 2051 eventOrder: parseFieldSpecs, 2052 eventOrderStrict: Boolean, 2053 handleWindowResize: Boolean, 2054 windowResizeDelay: Number, 2055 longPressDelay: Number, 2056 eventDragMinDistance: Number, 2057 expandRows: Boolean, 2058 height: identity, 2059 contentHeight: identity, 2060 direction: String, 2061 weekNumberFormat: createFormatter, 2062 eventResizableFromStart: Boolean, 2063 displayEventTime: Boolean, 2064 displayEventEnd: Boolean, 2065 weekText: String, 2066 weekTextLong: String, 2067 progressiveEventRendering: Boolean, 2068 businessHours: identity, 2069 initialDate: identity, 2070 now: identity, 2071 eventDataTransform: identity, 2072 stickyHeaderDates: identity, 2073 stickyFooterScrollbar: identity, 2074 viewHeight: identity, 2075 defaultAllDay: Boolean, 2076 eventSourceFailure: identity, 2077 eventSourceSuccess: identity, 2078 eventDisplay: String, 2079 eventStartEditable: Boolean, 2080 eventDurationEditable: Boolean, 2081 eventOverlap: identity, 2082 eventConstraint: identity, 2083 eventAllow: identity, 2084 eventBackgroundColor: String, 2085 eventBorderColor: String, 2086 eventTextColor: String, 2087 eventColor: String, 2088 eventClassNames: identity, 2089 eventContent: identity, 2090 eventDidMount: identity, 2091 eventWillUnmount: identity, 2092 selectConstraint: identity, 2093 selectOverlap: identity, 2094 selectAllow: identity, 2095 droppable: Boolean, 2096 unselectCancel: String, 2097 slotLabelFormat: identity, 2098 slotLaneClassNames: identity, 2099 slotLaneContent: identity, 2100 slotLaneDidMount: identity, 2101 slotLaneWillUnmount: identity, 2102 slotLabelClassNames: identity, 2103 slotLabelContent: identity, 2104 slotLabelDidMount: identity, 2105 slotLabelWillUnmount: identity, 2106 dayMaxEvents: identity, 2107 dayMaxEventRows: identity, 2108 dayMinWidth: Number, 2109 slotLabelInterval: createDuration, 2110 allDayText: String, 2111 allDayClassNames: identity, 2112 allDayContent: identity, 2113 allDayDidMount: identity, 2114 allDayWillUnmount: identity, 2115 slotMinWidth: Number, 2116 navLinks: Boolean, 2117 eventTimeFormat: createFormatter, 2118 rerenderDelay: Number, 2119 moreLinkText: identity, 2120 moreLinkHint: identity, 2121 selectMinDistance: Number, 2122 selectable: Boolean, 2123 selectLongPressDelay: Number, 2124 eventLongPressDelay: Number, 2125 selectMirror: Boolean, 2126 eventMaxStack: Number, 2127 eventMinHeight: Number, 2128 eventMinWidth: Number, 2129 eventShortHeight: Number, 2130 slotEventOverlap: Boolean, 2131 plugins: identity, 2132 firstDay: Number, 2133 dayCount: Number, 2134 dateAlignment: String, 2135 dateIncrement: createDuration, 2136 hiddenDays: identity, 2137 monthMode: Boolean, 2138 fixedWeekCount: Boolean, 2139 validRange: identity, 2140 visibleRange: identity, 2141 titleFormat: identity, 2142 eventInteractive: Boolean, 2143 // only used by list-view, but languages define the value, so we need it in base options 2144 noEventsText: String, 2145 viewHint: identity, 2146 navLinkHint: identity, 2147 closeHint: String, 2148 timeHint: String, 2149 eventHint: String, 2150 moreLinkClick: identity, 2151 moreLinkClassNames: identity, 2152 moreLinkContent: identity, 2153 moreLinkDidMount: identity, 2154 moreLinkWillUnmount: identity, 2155 // for connectors 2156 // (can't be part of plugin system b/c must be provided at runtime) 2157 handleCustomRendering: identity, 2158 customRenderingMetaMap: identity, 2159 customRenderingReplacesEl: Boolean, 2160 }; 2161 // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. 2162 // raw values. 2163 const BASE_OPTION_DEFAULTS = { 2164 eventDisplay: 'auto', 2165 defaultRangeSeparator: ' - ', 2166 titleRangeSeparator: ' \u2013 ', 2167 defaultTimedEventDuration: '01:00:00', 2168 defaultAllDayEventDuration: { day: 1 }, 2169 forceEventDuration: false, 2170 nextDayThreshold: '00:00:00', 2171 dayHeaders: true, 2172 initialView: '', 2173 aspectRatio: 1.35, 2174 headerToolbar: { 2175 start: 'title', 2176 center: '', 2177 end: 'today prev,next', 2178 }, 2179 weekends: true, 2180 weekNumbers: false, 2181 weekNumberCalculation: 'local', 2182 editable: false, 2183 nowIndicator: false, 2184 scrollTime: '06:00:00', 2185 scrollTimeReset: true, 2186 slotMinTime: '00:00:00', 2187 slotMaxTime: '24:00:00', 2188 showNonCurrentDates: true, 2189 lazyFetching: true, 2190 startParam: 'start', 2191 endParam: 'end', 2192 timeZoneParam: 'timeZone', 2193 timeZone: 'local', 2194 locales: [], 2195 locale: '', 2196 themeSystem: 'standard', 2197 dragRevertDuration: 500, 2198 dragScroll: true, 2199 allDayMaintainDuration: false, 2200 unselectAuto: true, 2201 dropAccept: '*', 2202 eventOrder: 'start,-duration,allDay,title', 2203 dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, 2204 handleWindowResize: true, 2205 windowResizeDelay: 100, 2206 longPressDelay: 1000, 2207 eventDragMinDistance: 5, 2208 expandRows: false, 2209 navLinks: false, 2210 selectable: false, 2211 eventMinHeight: 15, 2212 eventMinWidth: 30, 2213 eventShortHeight: 30, 2214 }; 2215 // calendar listeners 2216 // ------------------ 2217 const CALENDAR_LISTENER_REFINERS = { 2218 datesSet: identity, 2219 eventsSet: identity, 2220 eventAdd: identity, 2221 eventChange: identity, 2222 eventRemove: identity, 2223 windowResize: identity, 2224 eventClick: identity, 2225 eventMouseEnter: identity, 2226 eventMouseLeave: identity, 2227 select: identity, 2228 unselect: identity, 2229 loading: identity, 2230 // internal 2231 _unmount: identity, 2232 _beforeprint: identity, 2233 _afterprint: identity, 2234 _noEventDrop: identity, 2235 _noEventResize: identity, 2236 _resize: identity, 2237 _scrollRequest: identity, 2238 }; 2239 // calendar-specific options 2240 // ------------------------- 2241 const CALENDAR_OPTION_REFINERS = { 2242 buttonText: identity, 2243 buttonHints: identity, 2244 views: identity, 2245 plugins: identity, 2246 initialEvents: identity,
vendor: 6,050 bytes, lines 2247-2419
2247 events: identity, 2248 eventSources: identity, 2249 }; 2250 const COMPLEX_OPTION_COMPARATORS = { 2251 headerToolbar: isMaybeObjectsEqual, 2252 footerToolbar: isMaybeObjectsEqual, 2253 buttonText: isMaybeObjectsEqual, 2254 buttonHints: isMaybeObjectsEqual, 2255 buttonIcons: isMaybeObjectsEqual, 2256 dateIncrement: isMaybeObjectsEqual, 2257 }; 2258 function isMaybeObjectsEqual(a, b) { 2259 if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects 2260 return isPropsEqual(a, b); 2261 } 2262 return a === b; 2263 } 2264 // view-specific options 2265 // --------------------- 2266 const VIEW_OPTION_REFINERS = { 2267 type: String, 2268 component: identity, 2269 buttonText: String, 2270 buttonTextKey: String, 2271 dateProfileGeneratorClass: identity, 2272 usesMinMaxTime: Boolean, 2273 classNames: identity, 2274 content: identity, 2275 didMount: identity, 2276 willUnmount: identity, 2277 }; 2278 // util funcs 2279 // ---------------------------------------------------------------------------------------------------- 2280 function mergeRawOptions(optionSets) { 2281 return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); 2282 } 2283 function refineProps(input, refiners) { 2284 let refined = {}; 2285 let extra = {}; 2286 for (let propName in refiners) { 2287 if (propName in input) { 2288 refined[propName] = refiners[propName](input[propName]); 2289 } 2290 } 2291 for (let propName in input) { 2292 if (!(propName in refiners)) { 2293 extra[propName] = input[propName]; 2294 } 2295 } 2296 return { refined, extra }; 2297 } 2298 function identity(raw) { 2299 return raw; 2300 } 2301 2302 /* 2303 NOTE: this can be a public API, especially createElement for hooks. 2304 See examples/typescript-scheduler/src/index.ts 2305 */ 2306 function flushSync(runBeforeFlush) { 2307 runBeforeFlush(); 2308 let oldDebounceRendering = l$1.debounceRendering; // orig 2309 let callbackQ = []; 2310 function execCallbackSync(callback) { 2311 callbackQ.push(callback); 2312 } 2313 l$1.debounceRendering = execCallbackSync; 2314 P$1(h(FakeComponent, {}), document.createElement('div')); 2315 while (callbackQ.length) { 2316 callbackQ.shift()(); 2317 } 2318 l$1.debounceRendering = oldDebounceRendering; 2319 } 2320 class FakeComponent extends d { 2321 render() { return h('div', {}); } 2322 componentDidMount() { this.setState({}); } 2323 } 2324 // TODO: use preact/compat instead? 2325 function createContext(defaultValue) { 2326 let ContextType = B$1(defaultValue); 2327 let origProvider = ContextType.Provider; 2328 ContextType.Provider = function () { 2329 let isNew = !this.getChildContext; 2330 let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params 2331 if (isNew) { 2332 let subs = []; 2333 this.shouldComponentUpdate = (_props) => { 2334 if (this.props.value !== _props.value) { 2335 subs.forEach((c) => { 2336 c.context = _props.value; 2337 c.forceUpdate(); 2338 }); 2339 } 2340 }; 2341 this.sub = (c) => { 2342 subs.push(c); 2343 let old = c.componentWillUnmount; 2344 c.componentWillUnmount = () => { 2345 subs.splice(subs.indexOf(c), 1); 2346 old && old.call(c); 2347 }; 2348 }; 2349 } 2350 return children; 2351 }; 2352 return ContextType; 2353 } 2354 2355 class ScrollResponder { 2356 constructor(execFunc, emitter, scrollTime, scrollTimeReset) { 2357 this.execFunc = execFunc; 2358 this.emitter = emitter; 2359 this.scrollTime = scrollTime; 2360 this.scrollTimeReset = scrollTimeReset; 2361 this.handleScrollRequest = (request) => { 2362 this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request); 2363 this.drain(); 2364 }; 2365 emitter.on('_scrollRequest', this.handleScrollRequest); 2366 this.fireInitialScroll(); 2367 } 2368 detach() { 2369 this.emitter.off('_scrollRequest', this.handleScrollRequest); 2370 } 2371 update(isDatesNew) { 2372 if (isDatesNew && this.scrollTimeReset) { 2373 this.fireInitialScroll(); // will drain 2374 } 2375 else { 2376 this.drain(); 2377 } 2378 } 2379 fireInitialScroll() { 2380 this.handleScrollRequest({ 2381 time: this.scrollTime, 2382 }); 2383 } 2384 drain() { 2385 if (this.queuedRequest && this.execFunc(this.queuedRequest)) { 2386 this.queuedRequest = null; 2387 } 2388 } 2389 } 2390 2391 const ViewContextType = createContext({}); // for Components 2392 function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { 2393 return { 2394 dateEnv, 2395 options: viewOptions, 2396 pluginHooks, 2397 emitter, 2398 dispatch, 2399 getCurrentData, 2400 calendarApi, 2401 viewSpec, 2402 viewApi, 2403 dateProfileGenerator, 2404 theme, 2405 isRtl: viewOptions.direction === 'rtl', 2406 addResizeHandler(handler) { 2407 emitter.on('_resize', handler); 2408 }, 2409 removeResizeHandler(handler) { 2410 emitter.off('_resize', handler); 2411 }, 2412 createScrollResponder(execFunc) { 2413 return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); 2414 }, 2415 registerInteractiveComponent, 2416 unregisterInteractiveComponent, 2417 }; 2418 } 2419
2420 /* eslint max-classes-per-file: off */ 2421 class PureComponent extends d { 2422 shouldComponentUpdate(nextProps, nextState) { 2423 if (this.debug) { 2424 // eslint-disable-next-line no-console 2425 console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); 2426 } 2427 return !compareObjs(this.props, nextProps, this.propEquality) || 2428 !compareObjs(this.state, nextState, this.stateEquality); 2429 } 2430 // HACK for freakin' React StrictMode 2431 safeSetState(newState) { 2432 if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) { 2433 this.setState(newState); 2434 } 2435 } 2436 } 2437 PureComponent.addPropsEquality = addPropsEquality; 2438 PureComponent.addStateEquality = addStateEquality; 2439 PureComponent.contextType = ViewContextType; 2440 PureComponent.prototype.propEquality = {}; 2441 PureComponent.prototype.stateEquality = {}; 2442 class BaseComponent extends PureComponent { 2443 } 2444 BaseComponent.contextType = ViewContextType; 2445 function addPropsEquality(propEquality) { 2446 let hash = Object.create(this.prototype.propEquality); 2447 Object.assign(hash, propEquality); 2448 this.prototype.propEquality = hash; 2449 } 2450 function addStateEquality(stateEquality) { 2451 let hash = Object.create(this.prototype.stateEquality); 2452 Object.assign(hash, stateEquality); 2453 this.prototype.stateEquality = hash; 2454 } 2455 // use other one 2456 function setRef(ref, current) { 2457 if (typeof ref === 'function') { 2458 ref(current); 2459 } 2460 else if (ref) { 2461 // see https://github.com/facebook/react/issues/13029 2462 ref.current = current; 2463 } 2464 } 2465 2466 class ContentInjector extends BaseComponent { 2467 constructor() { 2468 super(...arguments); 2469 this.id = guid(); 2470 this.currentDomNodes = []; 2471 this.queuedDomNodes = []; 2472 this.handleEl = (el) => { 2473 if (this.props.elRef) { 2474 setRef(this.props.elRef, el); 2475 } 2476 }; 2477 } 2478 render() { 2479 const { props, context } = this; 2480 const { options } = context; 2481 const { generator, renderProps } = props; 2482 const attrs = buildElAttrs(props); 2483 let innerContent; 2484 let queuedDomNodes = []; 2485 if (hasCustomRenderingHandler(props.generatorName, options)) { 2486 if (options.customRenderingReplacesEl) { 2487 delete attrs.elRef; // because handleEl will be used 2488 } 2489 } 2490 else { 2491 const customContent = typeof generator === 'function' ? 2492 generator(renderProps, h) : 2493 generator; 2494 if (typeof customContent === 'string' || 2495 i$1(customContent) || 2496 Array.isArray(customContent)) { 2497 innerContent = customContent; 2498 } 2499 else if (typeof customContent === 'object') { 2500 if ('html' in customContent) { 2501 attrs.dangerouslySetInnerHTML = { __html: customContent.html }; 2502 } 2503 else if ('domNodes' in customContent) { 2504 queuedDomNodes = Array.prototype.slice.call(customContent.domNodes); 2505 } 2506 } 2507 } 2508 this.queuedDomNodes = queuedDomNodes; 2509 return h(props.elTag, attrs, innerContent); 2510 } 2511 componentDidMount() { 2512 this.applyQueueudDomNodes(); 2513 this.triggerCustomRendering(true); 2514 } 2515 componentDidUpdate() { 2516 this.applyQueueudDomNodes(); 2517 this.triggerCustomRendering(true); 2518 } 2519 componentWillUnmount() { 2520 this.triggerCustomRendering(false); // TODO: different API for removal? 2521 } 2522 triggerCustomRendering(isActive) { 2523 const { props, context } = this; 2524 const { handleCustomRendering, customRenderingMetaMap } = context.options; 2525 if (handleCustomRendering) { 2526 const customRenderingMeta = customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName]; 2527 if (customRenderingMeta) { 2528 handleCustomRendering(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.handleEl, generatorMeta: customRenderingMeta }, props)); 2529 } 2530 } 2531 } 2532 applyQueueudDomNodes() { 2533 const { queuedDomNodes, currentDomNodes } = this; 2534 const el = this.base; 2535 if (!isArraysEqual(queuedDomNodes, currentDomNodes)) {
vendor: 9,384 bytes, lines 2536-2751
2536 currentDomNodes.forEach(removeElement); 2537 for (let newNode of queuedDomNodes) { 2538 el.appendChild(newNode); 2539 } 2540 this.currentDomNodes = queuedDomNodes; 2541 } 2542 } 2543 } 2544 ContentInjector.addPropsEquality({ 2545 elClasses: isArraysEqual, 2546 elStyle: isPropsEqual, 2547 elAttrs: isPropsEqual, 2548 renderProps: isPropsEqual, 2549 }); 2550 // Util 2551 function hasCustomRenderingHandler(generatorName, options) { 2552 var _a; 2553 return Boolean(options.handleCustomRendering && 2554 generatorName && 2555 ((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName])); 2556 } 2557 function buildElAttrs(props, extraClassNames) { 2558 const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: props.elRef }); 2559 if (props.elClasses || extraClassNames) { 2560 attrs.className = (props.elClasses || []) 2561 .concat(extraClassNames || []) 2562 .concat(attrs.className || []) 2563 .filter(Boolean) 2564 .join(' '); 2565 } 2566 if (props.elStyle) { 2567 attrs.style = props.elStyle; 2568 } 2569 return attrs; 2570 } 2571 2572 const RenderId = createContext(0); 2573 2574 class ContentContainer extends d { 2575 constructor() { 2576 super(...arguments); 2577 this.InnerContent = InnerContentInjector.bind(undefined, this); 2578 } 2579 render() { 2580 const { props } = this; 2581 const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps); 2582 if (props.children) { 2583 const elAttrs = buildElAttrs(props, generatedClassNames); 2584 const children = props.children(this.InnerContent, props.renderProps, elAttrs); 2585 if (props.elTag) { 2586 return h(props.elTag, elAttrs, children); 2587 } 2588 else { 2589 return children; 2590 } 2591 } 2592 else { 2593 return h((ContentInjector), Object.assign(Object.assign({}, props), { elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context })); 2594 } 2595 } 2596 componentDidMount() { 2597 var _a, _b; 2598 (_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.base })); 2599 } 2600 componentWillUnmount() { 2601 var _a, _b; 2602 (_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.base })); 2603 } 2604 } 2605 ContentContainer.contextType = RenderId; 2606 function InnerContentInjector(containerComponent, props) { 2607 const parentProps = containerComponent.props; 2608 return h((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, generator: parentProps.generator, renderId: containerComponent.context }, props)); 2609 } 2610 // Utils 2611 function generateClassNames(classNameGenerator, renderProps) { 2612 const classNames = typeof classNameGenerator === 'function' ? 2613 classNameGenerator(renderProps) : 2614 classNameGenerator || []; 2615 return typeof classNames === 'string' ? [classNames] : classNames; 2616 } 2617 2618 class ViewContainer$1 extends BaseComponent { 2619 render() { 2620 let { props, context } = this; 2621 let { options } = context; 2622 let renderProps = { view: context.viewApi }; 2623 return (h(ContentContainer, Object.assign({}, props, { elTag: props.elTag || 'div', elClasses: [ 2624 ...buildViewClassNames(props.viewSpec), 2625 ...(props.elClasses || []), 2626 ], renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, generator: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }), () => props.children)); 2627 } 2628 } 2629 function buildViewClassNames(viewSpec) { 2630 return [ 2631 `fc-${viewSpec.type}-view`, 2632 'fc-view', 2633 ]; 2634 } 2635 2636 function parseRange(input, dateEnv) { 2637 let start = null; 2638 let end = null; 2639 if (input.start) { 2640 start = dateEnv.createMarker(input.start); 2641 } 2642 if (input.end) { 2643 end = dateEnv.createMarker(input.end); 2644 } 2645 if (!start && !end) { 2646 return null; 2647 } 2648 if (start && end && end < start) { 2649 return null; 2650 } 2651 return { start, end }; 2652 } 2653 // SIDE-EFFECT: will mutate ranges. 2654 // Will return a new array result. 2655 function invertRanges(ranges, constraintRange) { 2656 let invertedRanges = []; 2657 let { start } = constraintRange; // the end of the previous range. the start of the new range 2658 let i; 2659 let dateRange; 2660 // ranges need to be in order. required for our date-walking algorithm 2661 ranges.sort(compareRanges); 2662 for (i = 0; i < ranges.length; i += 1) { 2663 dateRange = ranges[i]; 2664 // add the span of time before the event (if there is any) 2665 if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) 2666 invertedRanges.push({ start, end: dateRange.start }); 2667 } 2668 if (dateRange.end > start) { 2669 start = dateRange.end; 2670 } 2671 } 2672 // add the span of time after the last event (if there is any) 2673 if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) 2674 invertedRanges.push({ start, end: constraintRange.end }); 2675 } 2676 return invertedRanges; 2677 } 2678 function compareRanges(range0, range1) { 2679 return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first 2680 } 2681 function intersectRanges(range0, range1) { 2682 let { start, end } = range0; 2683 let newRange = null; 2684 if (range1.start !== null) { 2685 if (start === null) { 2686 start = range1.start; 2687 } 2688 else { 2689 start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); 2690 } 2691 } 2692 if (range1.end != null) { 2693 if (end === null) { 2694 end = range1.end; 2695 } 2696 else { 2697 end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); 2698 } 2699 } 2700 if (start === null || end === null || start < end) { 2701 newRange = { start, end }; 2702 } 2703 return newRange; 2704 } 2705 function rangesEqual(range0, range1) { 2706 return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && 2707 (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); 2708 } 2709 function rangesIntersect(range0, range1) { 2710 return (range0.end === null || range1.start === null || range0.end > range1.start) && 2711 (range0.start === null || range1.end === null || range0.start < range1.end); 2712 } 2713 function rangeContainsRange(outerRange, innerRange) { 2714 return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && 2715 (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); 2716 } 2717 function rangeContainsMarker(range, date) { 2718 return (range.start === null || date >= range.start) && 2719 (range.end === null || date < range.end); 2720 } 2721 // If the given date is not within the given range, move it inside. 2722 // (If it's past the end, make it one millisecond before the end). 2723 function constrainMarkerToRange(date, range) { 2724 if (range.start != null && date < range.start) { 2725 return range.start; 2726 } 2727 if (range.end != null && date >= range.end) { 2728 return new Date(range.end.valueOf() - 1); 2729 } 2730 return date; 2731 } 2732 2733 /* Date stuff that doesn't belong in datelib core 2734 ----------------------------------------------------------------------------------------------------------------------*/ 2735 // given a timed range, computes an all-day range that has the same exact duration, 2736 // but whose start time is aligned with the start of the day. 2737 function computeAlignedDayRange(timedRange) { 2738 let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; 2739 let start = startOfDay(timedRange.start); 2740 let end = addDays(start, dayCnt); 2741 return { start, end }; 2742 } 2743 // given a timed range, computes an all-day range based on how for the end date bleeds into the next day 2744 // TODO: give nextDayThreshold a default arg 2745 function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) { 2746 let startDay = null; 2747 let endDay = null; 2748 if (timedRange.end) { 2749 endDay = startOfDay(timedRange.end); 2750 let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` 2751 // If the end time is actually inclusively part of the next day and is equal to or
vendor: 14,668 bytes, lines 2752-3068
2752 // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. 2753 // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. 2754 if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { 2755 endDay = addDays(endDay, 1); 2756 } 2757 } 2758 if (timedRange.start) { 2759 startDay = startOfDay(timedRange.start); // the beginning of the day the range starts 2760 // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. 2761 if (endDay && endDay <= startDay) { 2762 endDay = addDays(startDay, 1); 2763 } 2764 } 2765 return { start: startDay, end: endDay }; 2766 } 2767 // spans from one day into another? 2768 function isMultiDayRange(range) { 2769 let visibleRange = computeVisibleDayRange(range); 2770 return diffDays(visibleRange.start, visibleRange.end) > 1; 2771 } 2772 function diffDates(date0, date1, dateEnv, largeUnit) { 2773 if (largeUnit === 'year') { 2774 return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); 2775 } 2776 if (largeUnit === 'month') { 2777 return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); 2778 } 2779 return diffDayAndTime(date0, date1); // returns a duration 2780 } 2781 2782 function reduceCurrentDate(currentDate, action) { 2783 switch (action.type) { 2784 case 'CHANGE_DATE': 2785 return action.dateMarker; 2786 default: 2787 return currentDate; 2788 } 2789 } 2790 function getInitialDate(options, dateEnv) { 2791 let initialDateInput = options.initialDate; 2792 // compute the initial ambig-timezone date 2793 if (initialDateInput != null) { 2794 return dateEnv.createMarker(initialDateInput); 2795 } 2796 return getNow(options.now, dateEnv); // getNow already returns unzoned 2797 } 2798 function getNow(nowInput, dateEnv) { 2799 if (typeof nowInput === 'function') { 2800 nowInput = nowInput(); 2801 } 2802 if (nowInput == null) { 2803 return dateEnv.createNowMarker(); 2804 } 2805 return dateEnv.createMarker(nowInput); 2806 } 2807 2808 class DateProfileGenerator { 2809 constructor(props) { 2810 this.props = props; 2811 this.nowDate = getNow(props.nowInput, props.dateEnv); 2812 this.initHiddenDays(); 2813 } 2814 /* Date Range Computation 2815 ------------------------------------------------------------------------------------------------------------------*/ 2816 // Builds a structure with info about what the dates/ranges will be for the "prev" view. 2817 buildPrev(currentDateProfile, currentDate, forceToValid) { 2818 let { dateEnv } = this.props; 2819 let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month 2820 currentDateProfile.dateIncrement); 2821 return this.build(prevDate, -1, forceToValid); 2822 } 2823 // Builds a structure with info about what the dates/ranges will be for the "next" view. 2824 buildNext(currentDateProfile, currentDate, forceToValid) { 2825 let { dateEnv } = this.props; 2826 let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month 2827 currentDateProfile.dateIncrement); 2828 return this.build(nextDate, 1, forceToValid); 2829 } 2830 // Builds a structure holding dates/ranges for rendering around the given date. 2831 // Optional direction param indicates whether the date is being incremented/decremented 2832 // from its previous value. decremented = -1, incremented = 1 (default). 2833 build(currentDate, direction, forceToValid = true) { 2834 let { props } = this; 2835 let validRange; 2836 let currentInfo; 2837 let isRangeAllDay; 2838 let renderRange; 2839 let activeRange; 2840 let isValid; 2841 validRange = this.buildValidRange(); 2842 validRange = this.trimHiddenDays(validRange); 2843 if (forceToValid) { 2844 currentDate = constrainMarkerToRange(currentDate, validRange); 2845 } 2846 currentInfo = this.buildCurrentRangeInfo(currentDate, direction); 2847 isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); 2848 renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); 2849 renderRange = this.trimHiddenDays(renderRange); 2850 activeRange = renderRange; 2851 if (!props.showNonCurrentDates) { 2852 activeRange = intersectRanges(activeRange, currentInfo.range); 2853 } 2854 activeRange = this.adjustActiveRange(activeRange); 2855 activeRange = intersectRanges(activeRange, validRange); // might return null 2856 // it's invalid if the originally requested date is not contained, 2857 // or if the range is completely outside of the valid range. 2858 isValid = rangesIntersect(currentInfo.range, validRange); 2859 return { 2860 // constraint for where prev/next operations can go and where events can be dragged/resized to. 2861 // an object with optional start and end properties. 2862 validRange, 2863 // range the view is formally responsible for. 2864 // for example, a month view might have 1st-31st, excluding padded dates 2865 currentRange: currentInfo.range, 2866 // name of largest unit being displayed, like "month" or "week" 2867 currentRangeUnit: currentInfo.unit, 2868 isRangeAllDay, 2869 // dates that display events and accept drag-n-drop 2870 // will be `null` if no dates accept events 2871 activeRange, 2872 // date range with a rendered skeleton 2873 // includes not-active days that need some sort of DOM 2874 renderRange, 2875 // Duration object that denotes the first visible time of any given day 2876 slotMinTime: props.slotMinTime, 2877 // Duration object that denotes the exclusive visible end time of any given day 2878 slotMaxTime: props.slotMaxTime, 2879 isValid, 2880 // how far the current date will move for a prev/next operation 2881 dateIncrement: this.buildDateIncrement(currentInfo.duration), 2882 // pass a fallback (might be null) ^ 2883 }; 2884 } 2885 // Builds an object with optional start/end properties. 2886 // Indicates the minimum/maximum dates to display. 2887 // not responsible for trimming hidden days. 2888 buildValidRange() { 2889 let input = this.props.validRangeInput; 2890 let simpleInput = typeof input === 'function' 2891 ? input.call(this.props.calendarApi, this.nowDate) 2892 : input; 2893 return this.refineRange(simpleInput) || 2894 { start: null, end: null }; // completely open-ended 2895 } 2896 // Builds a structure with info about the "current" range, the range that is 2897 // highlighted as being the current month for example. 2898 // See build() for a description of `direction`. 2899 // Guaranteed to have `range` and `unit` properties. `duration` is optional. 2900 buildCurrentRangeInfo(date, direction) { 2901 let { props } = this; 2902 let duration = null; 2903 let unit = null; 2904 let range = null; 2905 let dayCount; 2906 if (props.duration) { 2907 duration = props.duration; 2908 unit = props.durationUnit; 2909 range = this.buildRangeFromDuration(date, direction, duration, unit); 2910 } 2911 else if ((dayCount = this.props.dayCount)) { 2912 unit = 'day'; 2913 range = this.buildRangeFromDayCount(date, direction, dayCount); 2914 } 2915 else if ((range = this.buildCustomVisibleRange(date))) { 2916 unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; 2917 } 2918 else { 2919 duration = this.getFallbackDuration(); 2920 unit = greatestDurationDenominator(duration).unit; 2921 range = this.buildRangeFromDuration(date, direction, duration, unit); 2922 } 2923 return { duration, unit, range }; 2924 } 2925 getFallbackDuration() { 2926 return createDuration({ day: 1 }); 2927 } 2928 // Returns a new activeRange to have time values (un-ambiguate) 2929 // slotMinTime or slotMaxTime causes the range to expand. 2930 adjustActiveRange(range) { 2931 let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props; 2932 let { start, end } = range; 2933 if (usesMinMaxTime) { 2934 // expand active range if slotMinTime is negative (why not when positive?) 2935 if (asRoughDays(slotMinTime) < 0) { 2936 start = startOfDay(start); // necessary? 2937 start = dateEnv.add(start, slotMinTime); 2938 } 2939 // expand active range if slotMaxTime is beyond one day (why not when negative?) 2940 if (asRoughDays(slotMaxTime) > 1) { 2941 end = startOfDay(end); // necessary? 2942 end = addDays(end, -1); 2943 end = dateEnv.add(end, slotMaxTime); 2944 } 2945 } 2946 return { start, end }; 2947 } 2948 // Builds the "current" range when it is specified as an explicit duration. 2949 // `unit` is the already-computed greatestDurationDenominator unit of duration. 2950 buildRangeFromDuration(date, direction, duration, unit) { 2951 let { dateEnv, dateAlignment } = this.props; 2952 let start; 2953 let end; 2954 let res; 2955 // compute what the alignment should be 2956 if (!dateAlignment) { 2957 let { dateIncrement } = this.props; 2958 if (dateIncrement) { 2959 // use the smaller of the two units 2960 if (asRoughMs(dateIncrement) < asRoughMs(duration)) { 2961 dateAlignment = greatestDurationDenominator(dateIncrement).unit; 2962 } 2963 else { 2964 dateAlignment = unit; 2965 } 2966 } 2967 else { 2968 dateAlignment = unit; 2969 } 2970 } 2971 // if the view displays a single day or smaller 2972 if (asRoughDays(duration) <= 1) { 2973 if (this.isHiddenDay(start)) { 2974 start = this.skipHiddenDays(start, direction); 2975 start = startOfDay(start); 2976 } 2977 } 2978 function computeRes() { 2979 start = dateEnv.startOf(date, dateAlignment); 2980 end = dateEnv.add(start, duration); 2981 res = { start, end }; 2982 } 2983 computeRes(); 2984 // if range is completely enveloped by hidden days, go past the hidden days 2985 if (!this.trimHiddenDays(res)) { 2986 date = this.skipHiddenDays(date, direction); 2987 computeRes(); 2988 } 2989 return res; 2990 } 2991 // Builds the "current" range when a dayCount is specified. 2992 buildRangeFromDayCount(date, direction, dayCount) { 2993 let { dateEnv, dateAlignment } = this.props; 2994 let runningCount = 0; 2995 let start = date; 2996 let end; 2997 if (dateAlignment) { 2998 start = dateEnv.startOf(start, dateAlignment); 2999 } 3000 start = startOfDay(start); 3001 start = this.skipHiddenDays(start, direction); 3002 end = start; 3003 do { 3004 end = addDays(end, 1); 3005 if (!this.isHiddenDay(end)) { 3006 runningCount += 1; 3007 } 3008 } while (runningCount < dayCount); 3009 return { start, end }; 3010 } 3011 // Builds a normalized range object for the "visible" range, 3012 // which is a way to define the currentRange and activeRange at the same time. 3013 buildCustomVisibleRange(date) { 3014 let { props } = this; 3015 let input = props.visibleRangeInput; 3016 let simpleInput = typeof input === 'function' 3017 ? input.call(props.calendarApi, props.dateEnv.toDate(date)) 3018 : input; 3019 let range = this.refineRange(simpleInput); 3020 if (range && (range.start == null || range.end == null)) { 3021 return null; 3022 } 3023 return range; 3024 } 3025 // Computes the range that will represent the element/cells for *rendering*, 3026 // but which may have voided days/times. 3027 // not responsible for trimming hidden days. 3028 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { 3029 return currentRange; 3030 } 3031 // Compute the duration value that should be added/substracted to the current date 3032 // when a prev/next operation happens. 3033 buildDateIncrement(fallback) { 3034 let { dateIncrement } = this.props; 3035 let customAlignment; 3036 if (dateIncrement) { 3037 return dateIncrement; 3038 } 3039 if ((customAlignment = this.props.dateAlignment)) { 3040 return createDuration(1, customAlignment); 3041 } 3042 if (fallback) { 3043 return fallback; 3044 } 3045 return createDuration({ days: 1 }); 3046 } 3047 refineRange(rangeInput) { 3048 if (rangeInput) { 3049 let range = parseRange(rangeInput, this.props.dateEnv); 3050 if (range) { 3051 range = computeVisibleDayRange(range); 3052 } 3053 return range; 3054 } 3055 return null; 3056 } 3057 /* Hidden Days 3058 ------------------------------------------------------------------------------------------------------------------*/ 3059 // Initializes internal variables related to calculating hidden days-of-week 3060 initHiddenDays() { 3061 let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden 3062 let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) 3063 let dayCnt = 0; 3064 let i; 3065 if (this.props.weekends === false) { 3066 hiddenDays.push(0, 6); // 0=sunday, 6=saturday 3067 } 3068 for (i = 0; i < 7; i += 1) {
vendor: 12,783 bytes, lines 3069-3385
3069 if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { 3070 dayCnt += 1; 3071 } 3072 } 3073 if (!dayCnt) { 3074 throw new Error('invalid hiddenDays'); // all days were hidden? bad. 3075 } 3076 this.isHiddenDayHash = isHiddenDayHash; 3077 } 3078 // Remove days from the beginning and end of the range that are computed as hidden. 3079 // If the whole range is trimmed off, returns null 3080 trimHiddenDays(range) { 3081 let { start, end } = range; 3082 if (start) { 3083 start = this.skipHiddenDays(start); 3084 } 3085 if (end) { 3086 end = this.skipHiddenDays(end, -1, true); 3087 } 3088 if (start == null || end == null || start < end) { 3089 return { start, end }; 3090 } 3091 return null; 3092 } 3093 // Is the current day hidden? 3094 // `day` is a day-of-week index (0-6), or a Date (used for UTC) 3095 isHiddenDay(day) { 3096 if (day instanceof Date) { 3097 day = day.getUTCDay(); 3098 } 3099 return this.isHiddenDayHash[day]; 3100 } 3101 // Incrementing the current day until it is no longer a hidden day, returning a copy. 3102 // DOES NOT CONSIDER validRange! 3103 // If the initial value of `date` is not a hidden day, don't do anything. 3104 // Pass `isExclusive` as `true` if you are dealing with an end date. 3105 // `inc` defaults to `1` (increment one day forward each time) 3106 skipHiddenDays(date, inc = 1, isExclusive = false) { 3107 while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { 3108 date = addDays(date, inc); 3109 } 3110 return date; 3111 } 3112 } 3113 3114 function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { 3115 return { 3116 instanceId: guid(), 3117 defId, 3118 range, 3119 forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, 3120 forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, 3121 }; 3122 } 3123 3124 function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { 3125 for (let i = 0; i < recurringTypes.length; i += 1) { 3126 let parsed = recurringTypes[i].parse(refined, dateEnv); 3127 if (parsed) { 3128 let { allDay } = refined; 3129 if (allDay == null) { 3130 allDay = defaultAllDay; 3131 if (allDay == null) { 3132 allDay = parsed.allDayGuess; 3133 if (allDay == null) { 3134 allDay = false; 3135 } 3136 } 3137 } 3138 return { 3139 allDay, 3140 duration: parsed.duration, 3141 typeData: parsed.typeData, 3142 typeId: i, 3143 }; 3144 } 3145 } 3146 return null; 3147 } 3148 function expandRecurring(eventStore, framingRange, context) { 3149 let { dateEnv, pluginHooks, options } = context; 3150 let { defs, instances } = eventStore; 3151 // remove existing recurring instances 3152 // TODO: bad. always expand events as a second step 3153 instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef); 3154 for (let defId in defs) { 3155 let def = defs[defId]; 3156 if (def.recurringDef) { 3157 let { duration } = def.recurringDef; 3158 if (!duration) { 3159 duration = def.allDay ? 3160 options.defaultAllDayEventDuration : 3161 options.defaultTimedEventDuration; 3162 } 3163 let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); 3164 for (let start of starts) { 3165 let instance = createEventInstance(defId, { 3166 start, 3167 end: dateEnv.add(start, duration), 3168 }); 3169 instances[instance.instanceId] = instance; 3170 } 3171 } 3172 } 3173 return { defs, instances }; 3174 } 3175 /* 3176 Event MUST have a recurringDef 3177 */ 3178 function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { 3179 let typeDef = recurringTypes[eventDef.recurringDef.typeId]; 3180 let markers = typeDef.expand(eventDef.recurringDef.typeData, { 3181 start: dateEnv.subtract(framingRange.start, duration), 3182 end: framingRange.end, 3183 }, dateEnv); 3184 // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to 3185 if (eventDef.allDay) { 3186 markers = markers.map(startOfDay); 3187 } 3188 return markers; 3189 } 3190 3191 const EVENT_NON_DATE_REFINERS = { 3192 id: String, 3193 groupId: String, 3194 title: String, 3195 url: String, 3196 interactive: Boolean, 3197 }; 3198 const EVENT_DATE_REFINERS = { 3199 start: identity, 3200 end: identity, 3201 date: identity, 3202 allDay: Boolean, 3203 }; 3204 const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); 3205 function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context)) { 3206 let { refined, extra } = refineEventDef(raw, context, refiners); 3207 let defaultAllDay = computeIsDefaultAllDay(eventSource, context); 3208 let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); 3209 if (recurringRes) { 3210 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context); 3211 def.recurringDef = { 3212 typeId: recurringRes.typeId, 3213 typeData: recurringRes.typeData, 3214 duration: recurringRes.duration, 3215 }; 3216 return { def, instance: null }; 3217 } 3218 let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); 3219 if (singleRes) { 3220 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context); 3221 let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); 3222 return { def, instance }; 3223 } 3224 return null; 3225 } 3226 function refineEventDef(raw, context, refiners = buildEventRefiners(context)) { 3227 return refineProps(raw, refiners); 3228 } 3229 function buildEventRefiners(context) { 3230 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); 3231 } 3232 /* 3233 Will NOT populate extendedProps with the leftover properties. 3234 Will NOT populate date-related props. 3235 */ 3236 function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context) { 3237 let def = { 3238 title: refined.title || '', 3239 groupId: refined.groupId || '', 3240 publicId: refined.id || '', 3241 url: refined.url || '', 3242 recurringDef: null, 3243 defId: guid(), 3244 sourceId, 3245 allDay, 3246 hasEnd, 3247 interactive: refined.interactive, 3248 ui: createEventUi(refined, context), 3249 extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra), 3250 }; 3251 for (let memberAdder of context.pluginHooks.eventDefMemberAdders) { 3252 Object.assign(def, memberAdder(refined)); 3253 } 3254 // help out EventImpl from having user modify props 3255 Object.freeze(def.ui.classNames); 3256 Object.freeze(def.extendedProps); 3257 return def; 3258 } 3259 function parseSingle(refined, defaultAllDay, context, allowOpenRange) { 3260 let { allDay } = refined; 3261 let startMeta; 3262 let startMarker = null; 3263 let hasEnd = false; 3264 let endMeta; 3265 let endMarker = null; 3266 let startInput = refined.start != null ? refined.start : refined.date; 3267 startMeta = context.dateEnv.createMarkerMeta(startInput); 3268 if (startMeta) { 3269 startMarker = startMeta.marker; 3270 } 3271 else if (!allowOpenRange) { 3272 return null; 3273 } 3274 if (refined.end != null) { 3275 endMeta = context.dateEnv.createMarkerMeta(refined.end); 3276 } 3277 if (allDay == null) { 3278 if (defaultAllDay != null) { 3279 allDay = defaultAllDay; 3280 } 3281 else { 3282 // fall back to the date props LAST 3283 allDay = (!startMeta || startMeta.isTimeUnspecified) && 3284 (!endMeta || endMeta.isTimeUnspecified); 3285 } 3286 } 3287 if (allDay && startMarker) { 3288 startMarker = startOfDay(startMarker); 3289 } 3290 if (endMeta) { 3291 endMarker = endMeta.marker; 3292 if (allDay) { 3293 endMarker = startOfDay(endMarker); 3294 } 3295 if (startMarker && endMarker <= startMarker) { 3296 endMarker = null; 3297 } 3298 } 3299 if (endMarker) { 3300 hasEnd = true; 3301 } 3302 else if (!allowOpenRange) { 3303 hasEnd = context.options.forceEventDuration || false; 3304 endMarker = context.dateEnv.add(startMarker, allDay ? 3305 context.options.defaultAllDayEventDuration : 3306 context.options.defaultTimedEventDuration); 3307 } 3308 return { 3309 allDay, 3310 hasEnd, 3311 range: { start: startMarker, end: endMarker }, 3312 forcedStartTzo: startMeta ? startMeta.forcedTzo : null, 3313 forcedEndTzo: endMeta ? endMeta.forcedTzo : null, 3314 }; 3315 } 3316 function computeIsDefaultAllDay(eventSource, context) { 3317 let res = null; 3318 if (eventSource) { 3319 res = eventSource.defaultAllDay; 3320 } 3321 if (res == null) { 3322 res = context.options.defaultAllDay; 3323 } 3324 return res; 3325 } 3326 3327 function parseEvents(rawEvents, eventSource, context, allowOpenRange) { 3328 let eventStore = createEmptyEventStore(); 3329 let eventRefiners = buildEventRefiners(context); 3330 for (let rawEvent of rawEvents) { 3331 let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners); 3332 if (tuple) { 3333 eventTupleToStore(tuple, eventStore); 3334 } 3335 } 3336 return eventStore; 3337 } 3338 function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) { 3339 eventStore.defs[tuple.def.defId] = tuple.def; 3340 if (tuple.instance) { 3341 eventStore.instances[tuple.instance.instanceId] = tuple.instance; 3342 } 3343 return eventStore; 3344 } 3345 // retrieves events that have the same groupId as the instance specified by `instanceId` 3346 // or they are the same as the instance. 3347 // why might instanceId not be in the store? an event from another calendar? 3348 function getRelevantEvents(eventStore, instanceId) { 3349 let instance = eventStore.instances[instanceId]; 3350 if (instance) { 3351 let def = eventStore.defs[instance.defId]; 3352 // get events/instances with same group 3353 let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef)); 3354 // add the original 3355 // TODO: wish we could use eventTupleToStore or something like it 3356 newStore.defs[def.defId] = def; 3357 newStore.instances[instance.instanceId] = instance; 3358 return newStore; 3359 } 3360 return createEmptyEventStore(); 3361 } 3362 function isEventDefsGrouped(def0, def1) { 3363 return Boolean(def0.groupId && def0.groupId === def1.groupId); 3364 } 3365 function createEmptyEventStore() { 3366 return { defs: {}, instances: {} }; 3367 } 3368 function mergeEventStores(store0, store1) { 3369 return { 3370 defs: Object.assign(Object.assign({}, store0.defs), store1.defs), 3371 instances: Object.assign(Object.assign({}, store0.instances), store1.instances), 3372 }; 3373 } 3374 function filterEventStoreDefs(eventStore, filterFunc) { 3375 let defs = filterHash(eventStore.defs, filterFunc); 3376 let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists? 3377 )); 3378 return { defs, instances }; 3379 } 3380 function excludeSubEventStore(master, sub) { 3381 let { defs, instances } = master; 3382 let filteredDefs = {}; 3383 let filteredInstances = {}; 3384 for (let defId in defs) { 3385 if (!sub.defs[defId]) { // not explicitly excluded
vendor: 6,832 bytes, lines 3386-3558
3386 filteredDefs[defId] = defs[defId]; 3387 } 3388 } 3389 for (let instanceId in instances) { 3390 if (!sub.instances[instanceId] && // not explicitly excluded 3391 filteredDefs[instances[instanceId].defId] // def wasn't filtered away 3392 ) { 3393 filteredInstances[instanceId] = instances[instanceId]; 3394 } 3395 } 3396 return { 3397 defs: filteredDefs, 3398 instances: filteredInstances, 3399 }; 3400 } 3401 3402 function normalizeConstraint(input, context) { 3403 if (Array.isArray(input)) { 3404 return parseEvents(input, null, context, true); // allowOpenRange=true 3405 } 3406 if (typeof input === 'object' && input) { // non-null object 3407 return parseEvents([input], null, context, true); // allowOpenRange=true 3408 } 3409 if (input != null) { 3410 return String(input); 3411 } 3412 return null; 3413 } 3414 3415 function parseClassNames(raw) { 3416 if (Array.isArray(raw)) { 3417 return raw; 3418 } 3419 if (typeof raw === 'string') { 3420 return raw.split(/\s+/); 3421 } 3422 return []; 3423 } 3424 3425 // TODO: better called "EventSettings" or "EventConfig" 3426 // TODO: move this file into structs 3427 // TODO: separate constraint/overlap/allow, because selection uses only that, not other props 3428 const EVENT_UI_REFINERS = { 3429 display: String, 3430 editable: Boolean, 3431 startEditable: Boolean, 3432 durationEditable: Boolean, 3433 constraint: identity, 3434 overlap: identity, 3435 allow: identity, 3436 className: parseClassNames, 3437 classNames: parseClassNames, 3438 color: String, 3439 backgroundColor: String, 3440 borderColor: String, 3441 textColor: String, 3442 }; 3443 const EMPTY_EVENT_UI = { 3444 display: null, 3445 startEditable: null, 3446 durationEditable: null, 3447 constraints: [], 3448 overlap: null, 3449 allows: [], 3450 backgroundColor: '', 3451 borderColor: '', 3452 textColor: '', 3453 classNames: [], 3454 }; 3455 function createEventUi(refined, context) { 3456 let constraint = normalizeConstraint(refined.constraint, context); 3457 return { 3458 display: refined.display || null, 3459 startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, 3460 durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, 3461 constraints: constraint != null ? [constraint] : [], 3462 overlap: refined.overlap != null ? refined.overlap : null, 3463 allows: refined.allow != null ? [refined.allow] : [], 3464 backgroundColor: refined.backgroundColor || refined.color || '', 3465 borderColor: refined.borderColor || refined.color || '', 3466 textColor: refined.textColor || '', 3467 classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural 3468 }; 3469 } 3470 // TODO: prevent against problems with <2 args! 3471 function combineEventUis(uis) { 3472 return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); 3473 } 3474 function combineTwoEventUis(item0, item1) { 3475 return { 3476 display: item1.display != null ? item1.display : item0.display, 3477 startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, 3478 durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, 3479 constraints: item0.constraints.concat(item1.constraints), 3480 overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, 3481 allows: item0.allows.concat(item1.allows), 3482 backgroundColor: item1.backgroundColor || item0.backgroundColor, 3483 borderColor: item1.borderColor || item0.borderColor, 3484 textColor: item1.textColor || item0.textColor, 3485 classNames: item0.classNames.concat(item1.classNames), 3486 }; 3487 } 3488 3489 const EVENT_SOURCE_REFINERS = { 3490 id: String, 3491 defaultAllDay: Boolean, 3492 url: String, 3493 format: String, 3494 events: identity, 3495 eventDataTransform: identity, 3496 // for any network-related sources 3497 success: identity, 3498 failure: identity, 3499 }; 3500 function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) { 3501 let rawObj; 3502 if (typeof raw === 'string') { 3503 rawObj = { url: raw }; 3504 } 3505 else if (typeof raw === 'function' || Array.isArray(raw)) { 3506 rawObj = { events: raw }; 3507 } 3508 else if (typeof raw === 'object' && raw) { // not null 3509 rawObj = raw; 3510 } 3511 if (rawObj) { 3512 let { refined, extra } = refineProps(rawObj, refiners); 3513 let metaRes = buildEventSourceMeta(refined, context); 3514 if (metaRes) { 3515 return { 3516 _raw: raw, 3517 isFetching: false, 3518 latestFetchId: '', 3519 fetchRange: null, 3520 defaultAllDay: refined.defaultAllDay, 3521 eventDataTransform: refined.eventDataTransform, 3522 success: refined.success, 3523 failure: refined.failure, 3524 publicId: refined.id || '', 3525 sourceId: guid(), 3526 sourceDefId: metaRes.sourceDefId, 3527 meta: metaRes.meta, 3528 ui: createEventUi(refined, context), 3529 extendedProps: extra, 3530 }; 3531 } 3532 } 3533 return null; 3534 } 3535 function buildEventSourceRefiners(context) { 3536 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners); 3537 } 3538 function buildEventSourceMeta(raw, context) { 3539 let defs = context.pluginHooks.eventSourceDefs; 3540 for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence 3541 let def = defs[i]; 3542 let meta = def.parseMeta(raw); 3543 if (meta) { 3544 return { sourceDefId: i, meta }; 3545 } 3546 } 3547 return null; 3548 } 3549 3550 function initEventSources(calendarOptions, dateProfile, context) { 3551 let activeRange = dateProfile ? dateProfile.activeRange : null; 3552 return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); 3553 } 3554 function reduceEventSources(eventSources, action, dateProfile, context) { 3555 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? 3556 switch (action.type) { 3557 case 'ADD_EVENT_SOURCES': // already parsed 3558 return addSources(eventSources, action.sources, activeRange, context);
vendor: 4,373 bytes, lines 3559-3658
3559 case 'REMOVE_EVENT_SOURCE': 3560 return removeSource(eventSources, action.sourceId); 3561 case 'PREV': // TODO: how do we track all actions that affect dateProfile :( 3562 case 'NEXT': 3563 case 'CHANGE_DATE': 3564 case 'CHANGE_VIEW_TYPE': 3565 if (dateProfile) { 3566 return fetchDirtySources(eventSources, activeRange, context); 3567 } 3568 return eventSources; 3569 case 'FETCH_EVENT_SOURCES': 3570 return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? 3571 arrayToHash(action.sourceIds) : 3572 excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); 3573 case 'RECEIVE_EVENTS': 3574 case 'RECEIVE_EVENT_ERROR': 3575 return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); 3576 case 'REMOVE_ALL_EVENT_SOURCES': 3577 return {}; 3578 default: 3579 return eventSources; 3580 } 3581 } 3582 function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { 3583 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? 3584 return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); 3585 } 3586 function computeEventSourcesLoading(eventSources) { 3587 for (let sourceId in eventSources) { 3588 if (eventSources[sourceId].isFetching) { 3589 return true; 3590 } 3591 } 3592 return false; 3593 } 3594 function addSources(eventSourceHash, sources, fetchRange, context) { 3595 let hash = {}; 3596 for (let source of sources) { 3597 hash[source.sourceId] = source; 3598 } 3599 if (fetchRange) { 3600 hash = fetchDirtySources(hash, fetchRange, context); 3601 } 3602 return Object.assign(Object.assign({}, eventSourceHash), hash); 3603 } 3604 function removeSource(eventSourceHash, sourceId) { 3605 return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId); 3606 } 3607 function fetchDirtySources(sourceHash, fetchRange, context) { 3608 return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context); 3609 } 3610 function isSourceDirty(eventSource, fetchRange, context) { 3611 if (!doesSourceNeedRange(eventSource, context)) { 3612 return !eventSource.latestFetchId; 3613 } 3614 return !context.options.lazyFetching || 3615 !eventSource.fetchRange || 3616 eventSource.isFetching || // always cancel outdated in-progress fetches 3617 fetchRange.start < eventSource.fetchRange.start || 3618 fetchRange.end > eventSource.fetchRange.end; 3619 } 3620 function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { 3621 let nextSources = {}; 3622 for (let sourceId in prevSources) { 3623 let source = prevSources[sourceId]; 3624 if (sourceIdHash[sourceId]) { 3625 nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); 3626 } 3627 else { 3628 nextSources[sourceId] = source; 3629 } 3630 } 3631 return nextSources; 3632 } 3633 function fetchSource(eventSource, fetchRange, isRefetch, context) { 3634 let { options, calendarApi } = context; 3635 let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; 3636 let fetchId = guid(); 3637 sourceDef.fetch({ 3638 eventSource, 3639 range: fetchRange, 3640 isRefetch, 3641 context, 3642 }, (res) => { 3643 let { rawEvents } = res; 3644 if (options.eventSourceSuccess) { 3645 rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents; 3646 } 3647 if (eventSource.success) { 3648 rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents; 3649 } 3650 context.dispatch({ 3651 type: 'RECEIVE_EVENTS', 3652 sourceId: eventSource.sourceId, 3653 fetchId, 3654 fetchRange, 3655 rawEvents, 3656 }); 3657 }, (error) => { 3658 let errorHandled = false;
vendor: 7,952 bytes, lines 3659-3840
3659 if (options.eventSourceFailure) { 3660 options.eventSourceFailure.call(calendarApi, error); 3661 errorHandled = true; 3662 } 3663 if (eventSource.failure) { 3664 eventSource.failure(error); 3665 errorHandled = true; 3666 } 3667 if (!errorHandled) { 3668 console.warn(error.message, error); 3669 } 3670 context.dispatch({ 3671 type: 'RECEIVE_EVENT_ERROR', 3672 sourceId: eventSource.sourceId, 3673 fetchId, 3674 fetchRange, 3675 error, 3676 }); 3677 }); 3678 return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); 3679 } 3680 function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { 3681 let eventSource = sourceHash[sourceId]; 3682 if (eventSource && // not already removed 3683 fetchId === eventSource.latestFetchId) { 3684 return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) }); 3685 } 3686 return sourceHash; 3687 } 3688 function excludeStaticSources(eventSources, context) { 3689 return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context)); 3690 } 3691 function parseInitialSources(rawOptions, context) { 3692 let refiners = buildEventSourceRefiners(context); 3693 let rawSources = [].concat(rawOptions.eventSources || []); 3694 let sources = []; // parsed 3695 if (rawOptions.initialEvents) { 3696 rawSources.unshift(rawOptions.initialEvents); 3697 } 3698 if (rawOptions.events) { 3699 rawSources.unshift(rawOptions.events); 3700 } 3701 for (let rawSource of rawSources) { 3702 let source = parseEventSource(rawSource, context, refiners); 3703 if (source) { 3704 sources.push(source); 3705 } 3706 } 3707 return sources; 3708 } 3709 function doesSourceNeedRange(eventSource, context) { 3710 let defs = context.pluginHooks.eventSourceDefs; 3711 return !defs[eventSource.sourceDefId].ignoreRange; 3712 } 3713 3714 function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { 3715 switch (action.type) { 3716 case 'RECEIVE_EVENTS': // raw 3717 return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); 3718 case 'ADD_EVENTS': // already parsed, but not expanded 3719 return addEvent(eventStore, action.eventStore, // new ones 3720 dateProfile ? dateProfile.activeRange : null, context); 3721 case 'RESET_EVENTS': 3722 return action.eventStore; 3723 case 'MERGE_EVENTS': // already parsed and expanded 3724 return mergeEventStores(eventStore, action.eventStore); 3725 case 'PREV': // TODO: how do we track all actions that affect dateProfile :( 3726 case 'NEXT': 3727 case 'CHANGE_DATE': 3728 case 'CHANGE_VIEW_TYPE': 3729 if (dateProfile) { 3730 return expandRecurring(eventStore, dateProfile.activeRange, context); 3731 } 3732 return eventStore; 3733 case 'REMOVE_EVENTS': 3734 return excludeSubEventStore(eventStore, action.eventStore); 3735 case 'REMOVE_EVENT_SOURCE': 3736 return excludeEventsBySourceId(eventStore, action.sourceId); 3737 case 'REMOVE_ALL_EVENT_SOURCES': 3738 return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id 3739 )); 3740 case 'REMOVE_ALL_EVENTS': 3741 return createEmptyEventStore(); 3742 default: 3743 return eventStore; 3744 } 3745 } 3746 function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { 3747 if (eventSource && // not already removed 3748 fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources 3749 ) { 3750 let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); 3751 if (fetchRange) { 3752 subset = expandRecurring(subset, fetchRange, context); 3753 } 3754 return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); 3755 } 3756 return eventStore; 3757 } 3758 function transformRawEvents(rawEvents, eventSource, context) { 3759 let calEachTransform = context.options.eventDataTransform; 3760 let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; 3761 if (sourceEachTransform) { 3762 rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); 3763 } 3764 if (calEachTransform) { 3765 rawEvents = transformEachRawEvent(rawEvents, calEachTransform); 3766 } 3767 return rawEvents; 3768 } 3769 function transformEachRawEvent(rawEvents, func) { 3770 let refinedEvents; 3771 if (!func) { 3772 refinedEvents = rawEvents; 3773 } 3774 else { 3775 refinedEvents = []; 3776 for (let rawEvent of rawEvents) { 3777 let refinedEvent = func(rawEvent); 3778 if (refinedEvent) { 3779 refinedEvents.push(refinedEvent); 3780 } 3781 else if (refinedEvent == null) { 3782 refinedEvents.push(rawEvent); 3783 } // if a different falsy value, do nothing 3784 } 3785 } 3786 return refinedEvents; 3787 } 3788 function addEvent(eventStore, subset, expandRange, context) { 3789 if (expandRange) { 3790 subset = expandRecurring(subset, expandRange, context); 3791 } 3792 return mergeEventStores(eventStore, subset); 3793 } 3794 function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { 3795 let { defs } = eventStore; 3796 let instances = mapHash(eventStore.instances, (instance) => { 3797 let def = defs[instance.defId]; 3798 if (def.allDay || def.recurringDef) { 3799 return instance; // isn't dependent on timezone 3800 } 3801 return Object.assign(Object.assign({}, instance), { range: { 3802 start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), 3803 end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), 3804 }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); 3805 }); 3806 return { defs, instances }; 3807 } 3808 function excludeEventsBySourceId(eventStore, sourceId) { 3809 return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId); 3810 } 3811 // QUESTION: why not just return instances? do a general object-property-exclusion util 3812 function excludeInstances(eventStore, removals) { 3813 return { 3814 defs: eventStore.defs, 3815 instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]), 3816 }; 3817 } 3818 3819 class Emitter { 3820 constructor() { 3821 this.handlers = {}; 3822 this.thisContext = null; 3823 } 3824 setThisContext(thisContext) { 3825 this.thisContext = thisContext; 3826 } 3827 setOptions(options) { 3828 this.options = options; 3829 } 3830 on(type, handler) { 3831 addToHash(this.handlers, type, handler); 3832 } 3833 off(type, handler) { 3834 removeFromHash(this.handlers, type, handler); 3835 } 3836 trigger(type, ...args) { 3837 let attachedHandlers = this.handlers[type] || []; 3838 let optionHandler = this.options && this.options[type]; 3839 let handlers = [].concat(optionHandler || [], attachedHandlers); 3840 for (let handler of handlers) {
vendor: 7,958 bytes, lines 3841-4039
3841 handler.apply(this.thisContext, args); 3842 } 3843 } 3844 hasHandlers(type) { 3845 return Boolean((this.handlers[type] && this.handlers[type].length) || 3846 (this.options && this.options[type])); 3847 } 3848 } 3849 function addToHash(hash, type, handler) { 3850 (hash[type] || (hash[type] = [])) 3851 .push(handler); 3852 } 3853 function removeFromHash(hash, type, handler) { 3854 if (handler) { 3855 if (hash[type]) { 3856 hash[type] = hash[type].filter((func) => func !== handler); 3857 } 3858 } 3859 else { 3860 delete hash[type]; // remove all handler funcs for this type 3861 } 3862 } 3863 3864 const DEF_DEFAULTS = { 3865 startTime: '09:00', 3866 endTime: '17:00', 3867 daysOfWeek: [1, 2, 3, 4, 5], 3868 display: 'inverse-background', 3869 classNames: 'fc-non-business', 3870 groupId: '_businessHours', // so multiple defs get grouped 3871 }; 3872 /* 3873 TODO: pass around as EventDefHash!!! 3874 */ 3875 function parseBusinessHours(input, context) { 3876 return parseEvents(refineInputs(input), null, context); 3877 } 3878 function refineInputs(input) { 3879 let rawDefs; 3880 if (input === true) { 3881 rawDefs = [{}]; // will get DEF_DEFAULTS verbatim 3882 } 3883 else if (Array.isArray(input)) { 3884 // if specifying an array, every sub-definition NEEDS a day-of-week 3885 rawDefs = input.filter((rawDef) => rawDef.daysOfWeek); 3886 } 3887 else if (typeof input === 'object' && input) { // non-null object 3888 rawDefs = [input]; 3889 } 3890 else { // is probably false 3891 rawDefs = []; 3892 } 3893 rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef))); 3894 return rawDefs; 3895 } 3896 3897 let eventSourceDef$2 = { 3898 ignoreRange: true, 3899 parseMeta(refined) { 3900 if (Array.isArray(refined.events)) { 3901 return refined.events; 3902 } 3903 return null; 3904 }, 3905 fetch(arg, successCallback) { 3906 successCallback({ 3907 rawEvents: arg.eventSource.meta, 3908 }); 3909 }, 3910 }; 3911 const arrayEventSourcePlugin = createPlugin({ 3912 name: 'array-event-source', 3913 eventSourceDefs: [eventSourceDef$2], 3914 }); 3915 3916 function triggerDateSelect(selection, pev, context) { 3917 context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); 3918 } 3919 function triggerDateUnselect(pev, context) { 3920 context.emitter.trigger('unselect', { 3921 jsEvent: pev ? pev.origEvent : null, 3922 view: context.viewApi || context.calendarApi.view, 3923 }); 3924 } 3925 function buildDateSpanApiWithContext(dateSpan, context) { 3926 let props = {}; 3927 for (let transform of context.pluginHooks.dateSpanTransforms) { 3928 Object.assign(props, transform(dateSpan, context)); 3929 } 3930 Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); 3931 return props; 3932 } 3933 // Given an event's allDay status and start date, return what its fallback end date should be. 3934 // TODO: rename to computeDefaultEventEnd 3935 function getDefaultEventEnd(allDay, marker, context) { 3936 let { dateEnv, options } = context; 3937 let end = marker; 3938 if (allDay) { 3939 end = startOfDay(end); 3940 end = dateEnv.add(end, options.defaultAllDayEventDuration); 3941 } 3942 else { 3943 end = dateEnv.add(end, options.defaultTimedEventDuration); 3944 } 3945 return end; 3946 } 3947 3948 // applies the mutation to ALL defs/instances within the event store 3949 function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { 3950 let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); 3951 let dest = createEmptyEventStore(); 3952 for (let defId in eventStore.defs) { 3953 let def = eventStore.defs[defId]; 3954 dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); 3955 } 3956 for (let instanceId in eventStore.instances) { 3957 let instance = eventStore.instances[instanceId]; 3958 let def = dest.defs[instance.defId]; // important to grab the newly modified def 3959 dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); 3960 } 3961 return dest; 3962 } 3963 function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { 3964 let standardProps = mutation.standardProps || {}; 3965 // if hasEnd has not been specified, guess a good value based on deltas. 3966 // if duration will change, there's no way the default duration will persist, 3967 // and thus, we need to mark the event as having a real end 3968 if (standardProps.hasEnd == null && 3969 eventConfig.durationEditable && 3970 (mutation.startDelta || mutation.endDelta)) { 3971 standardProps.hasEnd = true; // TODO: is this mutation okay? 3972 } 3973 let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) }); 3974 if (mutation.extendedProps) { 3975 copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps); 3976 } 3977 for (let applier of context.pluginHooks.eventDefMutationAppliers) { 3978 applier(copy, mutation, context); 3979 } 3980 if (!copy.hasEnd && context.options.forceEventDuration) { 3981 copy.hasEnd = true; 3982 } 3983 return copy; 3984 } 3985 function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef 3986 eventConfig, mutation, context) { 3987 let { dateEnv } = context; 3988 let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; 3989 let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; 3990 let copy = Object.assign({}, eventInstance); 3991 if (forceAllDay) { 3992 copy.range = computeAlignedDayRange(copy.range); 3993 } 3994 if (mutation.datesDelta && eventConfig.startEditable) { 3995 copy.range = { 3996 start: dateEnv.add(copy.range.start, mutation.datesDelta), 3997 end: dateEnv.add(copy.range.end, mutation.datesDelta), 3998 }; 3999 } 4000 if (mutation.startDelta && eventConfig.durationEditable) { 4001 copy.range = { 4002 start: dateEnv.add(copy.range.start, mutation.startDelta), 4003 end: copy.range.end, 4004 }; 4005 } 4006 if (mutation.endDelta && eventConfig.durationEditable) { 4007 copy.range = { 4008 start: copy.range.start, 4009 end: dateEnv.add(copy.range.end, mutation.endDelta), 4010 }; 4011 } 4012 if (clearEnd) { 4013 copy.range = { 4014 start: copy.range.start, 4015 end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), 4016 }; 4017 } 4018 // in case event was all-day but the supplied deltas were not 4019 // better util for this? 4020 if (eventDef.allDay) { 4021 copy.range = { 4022 start: startOfDay(copy.range.start), 4023 end: startOfDay(copy.range.end), 4024 }; 4025 } 4026 // handle invalid durations 4027 if (copy.range.end < copy.range.start) { 4028 copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); 4029 } 4030 return copy; 4031 } 4032 4033 class EventSourceImpl { 4034 constructor(context, internalEventSource) { 4035 this.context = context; 4036 this.internalEventSource = internalEventSource; 4037 } 4038 remove() { 4039 this.context.dispatch({
vendor: 26,379 bytes, lines 4040-4705
4040 type: 'REMOVE_EVENT_SOURCE', 4041 sourceId: this.internalEventSource.sourceId, 4042 }); 4043 } 4044 refetch() { 4045 this.context.dispatch({ 4046 type: 'FETCH_EVENT_SOURCES', 4047 sourceIds: [this.internalEventSource.sourceId], 4048 isRefetch: true, 4049 }); 4050 } 4051 get id() { 4052 return this.internalEventSource.publicId; 4053 } 4054 get url() { 4055 return this.internalEventSource.meta.url; 4056 } 4057 get format() { 4058 return this.internalEventSource.meta.format; // TODO: bad. not guaranteed 4059 } 4060 } 4061 4062 class EventImpl { 4063 // instance will be null if expressing a recurring event that has no current instances, 4064 // OR if trying to validate an incoming external event that has no dates assigned 4065 constructor(context, def, instance) { 4066 this._context = context; 4067 this._def = def; 4068 this._instance = instance || null; 4069 } 4070 /* 4071 TODO: make event struct more responsible for this 4072 */ 4073 setProp(name, val) { 4074 if (name in EVENT_DATE_REFINERS) { 4075 console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); 4076 // TODO: make proper aliasing system? 4077 } 4078 else if (name === 'id') { 4079 val = EVENT_NON_DATE_REFINERS[name](val); 4080 this.mutate({ 4081 standardProps: { publicId: val }, // hardcoded internal name 4082 }); 4083 } 4084 else if (name in EVENT_NON_DATE_REFINERS) { 4085 val = EVENT_NON_DATE_REFINERS[name](val); 4086 this.mutate({ 4087 standardProps: { [name]: val }, 4088 }); 4089 } 4090 else if (name in EVENT_UI_REFINERS) { 4091 let ui = EVENT_UI_REFINERS[name](val); 4092 if (name === 'color') { 4093 ui = { backgroundColor: val, borderColor: val }; 4094 } 4095 else if (name === 'editable') { 4096 ui = { startEditable: val, durationEditable: val }; 4097 } 4098 else { 4099 ui = { [name]: val }; 4100 } 4101 this.mutate({ 4102 standardProps: { ui }, 4103 }); 4104 } 4105 else { 4106 console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`); 4107 } 4108 } 4109 setExtendedProp(name, val) { 4110 this.mutate({ 4111 extendedProps: { [name]: val }, 4112 }); 4113 } 4114 setStart(startInput, options = {}) { 4115 let { dateEnv } = this._context; 4116 let start = dateEnv.createMarker(startInput); 4117 if (start && this._instance) { // TODO: warning if parsed bad 4118 let instanceRange = this._instance.range; 4119 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? 4120 if (options.maintainDuration) { 4121 this.mutate({ datesDelta: startDelta }); 4122 } 4123 else { 4124 this.mutate({ startDelta }); 4125 } 4126 } 4127 } 4128 setEnd(endInput, options = {}) { 4129 let { dateEnv } = this._context; 4130 let end; 4131 if (endInput != null) { 4132 end = dateEnv.createMarker(endInput); 4133 if (!end) { 4134 return; // TODO: warning if parsed bad 4135 } 4136 } 4137 if (this._instance) { 4138 if (end) { 4139 let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); 4140 this.mutate({ endDelta }); 4141 } 4142 else { 4143 this.mutate({ standardProps: { hasEnd: false } }); 4144 } 4145 } 4146 } 4147 setDates(startInput, endInput, options = {}) { 4148 let { dateEnv } = this._context; 4149 let standardProps = { allDay: options.allDay }; 4150 let start = dateEnv.createMarker(startInput); 4151 let end; 4152 if (!start) { 4153 return; // TODO: warning if parsed bad 4154 } 4155 if (endInput != null) { 4156 end = dateEnv.createMarker(endInput); 4157 if (!end) { // TODO: warning if parsed bad 4158 return; 4159 } 4160 } 4161 if (this._instance) { 4162 let instanceRange = this._instance.range; 4163 // when computing the diff for an event being converted to all-day, 4164 // compute diff off of the all-day values the way event-mutation does. 4165 if (options.allDay === true) { 4166 instanceRange = computeAlignedDayRange(instanceRange); 4167 } 4168 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); 4169 if (end) { 4170 let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); 4171 if (durationsEqual(startDelta, endDelta)) { 4172 this.mutate({ datesDelta: startDelta, standardProps }); 4173 } 4174 else { 4175 this.mutate({ startDelta, endDelta, standardProps }); 4176 } 4177 } 4178 else { // means "clear the end" 4179 standardProps.hasEnd = false; 4180 this.mutate({ datesDelta: startDelta, standardProps }); 4181 } 4182 } 4183 } 4184 moveStart(deltaInput) { 4185 let delta = createDuration(deltaInput); 4186 if (delta) { // TODO: warning if parsed bad 4187 this.mutate({ startDelta: delta }); 4188 } 4189 } 4190 moveEnd(deltaInput) { 4191 let delta = createDuration(deltaInput); 4192 if (delta) { // TODO: warning if parsed bad 4193 this.mutate({ endDelta: delta }); 4194 } 4195 } 4196 moveDates(deltaInput) { 4197 let delta = createDuration(deltaInput); 4198 if (delta) { // TODO: warning if parsed bad 4199 this.mutate({ datesDelta: delta }); 4200 } 4201 } 4202 setAllDay(allDay, options = {}) { 4203 let standardProps = { allDay }; 4204 let { maintainDuration } = options; 4205 if (maintainDuration == null) { 4206 maintainDuration = this._context.options.allDayMaintainDuration; 4207 } 4208 if (this._def.allDay !== allDay) { 4209 standardProps.hasEnd = maintainDuration; 4210 } 4211 this.mutate({ standardProps }); 4212 } 4213 formatRange(formatInput) { 4214 let { dateEnv } = this._context; 4215 let instance = this._instance; 4216 let formatter = createFormatter(formatInput); 4217 if (this._def.hasEnd) { 4218 return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { 4219 forcedStartTzo: instance.forcedStartTzo, 4220 forcedEndTzo: instance.forcedEndTzo, 4221 }); 4222 } 4223 return dateEnv.format(instance.range.start, formatter, { 4224 forcedTzo: instance.forcedStartTzo, 4225 }); 4226 } 4227 mutate(mutation) { 4228 let instance = this._instance; 4229 if (instance) { 4230 let def = this._def; 4231 let context = this._context; 4232 let { eventStore } = context.getCurrentData(); 4233 let relevantEvents = getRelevantEvents(eventStore, instance.instanceId); 4234 let eventConfigBase = { 4235 '': { 4236 display: '', 4237 startEditable: true, 4238 durationEditable: true, 4239 constraints: [], 4240 overlap: null, 4241 allows: [], 4242 backgroundColor: '', 4243 borderColor: '', 4244 textColor: '', 4245 classNames: [], 4246 }, 4247 }; 4248 relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context); 4249 let oldEvent = new EventImpl(context, def, instance); // snapshot 4250 this._def = relevantEvents.defs[def.defId]; 4251 this._instance = relevantEvents.instances[instance.instanceId]; 4252 context.dispatch({ 4253 type: 'MERGE_EVENTS', 4254 eventStore: relevantEvents, 4255 }); 4256 context.emitter.trigger('eventChange', { 4257 oldEvent, 4258 event: this, 4259 relatedEvents: buildEventApis(relevantEvents, context, instance), 4260 revert() { 4261 context.dispatch({ 4262 type: 'RESET_EVENTS', 4263 eventStore, // the ORIGINAL store 4264 }); 4265 }, 4266 }); 4267 } 4268 } 4269 remove() { 4270 let context = this._context; 4271 let asStore = eventApiToStore(this); 4272 context.dispatch({ 4273 type: 'REMOVE_EVENTS', 4274 eventStore: asStore, 4275 }); 4276 context.emitter.trigger('eventRemove', { 4277 event: this, 4278 relatedEvents: [], 4279 revert() { 4280 context.dispatch({ 4281 type: 'MERGE_EVENTS', 4282 eventStore: asStore, 4283 }); 4284 }, 4285 }); 4286 } 4287 get source() { 4288 let { sourceId } = this._def; 4289 if (sourceId) { 4290 return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]); 4291 } 4292 return null; 4293 } 4294 get start() { 4295 return this._instance ? 4296 this._context.dateEnv.toDate(this._instance.range.start) : 4297 null; 4298 } 4299 get end() { 4300 return (this._instance && this._def.hasEnd) ? 4301 this._context.dateEnv.toDate(this._instance.range.end) : 4302 null; 4303 } 4304 get startStr() { 4305 let instance = this._instance; 4306 if (instance) { 4307 return this._context.dateEnv.formatIso(instance.range.start, { 4308 omitTime: this._def.allDay, 4309 forcedTzo: instance.forcedStartTzo, 4310 }); 4311 } 4312 return ''; 4313 } 4314 get endStr() { 4315 let instance = this._instance; 4316 if (instance && this._def.hasEnd) { 4317 return this._context.dateEnv.formatIso(instance.range.end, { 4318 omitTime: this._def.allDay, 4319 forcedTzo: instance.forcedEndTzo, 4320 }); 4321 } 4322 return ''; 4323 } 4324 // computable props that all access the def 4325 // TODO: find a TypeScript-compatible way to do this at scale 4326 get id() { return this._def.publicId; } 4327 get groupId() { return this._def.groupId; } 4328 get allDay() { return this._def.allDay; } 4329 get title() { return this._def.title; } 4330 get url() { return this._def.url; } 4331 get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier 4332 get startEditable() { return this._def.ui.startEditable; } 4333 get durationEditable() { return this._def.ui.durationEditable; } 4334 get constraint() { return this._def.ui.constraints[0] || null; } 4335 get overlap() { return this._def.ui.overlap; } 4336 get allow() { return this._def.ui.allows[0] || null; } 4337 get backgroundColor() { return this._def.ui.backgroundColor; } 4338 get borderColor() { return this._def.ui.borderColor; } 4339 get textColor() { return this._def.ui.textColor; } 4340 // NOTE: user can't modify these because Object.freeze was called in event-def parsing 4341 get classNames() { return this._def.ui.classNames; } 4342 get extendedProps() { return this._def.extendedProps; } 4343 toPlainObject(settings = {}) { 4344 let def = this._def; 4345 let { ui } = def; 4346 let { startStr, endStr } = this; 4347 let res = {}; 4348 if (def.title) { 4349 res.title = def.title; 4350 } 4351 if (startStr) { 4352 res.start = startStr; 4353 } 4354 if (endStr) { 4355 res.end = endStr; 4356 } 4357 if (def.publicId) { 4358 res.id = def.publicId; 4359 } 4360 if (def.groupId) { 4361 res.groupId = def.groupId; 4362 } 4363 if (def.url) { 4364 res.url = def.url; 4365 } 4366 if (ui.display && ui.display !== 'auto') { 4367 res.display = ui.display; 4368 } 4369 // TODO: what about recurring-event properties??? 4370 // TODO: include startEditable/durationEditable/constraint/overlap/allow 4371 if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { 4372 res.color = ui.backgroundColor; 4373 } 4374 else { 4375 if (ui.backgroundColor) { 4376 res.backgroundColor = ui.backgroundColor; 4377 } 4378 if (ui.borderColor) { 4379 res.borderColor = ui.borderColor; 4380 } 4381 } 4382 if (ui.textColor) { 4383 res.textColor = ui.textColor; 4384 } 4385 if (ui.classNames.length) { 4386 res.classNames = ui.classNames; 4387 } 4388 if (Object.keys(def.extendedProps).length) { 4389 if (settings.collapseExtendedProps) { 4390 Object.assign(res, def.extendedProps); 4391 } 4392 else { 4393 res.extendedProps = def.extendedProps; 4394 } 4395 } 4396 return res; 4397 } 4398 toJSON() { 4399 return this.toPlainObject(); 4400 } 4401 } 4402 function eventApiToStore(eventApi) { 4403 let def = eventApi._def; 4404 let instance = eventApi._instance; 4405 return { 4406 defs: { [def.defId]: def }, 4407 instances: instance 4408 ? { [instance.instanceId]: instance } 4409 : {}, 4410 }; 4411 } 4412 function buildEventApis(eventStore, context, excludeInstance) { 4413 let { defs, instances } = eventStore; 4414 let eventApis = []; 4415 let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; 4416 for (let id in instances) { 4417 let instance = instances[id]; 4418 let def = defs[instance.defId]; 4419 if (instance.instanceId !== excludeInstanceId) { 4420 eventApis.push(new EventImpl(context, def, instance)); 4421 } 4422 } 4423 return eventApis; 4424 } 4425 4426 /* 4427 Specifying nextDayThreshold signals that all-day ranges should be sliced. 4428 */ 4429 function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { 4430 let inverseBgByGroupId = {}; 4431 let inverseBgByDefId = {}; 4432 let defByGroupId = {}; 4433 let bgRanges = []; 4434 let fgRanges = []; 4435 let eventUis = compileEventUis(eventStore.defs, eventUiBases); 4436 for (let defId in eventStore.defs) { 4437 let def = eventStore.defs[defId]; 4438 let ui = eventUis[def.defId]; 4439 if (ui.display === 'inverse-background') { 4440 if (def.groupId) { 4441 inverseBgByGroupId[def.groupId] = []; 4442 if (!defByGroupId[def.groupId]) { 4443 defByGroupId[def.groupId] = def; 4444 } 4445 } 4446 else { 4447 inverseBgByDefId[defId] = []; 4448 } 4449 } 4450 } 4451 for (let instanceId in eventStore.instances) { 4452 let instance = eventStore.instances[instanceId]; 4453 let def = eventStore.defs[instance.defId]; 4454 let ui = eventUis[def.defId]; 4455 let origRange = instance.range; 4456 let normalRange = (!def.allDay && nextDayThreshold) ? 4457 computeVisibleDayRange(origRange, nextDayThreshold) : 4458 origRange; 4459 let slicedRange = intersectRanges(normalRange, framingRange); 4460 if (slicedRange) { 4461 if (ui.display === 'inverse-background') { 4462 if (def.groupId) { 4463 inverseBgByGroupId[def.groupId].push(slicedRange); 4464 } 4465 else { 4466 inverseBgByDefId[instance.defId].push(slicedRange); 4467 } 4468 } 4469 else if (ui.display !== 'none') { 4470 (ui.display === 'background' ? bgRanges : fgRanges).push({ 4471 def, 4472 ui, 4473 instance, 4474 range: slicedRange, 4475 isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), 4476 isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), 4477 }); 4478 } 4479 } 4480 } 4481 for (let groupId in inverseBgByGroupId) { // BY GROUP 4482 let ranges = inverseBgByGroupId[groupId]; 4483 let invertedRanges = invertRanges(ranges, framingRange); 4484 for (let invertedRange of invertedRanges) { 4485 let def = defByGroupId[groupId]; 4486 let ui = eventUis[def.defId]; 4487 bgRanges.push({ 4488 def, 4489 ui, 4490 instance: null, 4491 range: invertedRange, 4492 isStart: false, 4493 isEnd: false, 4494 }); 4495 } 4496 } 4497 for (let defId in inverseBgByDefId) { 4498 let ranges = inverseBgByDefId[defId]; 4499 let invertedRanges = invertRanges(ranges, framingRange); 4500 for (let invertedRange of invertedRanges) { 4501 bgRanges.push({ 4502 def: eventStore.defs[defId], 4503 ui: eventUis[defId], 4504 instance: null, 4505 range: invertedRange, 4506 isStart: false, 4507 isEnd: false, 4508 }); 4509 } 4510 } 4511 return { bg: bgRanges, fg: fgRanges }; 4512 } 4513 function hasBgRendering(def) { 4514 return def.ui.display === 'background' || def.ui.display === 'inverse-background'; 4515 } 4516 function setElSeg(el, seg) { 4517 el.fcSeg = seg; 4518 } 4519 function getElSeg(el) { 4520 return el.fcSeg || 4521 el.parentNode.fcSeg || // for the harness 4522 null; 4523 } 4524 // event ui computation 4525 function compileEventUis(eventDefs, eventUiBases) { 4526 return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases)); 4527 } 4528 function compileEventUi(eventDef, eventUiBases) { 4529 let uis = []; 4530 if (eventUiBases['']) { 4531 uis.push(eventUiBases['']); 4532 } 4533 if (eventUiBases[eventDef.defId]) { 4534 uis.push(eventUiBases[eventDef.defId]); 4535 } 4536 uis.push(eventDef.ui); 4537 return combineEventUis(uis); 4538 } 4539 function sortEventSegs(segs, eventOrderSpecs) { 4540 let objs = segs.map(buildSegCompareObj); 4541 objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs)); 4542 return objs.map((c) => c._seg); 4543 } 4544 // returns a object with all primitive props that can be compared 4545 function buildSegCompareObj(seg) { 4546 let { eventRange } = seg; 4547 let eventDef = eventRange.def; 4548 let range = eventRange.instance ? eventRange.instance.range : eventRange.range; 4549 let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events 4550 let end = range.end ? range.end.valueOf() : 0; // " 4551 return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start, 4552 end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); 4553 } 4554 function computeSegDraggable(seg, context) { 4555 let { pluginHooks } = context; 4556 let transformers = pluginHooks.isDraggableTransformers; 4557 let { def, ui } = seg.eventRange; 4558 let val = ui.startEditable; 4559 for (let transformer of transformers) { 4560 val = transformer(val, def, ui, context); 4561 } 4562 return val; 4563 } 4564 function computeSegStartResizable(seg, context) { 4565 return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; 4566 } 4567 function computeSegEndResizable(seg, context) { 4568 return seg.isEnd && seg.eventRange.ui.durationEditable; 4569 } 4570 function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true 4571 defaultDisplayEventEnd, // defaults to true 4572 startOverride, endOverride) { 4573 let { dateEnv, options } = context; 4574 let { displayEventTime, displayEventEnd } = options; 4575 let eventDef = seg.eventRange.def; 4576 let eventInstance = seg.eventRange.instance; 4577 if (displayEventTime == null) { 4578 displayEventTime = defaultDisplayEventTime !== false; 4579 } 4580 if (displayEventEnd == null) { 4581 displayEventEnd = defaultDisplayEventEnd !== false; 4582 } 4583 let wholeEventStart = eventInstance.range.start; 4584 let wholeEventEnd = eventInstance.range.end; 4585 let segStart = startOverride || seg.start || seg.eventRange.range.start; 4586 let segEnd = endOverride || seg.end || seg.eventRange.range.end; 4587 let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); 4588 let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); 4589 if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { 4590 segStart = isStartDay ? wholeEventStart : segStart; 4591 segEnd = isEndDay ? wholeEventEnd : segEnd; 4592 if (displayEventEnd && eventDef.hasEnd) { 4593 return dateEnv.formatRange(segStart, segEnd, timeFormat, { 4594 forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, 4595 forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, 4596 }); 4597 } 4598 return dateEnv.format(segStart, timeFormat, { 4599 forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same 4600 }); 4601 } 4602 return ''; 4603 } 4604 function getSegMeta(seg, todayRange, nowDate) { 4605 let segRange = seg.eventRange.range; 4606 return { 4607 isPast: segRange.end < (nowDate || todayRange.start), 4608 isFuture: segRange.start >= (nowDate || todayRange.end), 4609 isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), 4610 }; 4611 } 4612 function getEventClassNames(props) { 4613 let classNames = ['fc-event']; 4614 if (props.isMirror) { 4615 classNames.push('fc-event-mirror'); 4616 } 4617 if (props.isDraggable) { 4618 classNames.push('fc-event-draggable'); 4619 } 4620 if (props.isStartResizable || props.isEndResizable) { 4621 classNames.push('fc-event-resizable'); 4622 } 4623 if (props.isDragging) { 4624 classNames.push('fc-event-dragging'); 4625 } 4626 if (props.isResizing) { 4627 classNames.push('fc-event-resizing'); 4628 } 4629 if (props.isSelected) { 4630 classNames.push('fc-event-selected'); 4631 } 4632 if (props.isStart) { 4633 classNames.push('fc-event-start'); 4634 } 4635 if (props.isEnd) { 4636 classNames.push('fc-event-end'); 4637 } 4638 if (props.isPast) { 4639 classNames.push('fc-event-past'); 4640 } 4641 if (props.isToday) { 4642 classNames.push('fc-event-today'); 4643 } 4644 if (props.isFuture) { 4645 classNames.push('fc-event-future'); 4646 } 4647 return classNames; 4648 } 4649 function buildEventRangeKey(eventRange) { 4650 return eventRange.instance 4651 ? eventRange.instance.instanceId 4652 : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`; 4653 // inverse-background events don't have specific instances. TODO: better solution 4654 } 4655 function getSegAnchorAttrs(seg, context) { 4656 let { def, instance } = seg.eventRange; 4657 let { url } = def; 4658 if (url) { 4659 return { href: url }; 4660 } 4661 let { emitter, options } = context; 4662 let { eventInteractive } = options; 4663 if (eventInteractive == null) { 4664 eventInteractive = def.interactive; 4665 if (eventInteractive == null) { 4666 eventInteractive = Boolean(emitter.hasHandlers('eventClick')); 4667 } 4668 } 4669 // mock what happens in EventClicking 4670 if (eventInteractive) { 4671 // only attach keyboard-related handlers because click handler is already done in EventClicking 4672 return createAriaKeyboardAttrs((ev) => { 4673 emitter.trigger('eventClick', { 4674 el: ev.target, 4675 event: new EventImpl(context, def, instance), 4676 jsEvent: ev, 4677 view: context.viewApi, 4678 }); 4679 }); 4680 } 4681 return {}; 4682 } 4683 4684 const STANDARD_PROPS = { 4685 start: identity, 4686 end: identity, 4687 allDay: Boolean, 4688 }; 4689 function parseDateSpan(raw, dateEnv, defaultDuration) { 4690 let span = parseOpenDateSpan(raw, dateEnv); 4691 let { range } = span; 4692 if (!range.start) { 4693 return null; 4694 } 4695 if (!range.end) { 4696 if (defaultDuration == null) { 4697 return null; 4698 } 4699 range.end = dateEnv.add(range.start, defaultDuration); 4700 } 4701 return span; 4702 } 4703 /* 4704 TODO: somehow combine with parseRange? 4705 Will return null if the start/end props were present but parsed invali
vendor: 4,475 bytes, lines 4705-4818
4705dly. 4706 */ 4707 function parseOpenDateSpan(raw, dateEnv) { 4708 let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS); 4709 let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; 4710 let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; 4711 let { allDay } = standardProps; 4712 if (allDay == null) { 4713 allDay = (startMeta && startMeta.isTimeUnspecified) && 4714 (!endMeta || endMeta.isTimeUnspecified); 4715 } 4716 return Object.assign({ range: { 4717 start: startMeta ? startMeta.marker : null, 4718 end: endMeta ? endMeta.marker : null, 4719 }, allDay }, extra); 4720 } 4721 function isDateSpansEqual(span0, span1) { 4722 return rangesEqual(span0.range, span1.range) && 4723 span0.allDay === span1.allDay && 4724 isSpanPropsEqual(span0, span1); 4725 } 4726 // the NON-DATE-RELATED props 4727 function isSpanPropsEqual(span0, span1) { 4728 for (let propName in span1) { 4729 if (propName !== 'range' && propName !== 'allDay') { 4730 if (span0[propName] !== span1[propName]) { 4731 return false; 4732 } 4733 } 4734 } 4735 // are there any props that span0 has that span1 DOESN'T have? 4736 // both have range/allDay, so no need to special-case. 4737 for (let propName in span0) { 4738 if (!(propName in span1)) { 4739 return false; 4740 } 4741 } 4742 return true; 4743 } 4744 function buildDateSpanApi(span, dateEnv) { 4745 return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); 4746 } 4747 function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { 4748 return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); 4749 } 4750 function buildRangeApi(range, dateEnv, omitTime) { 4751 return { 4752 start: dateEnv.toDate(range.start), 4753 end: dateEnv.toDate(range.end), 4754 startStr: dateEnv.formatIso(range.start, { omitTime }), 4755 endStr: dateEnv.formatIso(range.end, { omitTime }), 4756 }; 4757 } 4758 function fabricateEventRange(dateSpan, eventUiBases, context) { 4759 let res = refineEventDef({ editable: false }, context); 4760 let def = parseEventDef(res.refined, res.extra, '', // sourceId 4761 dateSpan.allDay, true, // hasEnd 4762 context); 4763 return { 4764 def, 4765 ui: compileEventUi(def, eventUiBases), 4766 instance: createEventInstance(def.defId, dateSpan.range), 4767 range: dateSpan.range, 4768 isStart: true, 4769 isEnd: true, 4770 }; 4771 } 4772 4773 /* 4774 given a function that resolves a result asynchronously. 4775 the function can either call passed-in success and failure callbacks, 4776 or it can return a promise. 4777 if you need to pass additional params to func, bind them first. 4778 */ 4779 function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) { 4780 // guard against success/failure callbacks being called more than once 4781 // and guard against a promise AND callback being used together. 4782 let isResolved = false; 4783 let wrappedSuccess = function (res) { 4784 if (!isResolved) { 4785 isResolved = true; 4786 normalizedSuccessCallback(res); 4787 } 4788 }; 4789 let wrappedFailure = function (error) { 4790 if (!isResolved) { 4791 isResolved = true; 4792 normalizedFailureCallback(error); 4793 } 4794 }; 4795 let res = func(wrappedSuccess, wrappedFailure); 4796 if (res && typeof res.then === 'function') { 4797 res.then(wrappedSuccess, wrappedFailure); 4798 } 4799 } 4800 4801 let eventSourceDef$1 = { 4802 parseMeta(refined) { 4803 if (typeof refined.events === 'function') { 4804 return refined.events; 4805 } 4806 return null; 4807 }, 4808 fetch(arg, successCallback, errorCallback) { 4809 const { dateEnv } = arg.context; 4810 const func = arg.eventSource.meta; 4811 unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback); 4812 }, 4813 }; 4814 const funcEventSourcePlugin = createPlugin({ 4815 name: 'func-event-source', 4816 eventSourceDefs: [eventSourceDef$1], 4817 }); 4818
vendor: 2,688 bytes, lines 4819-4894
4819 class JsonRequestError extends Error { 4820 constructor(message, response) { 4821 super(message); 4822 this.response = response; 4823 } 4824 } 4825 function requestJson(method, url, params) { 4826 method = method.toUpperCase(); 4827 const fetchOptions = { 4828 method, 4829 }; 4830 if (method === 'GET') { 4831 url += (url.indexOf('?') === -1 ? '?' : '&') + 4832 new URLSearchParams(params); 4833 } 4834 else { 4835 fetchOptions.body = new URLSearchParams(params); 4836 fetchOptions.headers = { 4837 'Content-Type': 'application/x-www-form-urlencoded', 4838 }; 4839 } 4840 return fetch(url, fetchOptions).then((fetchRes) => { 4841 if (fetchRes.ok) { 4842 return fetchRes.json().then((parsedResponse) => { 4843 return [parsedResponse, fetchRes]; 4844 }, () => { 4845 throw new JsonRequestError('Failure parsing JSON', fetchRes); 4846 }); 4847 } 4848 else { 4849 throw new JsonRequestError('Request failed', fetchRes); 4850 } 4851 }); 4852 } 4853 4854 const JSON_FEED_EVENT_SOURCE_REFINERS = { 4855 method: String, 4856 extraParams: identity, 4857 startParam: String, 4858 endParam: String, 4859 timeZoneParam: String, 4860 }; 4861 4862 let eventSourceDef = { 4863 parseMeta(refined) { 4864 if (refined.url && (refined.format === 'json' || !refined.format)) { 4865 return { 4866 url: refined.url, 4867 format: 'json', 4868 method: (refined.method || 'GET').toUpperCase(), 4869 extraParams: refined.extraParams, 4870 startParam: refined.startParam, 4871 endParam: refined.endParam, 4872 timeZoneParam: refined.timeZoneParam, 4873 }; 4874 } 4875 return null; 4876 }, 4877 fetch(arg, successCallback, errorCallback) { 4878 const { meta } = arg.eventSource; 4879 const requestParams = buildRequestParams(meta, arg.range, arg.context); 4880 requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => { 4881 successCallback({ rawEvents, response }); 4882 }, errorCallback); 4883 }, 4884 }; 4885 const jsonFeedEventSourcePlugin = createPlugin({ 4886 name: 'json-event-source', 4887 eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, 4888 eventSourceDefs: [eventSourceDef], 4889 }); 4890 function buildRequestParams(meta, range, context) { 4891 let { dateEnv, options } = context; 4892 let startParam; 4893 let endParam; 4894 let timeZoneParam;
vendor: 12,932 bytes, lines 4895-5241
4895 let customRequestParams; 4896 let params = {}; 4897 startParam = meta.startParam; 4898 if (startParam == null) { 4899 startParam = options.startParam; 4900 } 4901 endParam = meta.endParam; 4902 if (endParam == null) { 4903 endParam = options.endParam; 4904 } 4905 timeZoneParam = meta.timeZoneParam; 4906 if (timeZoneParam == null) { 4907 timeZoneParam = options.timeZoneParam; 4908 } 4909 // retrieve any outbound GET/POST data from the options 4910 if (typeof meta.extraParams === 'function') { 4911 // supplied as a function that returns a key/value object 4912 customRequestParams = meta.extraParams(); 4913 } 4914 else { 4915 // probably supplied as a straight key/value object 4916 customRequestParams = meta.extraParams || {}; 4917 } 4918 Object.assign(params, customRequestParams); 4919 params[startParam] = dateEnv.formatIso(range.start); 4920 params[endParam] = dateEnv.formatIso(range.end); 4921 if (dateEnv.timeZone !== 'local') { 4922 params[timeZoneParam] = dateEnv.timeZone; 4923 } 4924 return params; 4925 } 4926 4927 const SIMPLE_RECURRING_REFINERS = { 4928 daysOfWeek: identity, 4929 startTime: createDuration, 4930 endTime: createDuration, 4931 duration: createDuration, 4932 startRecur: identity, 4933 endRecur: identity, 4934 }; 4935 4936 let recurring = { 4937 parse(refined, dateEnv) { 4938 if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { 4939 let recurringData = { 4940 daysOfWeek: refined.daysOfWeek || null, 4941 startTime: refined.startTime || null, 4942 endTime: refined.endTime || null, 4943 startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, 4944 endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, 4945 }; 4946 let duration; 4947 if (refined.duration) { 4948 duration = refined.duration; 4949 } 4950 if (!duration && refined.startTime && refined.endTime) { 4951 duration = subtractDurations(refined.endTime, refined.startTime); 4952 } 4953 return { 4954 allDayGuess: Boolean(!refined.startTime && !refined.endTime), 4955 duration, 4956 typeData: recurringData, // doesn't need endTime anymore but oh well 4957 }; 4958 } 4959 return null; 4960 }, 4961 expand(typeData, framingRange, dateEnv) { 4962 let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); 4963 if (clippedFramingRange) { 4964 return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); 4965 } 4966 return []; 4967 }, 4968 }; 4969 const simpleRecurringEventsPlugin = createPlugin({ 4970 name: 'simple-recurring-event', 4971 recurringTypes: [recurring], 4972 eventRefiners: SIMPLE_RECURRING_REFINERS, 4973 }); 4974 function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { 4975 let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; 4976 let dayMarker = startOfDay(framingRange.start); 4977 let endMarker = framingRange.end; 4978 let instanceStarts = []; 4979 while (dayMarker < endMarker) { 4980 let instanceStart; 4981 // if everyday, or this particular day-of-week 4982 if (!dowHash || dowHash[dayMarker.getUTCDay()]) { 4983 if (startTime) { 4984 instanceStart = dateEnv.add(dayMarker, startTime); 4985 } 4986 else { 4987 instanceStart = dayMarker; 4988 } 4989 instanceStarts.push(instanceStart); 4990 } 4991 dayMarker = addDays(dayMarker, 1); 4992 } 4993 return instanceStarts; 4994 } 4995 4996 const changeHandlerPlugin = createPlugin({ 4997 name: 'change-handler', 4998 optionChangeHandlers: { 4999 events(events, context) { 5000 handleEventSources([events], context); 5001 }, 5002 eventSources: handleEventSources, 5003 }, 5004 }); 5005 /* 5006 BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out 5007 */ 5008 function handleEventSources(inputs, context) { 5009 let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); 5010 let newInputs = []; 5011 for (let input of inputs) { 5012 let inputFound = false; 5013 for (let i = 0; i < unfoundSources.length; i += 1) { 5014 if (unfoundSources[i]._raw === input) { 5015 unfoundSources.splice(i, 1); // delete 5016 inputFound = true; 5017 break; 5018 } 5019 } 5020 if (!inputFound) { 5021 newInputs.push(input); 5022 } 5023 } 5024 for (let unfoundSource of unfoundSources) { 5025 context.dispatch({ 5026 type: 'REMOVE_EVENT_SOURCE', 5027 sourceId: unfoundSource.sourceId, 5028 }); 5029 } 5030 for (let newInput of newInputs) { 5031 context.calendarApi.addEventSource(newInput); 5032 } 5033 } 5034 5035 function handleDateProfile(dateProfile, context) { 5036 context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); 5037 } 5038 5039 function handleEventStore(eventStore, context) { 5040 let { emitter } = context; 5041 if (emitter.hasHandlers('eventsSet')) { 5042 emitter.trigger('eventsSet', buildEventApis(eventStore, context)); 5043 } 5044 } 5045 5046 /* 5047 this array is exposed on the root namespace so that UMD plugins can add to it. 5048 see the rollup-bundles script. 5049 */ 5050 const globalPlugins = [ 5051 arrayEventSourcePlugin, 5052 funcEventSourcePlugin, 5053 jsonFeedEventSourcePlugin, 5054 simpleRecurringEventsPlugin, 5055 changeHandlerPlugin, 5056 createPlugin({ 5057 name: 'misc', 5058 isLoadingFuncs: [ 5059 (state) => computeEventSourcesLoading(state.eventSources), 5060 ], 5061 propSetHandlers: { 5062 dateProfile: handleDateProfile, 5063 eventStore: handleEventStore, 5064 }, 5065 }), 5066 ]; 5067 5068 let canVGrowWithinCell; 5069 function getCanVGrowWithinCell() { 5070 if (canVGrowWithinCell == null) { 5071 canVGrowWithinCell = computeCanVGrowWithinCell(); 5072 } 5073 return canVGrowWithinCell; 5074 } 5075 function computeCanVGrowWithinCell() { 5076 // for SSR, because this function is call immediately at top-level 5077 // TODO: just make this logic execute top-level, immediately, instead of doing lazily 5078 if (typeof document === 'undefined') { 5079 return true; 5080 } 5081 let el = document.createElement('div'); 5082 el.style.position = 'absolute'; 5083 el.style.top = '0px'; 5084 el.style.left = '0px'; 5085 el.innerHTML = '<table><tr><td><div></div></td></tr></table>'; 5086 el.querySelector('table').style.height = '100px'; 5087 el.querySelector('div').style.height = '100%'; 5088 document.body.appendChild(el); 5089 let div = el.querySelector('div'); 5090 let possible = div.offsetHeight > 0; 5091 document.body.removeChild(el); 5092 return possible; 5093 } 5094 5095 class CalendarRoot extends BaseComponent { 5096 constructor() { 5097 super(...arguments); 5098 this.state = { 5099 forPrint: false, 5100 }; 5101 this.handleBeforePrint = () => { 5102 this.setState({ forPrint: true }); 5103 }; 5104 this.handleAfterPrint = () => { 5105 this.setState({ forPrint: false }); 5106 }; 5107 } 5108 render() { 5109 let { props } = this; 5110 let { options } = props; 5111 let { forPrint } = this.state; 5112 let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; 5113 let height = (!isHeightAuto && options.height != null) ? options.height : ''; 5114 let classNames = [ 5115 'fc', 5116 forPrint ? 'fc-media-print' : 'fc-media-screen', 5117 `fc-direction-${options.direction}`, 5118 props.theme.getClass('root'), 5119 ]; 5120 if (!getCanVGrowWithinCell()) { 5121 classNames.push('fc-liquid-hack'); 5122 } 5123 return props.children(classNames, height, isHeightAuto, forPrint); 5124 } 5125 componentDidMount() { 5126 let { emitter } = this.props; 5127 emitter.on('_beforeprint', this.handleBeforePrint); 5128 emitter.on('_afterprint', this.handleAfterPrint); 5129 } 5130 componentWillUnmount() { 5131 let { emitter } = this.props; 5132 emitter.off('_beforeprint', this.handleBeforePrint); 5133 emitter.off('_afterprint', this.handleAfterPrint); 5134 } 5135 } 5136 5137 class Interaction { 5138 constructor(settings) { 5139 this.component = settings.component; 5140 this.isHitComboAllowed = settings.isHitComboAllowed || null; 5141 } 5142 destroy() { 5143 } 5144 } 5145 function parseInteractionSettings(component, input) { 5146 return { 5147 component, 5148 el: input.el, 5149 useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, 5150 isHitComboAllowed: input.isHitComboAllowed || null, 5151 }; 5152 } 5153 function interactionSettingsToStore(settings) { 5154 return { 5155 [settings.component.uid]: settings, 5156 }; 5157 } 5158 // global state 5159 const interactionSettingsStore = {}; 5160 5161 class CalendarImpl { 5162 getCurrentData() { 5163 return this.currentDataManager.getCurrentData(); 5164 } 5165 dispatch(action) { 5166 this.currentDataManager.dispatch(action); 5167 } 5168 get view() { return this.getCurrentData().viewApi; } 5169 batchRendering(callback) { 5170 callback(); 5171 } 5172 updateSize() { 5173 this.trigger('_resize', true); 5174 } 5175 // Options 5176 // ----------------------------------------------------------------------------------------------------------------- 5177 setOption(name, val) { 5178 this.dispatch({ 5179 type: 'SET_OPTION', 5180 optionName: name, 5181 rawOptionValue: val, 5182 }); 5183 } 5184 getOption(name) { 5185 return this.currentDataManager.currentCalendarOptionsInput[name]; 5186 } 5187 getAvailableLocaleCodes() { 5188 return Object.keys(this.getCurrentData().availableRawLocales); 5189 } 5190 // Trigger 5191 // ----------------------------------------------------------------------------------------------------------------- 5192 on(handlerName, handler) { 5193 let { currentDataManager } = this; 5194 if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { 5195 currentDataManager.emitter.on(handlerName, handler); 5196 } 5197 else { 5198 console.warn(`Unknown listener name '${handlerName}'`); 5199 } 5200 } 5201 off(handlerName, handler) { 5202 this.currentDataManager.emitter.off(handlerName, handler); 5203 } 5204 // not meant for public use 5205 trigger(handlerName, ...args) { 5206 this.currentDataManager.emitter.trigger(handlerName, ...args); 5207 } 5208 // View 5209 // ----------------------------------------------------------------------------------------------------------------- 5210 changeView(viewType, dateOrRange) { 5211 this.batchRendering(() => { 5212 this.unselect(); 5213 if (dateOrRange) { 5214 if (dateOrRange.start && dateOrRange.end) { // a range 5215 this.dispatch({ 5216 type: 'CHANGE_VIEW_TYPE', 5217 viewType, 5218 }); 5219 this.dispatch({ 5220 type: 'SET_OPTION', 5221 optionName: 'visibleRange', 5222 rawOptionValue: dateOrRange, 5223 }); 5224 } 5225 else { 5226 let { dateEnv } = this.getCurrentData(); 5227 this.dispatch({ 5228 type: 'CHANGE_VIEW_TYPE', 5229 viewType, 5230 dateMarker: dateEnv.createMarker(dateOrRange), 5231 }); 5232 } 5233 } 5234 else { 5235 this.dispatch({ 5236 type: 'CHANGE_VIEW_TYPE', 5237 viewType, 5238 }); 5239 } 5240 }); 5241 }
vendor: 21,600 bytes, lines 5242-5773
5242 // Forces navigation to a view for the given date. 5243 // `viewType` can be a specific view name or a generic one like "week" or "day". 5244 // needs to change 5245 zoomTo(dateMarker, viewType) { 5246 let state = this.getCurrentData(); 5247 let spec; 5248 viewType = viewType || 'day'; // day is default zoom 5249 spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); 5250 this.unselect(); 5251 if (spec) { 5252 this.dispatch({ 5253 type: 'CHANGE_VIEW_TYPE', 5254 viewType: spec.type, 5255 dateMarker, 5256 }); 5257 } 5258 else { 5259 this.dispatch({ 5260 type: 'CHANGE_DATE', 5261 dateMarker, 5262 }); 5263 } 5264 } 5265 // Given a duration singular unit, like "week" or "day", finds a matching view spec. 5266 // Preference is given to views that have corresponding buttons. 5267 getUnitViewSpec(unit) { 5268 let { viewSpecs, toolbarConfig } = this.getCurrentData(); 5269 let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []); 5270 let i; 5271 let spec; 5272 for (let viewType in viewSpecs) { 5273 viewTypes.push(viewType); 5274 } 5275 for (i = 0; i < viewTypes.length; i += 1) { 5276 spec = viewSpecs[viewTypes[i]]; 5277 if (spec) { 5278 if (spec.singleUnit === unit) { 5279 return spec; 5280 } 5281 } 5282 } 5283 return null; 5284 } 5285 // Current Date 5286 // ----------------------------------------------------------------------------------------------------------------- 5287 prev() { 5288 this.unselect(); 5289 this.dispatch({ type: 'PREV' }); 5290 } 5291 next() { 5292 this.unselect(); 5293 this.dispatch({ type: 'NEXT' }); 5294 } 5295 prevYear() { 5296 let state = this.getCurrentData(); 5297 this.unselect(); 5298 this.dispatch({ 5299 type: 'CHANGE_DATE', 5300 dateMarker: state.dateEnv.addYears(state.currentDate, -1), 5301 }); 5302 } 5303 nextYear() { 5304 let state = this.getCurrentData(); 5305 this.unselect(); 5306 this.dispatch({ 5307 type: 'CHANGE_DATE', 5308 dateMarker: state.dateEnv.addYears(state.currentDate, 1), 5309 }); 5310 } 5311 today() { 5312 let state = this.getCurrentData(); 5313 this.unselect(); 5314 this.dispatch({ 5315 type: 'CHANGE_DATE', 5316 dateMarker: getNow(state.calendarOptions.now, state.dateEnv), 5317 }); 5318 } 5319 gotoDate(zonedDateInput) { 5320 let state = this.getCurrentData(); 5321 this.unselect(); 5322 this.dispatch({ 5323 type: 'CHANGE_DATE', 5324 dateMarker: state.dateEnv.createMarker(zonedDateInput), 5325 }); 5326 } 5327 incrementDate(deltaInput) { 5328 let state = this.getCurrentData(); 5329 let delta = createDuration(deltaInput); 5330 if (delta) { // else, warn about invalid input? 5331 this.unselect(); 5332 this.dispatch({ 5333 type: 'CHANGE_DATE', 5334 dateMarker: state.dateEnv.add(state.currentDate, delta), 5335 }); 5336 } 5337 } 5338 getDate() { 5339 let state = this.getCurrentData(); 5340 return state.dateEnv.toDate(state.currentDate); 5341 } 5342 // Date Formatting Utils 5343 // ----------------------------------------------------------------------------------------------------------------- 5344 formatDate(d, formatter) { 5345 let { dateEnv } = this.getCurrentData(); 5346 return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); 5347 } 5348 // `settings` is for formatter AND isEndExclusive 5349 formatRange(d0, d1, settings) { 5350 let { dateEnv } = this.getCurrentData(); 5351 return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); 5352 } 5353 formatIso(d, omitTime) { 5354 let { dateEnv } = this.getCurrentData(); 5355 return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime }); 5356 } 5357 // Date Selection / Event Selection / DayClick 5358 // ----------------------------------------------------------------------------------------------------------------- 5359 select(dateOrObj, endDate) { 5360 let selectionInput; 5361 if (endDate == null) { 5362 if (dateOrObj.start != null) { 5363 selectionInput = dateOrObj; 5364 } 5365 else { 5366 selectionInput = { 5367 start: dateOrObj, 5368 end: null, 5369 }; 5370 } 5371 } 5372 else { 5373 selectionInput = { 5374 start: dateOrObj, 5375 end: endDate, 5376 }; 5377 } 5378 let state = this.getCurrentData(); 5379 let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); 5380 if (selection) { // throw parse error otherwise? 5381 this.dispatch({ type: 'SELECT_DATES', selection }); 5382 triggerDateSelect(selection, null, state); 5383 } 5384 } 5385 unselect(pev) { 5386 let state = this.getCurrentData(); 5387 if (state.dateSelection) { 5388 this.dispatch({ type: 'UNSELECT_DATES' }); 5389 triggerDateUnselect(pev, state); 5390 } 5391 } 5392 // Public Events API 5393 // ----------------------------------------------------------------------------------------------------------------- 5394 addEvent(eventInput, sourceInput) { 5395 if (eventInput instanceof EventImpl) { 5396 let def = eventInput._def; 5397 let instance = eventInput._instance; 5398 let currentData = this.getCurrentData(); 5399 // not already present? don't want to add an old snapshot 5400 if (!currentData.eventStore.defs[def.defId]) { 5401 this.dispatch({ 5402 type: 'ADD_EVENTS', 5403 eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args? 5404 }); 5405 this.triggerEventAdd(eventInput); 5406 } 5407 return eventInput; 5408 } 5409 let state = this.getCurrentData(); 5410 let eventSource; 5411 if (sourceInput instanceof EventSourceImpl) { 5412 eventSource = sourceInput.internalEventSource; 5413 } 5414 else if (typeof sourceInput === 'boolean') { 5415 if (sourceInput) { // true. part of the first event source 5416 [eventSource] = hashValuesToArray(state.eventSources); 5417 } 5418 } 5419 else if (sourceInput != null) { // an ID. accepts a number too 5420 let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function 5421 if (!sourceApi) { 5422 console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test 5423 return null; 5424 } 5425 eventSource = sourceApi.internalEventSource; 5426 } 5427 let tuple = parseEvent(eventInput, eventSource, state, false); 5428 if (tuple) { 5429 let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); 5430 this.dispatch({ 5431 type: 'ADD_EVENTS', 5432 eventStore: eventTupleToStore(tuple), 5433 }); 5434 this.triggerEventAdd(newEventApi); 5435 return newEventApi; 5436 } 5437 return null; 5438 } 5439 triggerEventAdd(eventApi) { 5440 let { emitter } = this.getCurrentData(); 5441 emitter.trigger('eventAdd', { 5442 event: eventApi, 5443 relatedEvents: [], 5444 revert: () => { 5445 this.dispatch({ 5446 type: 'REMOVE_EVENTS', 5447 eventStore: eventApiToStore(eventApi), 5448 }); 5449 }, 5450 }); 5451 } 5452 // TODO: optimize 5453 getEventById(id) { 5454 let state = this.getCurrentData(); 5455 let { defs, instances } = state.eventStore; 5456 id = String(id); 5457 for (let defId in defs) { 5458 let def = defs[defId]; 5459 if (def.publicId === id) { 5460 if (def.recurringDef) { 5461 return new EventImpl(state, def, null); 5462 } 5463 for (let instanceId in instances) { 5464 let instance = instances[instanceId]; 5465 if (instance.defId === def.defId) { 5466 return new EventImpl(state, def, instance); 5467 } 5468 } 5469 } 5470 } 5471 return null; 5472 } 5473 getEvents() { 5474 let currentData = this.getCurrentData(); 5475 return buildEventApis(currentData.eventStore, currentData); 5476 } 5477 removeAllEvents() { 5478 this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); 5479 } 5480 // Public Event Sources API 5481 // ----------------------------------------------------------------------------------------------------------------- 5482 getEventSources() { 5483 let state = this.getCurrentData(); 5484 let sourceHash = state.eventSources; 5485 let sourceApis = []; 5486 for (let internalId in sourceHash) { 5487 sourceApis.push(new EventSourceImpl(state, sourceHash[internalId])); 5488 } 5489 return sourceApis; 5490 } 5491 getEventSourceById(id) { 5492 let state = this.getCurrentData(); 5493 let sourceHash = state.eventSources; 5494 id = String(id); 5495 for (let sourceId in sourceHash) { 5496 if (sourceHash[sourceId].publicId === id) { 5497 return new EventSourceImpl(state, sourceHash[sourceId]); 5498 } 5499 } 5500 return null; 5501 } 5502 addEventSource(sourceInput) { 5503 let state = this.getCurrentData(); 5504 if (sourceInput instanceof EventSourceImpl) { 5505 // not already present? don't want to add an old snapshot 5506 if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { 5507 this.dispatch({ 5508 type: 'ADD_EVENT_SOURCES', 5509 sources: [sourceInput.internalEventSource], 5510 }); 5511 } 5512 return sourceInput; 5513 } 5514 let eventSource = parseEventSource(sourceInput, state); 5515 if (eventSource) { // TODO: error otherwise? 5516 this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); 5517 return new EventSourceImpl(state, eventSource); 5518 } 5519 return null; 5520 } 5521 removeAllEventSources() { 5522 this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); 5523 } 5524 refetchEvents() { 5525 this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); 5526 } 5527 // Scroll 5528 // ----------------------------------------------------------------------------------------------------------------- 5529 scrollToTime(timeInput) { 5530 let time = createDuration(timeInput); 5531 if (time) { 5532 this.trigger('_scrollRequest', { time }); 5533 } 5534 } 5535 } 5536 5537 function pointInsideRect(point, rect) { 5538 return point.left >= rect.left && 5539 point.left < rect.right && 5540 point.top >= rect.top && 5541 point.top < rect.bottom; 5542 } 5543 // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false 5544 function intersectRects(rect1, rect2) { 5545 let res = { 5546 left: Math.max(rect1.left, rect2.left), 5547 right: Math.min(rect1.right, rect2.right), 5548 top: Math.max(rect1.top, rect2.top), 5549 bottom: Math.min(rect1.bottom, rect2.bottom), 5550 }; 5551 if (res.left < res.right && res.top < res.bottom) { 5552 return res; 5553 } 5554 return false; 5555 } 5556 function translateRect(rect, deltaX, deltaY) { 5557 return { 5558 left: rect.left + deltaX, 5559 right: rect.right + deltaX, 5560 top: rect.top + deltaY, 5561 bottom: rect.bottom + deltaY, 5562 }; 5563 } 5564 // Returns a new point that will have been moved to reside within the given rectangle 5565 function constrainPoint(point, rect) { 5566 return { 5567 left: Math.min(Math.max(point.left, rect.left), rect.right), 5568 top: Math.min(Math.max(point.top, rect.top), rect.bottom), 5569 }; 5570 } 5571 // Returns a point that is the center of the given rectangle 5572 function getRectCenter(rect) { 5573 return { 5574 left: (rect.left + rect.right) / 2, 5575 top: (rect.top + rect.bottom) / 2, 5576 }; 5577 } 5578 // Subtracts point2's coordinates from point1's coordinates, returning a delta 5579 function diffPoints(point1, point2) { 5580 return { 5581 left: point1.left - point2.left, 5582 top: point1.top - point2.top, 5583 }; 5584 } 5585 5586 const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere 5587 class Splitter { 5588 constructor() { 5589 this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); 5590 this.splitDateSelection = memoize(this._splitDateSpan); 5591 this.splitEventStore = memoize(this._splitEventStore); 5592 this.splitIndividualUi = memoize(this._splitIndividualUi); 5593 this.splitEventDrag = memoize(this._splitInteraction); 5594 this.splitEventResize = memoize(this._splitInteraction); 5595 this.eventUiBuilders = {}; // TODO: typescript protection 5596 } 5597 splitProps(props) { 5598 let keyInfos = this.getKeyInfo(props); 5599 let defKeys = this.getKeysForEventDefs(props.eventStore); 5600 let dateSelections = this.splitDateSelection(props.dateSelection); 5601 let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* 5602 let eventStores = this.splitEventStore(props.eventStore, defKeys); 5603 let eventDrags = this.splitEventDrag(props.eventDrag); 5604 let eventResizes = this.splitEventResize(props.eventResize); 5605 let splitProps = {}; 5606 this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey)); 5607 for (let key in keyInfos) { 5608 let keyInfo = keyInfos[key]; 5609 let eventStore = eventStores[key] || EMPTY_EVENT_STORE; 5610 let buildEventUi = this.eventUiBuilders[key]; 5611 splitProps[key] = { 5612 businessHours: keyInfo.businessHours || props.businessHours, 5613 dateSelection: dateSelections[key] || null, 5614 eventStore, 5615 eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), 5616 eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', 5617 eventDrag: eventDrags[key] || null, 5618 eventResize: eventResizes[key] || null, 5619 }; 5620 } 5621 return splitProps; 5622 } 5623 _splitDateSpan(dateSpan) { 5624 let dateSpans = {}; 5625 if (dateSpan) { 5626 let keys = this.getKeysForDateSpan(dateSpan); 5627 for (let key of keys) { 5628 dateSpans[key] = dateSpan; 5629 } 5630 } 5631 return dateSpans; 5632 } 5633 _getKeysForEventDefs(eventStore) { 5634 return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef)); 5635 } 5636 _splitEventStore(eventStore, defKeys) { 5637 let { defs, instances } = eventStore; 5638 let splitStores = {}; 5639 for (let defId in defs) { 5640 for (let key of defKeys[defId]) { 5641 if (!splitStores[key]) { 5642 splitStores[key] = createEmptyEventStore(); 5643 } 5644 splitStores[key].defs[defId] = defs[defId]; 5645 } 5646 } 5647 for (let instanceId in instances) { 5648 let instance = instances[instanceId]; 5649 for (let key of defKeys[instance.defId]) { 5650 if (splitStores[key]) { // must have already been created 5651 splitStores[key].instances[instanceId] = instance; 5652 } 5653 } 5654 } 5655 return splitStores; 5656 } 5657 _splitIndividualUi(eventUiBases, defKeys) { 5658 let splitHashes = {}; 5659 for (let defId in eventUiBases) { 5660 if (defId) { // not the '' key 5661 for (let key of defKeys[defId]) { 5662 if (!splitHashes[key]) { 5663 splitHashes[key] = {}; 5664 } 5665 splitHashes[key][defId] = eventUiBases[defId]; 5666 } 5667 } 5668 } 5669 return splitHashes; 5670 } 5671 _splitInteraction(interaction) { 5672 let splitStates = {}; 5673 if (interaction) { 5674 let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); 5675 // can't rely on defKeys because event data is mutated 5676 let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); 5677 let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); 5678 let populate = (key) => { 5679 if (!splitStates[key]) { 5680 splitStates[key] = { 5681 affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE, 5682 mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE, 5683 isEvent: interaction.isEvent, 5684 }; 5685 } 5686 }; 5687 for (let key in affectedStores) { 5688 populate(key); 5689 } 5690 for (let key in mutatedStores) { 5691 populate(key); 5692 } 5693 } 5694 return splitStates; 5695 } 5696 } 5697 function buildEventUiForKey(allUi, eventUiForKey, individualUi) { 5698 let baseParts = []; 5699 if (allUi) { 5700 baseParts.push(allUi); 5701 } 5702 if (eventUiForKey) { 5703 baseParts.push(eventUiForKey); 5704 } 5705 let stuff = { 5706 '': combineEventUis(baseParts), 5707 }; 5708 if (individualUi) { 5709 Object.assign(stuff, individualUi); 5710 } 5711 return stuff; 5712 } 5713 5714 function getDateMeta(date, todayRange, nowDate, dateProfile) { 5715 return { 5716 dow: date.getUTCDay(), 5717 isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), 5718 isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), 5719 isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), 5720 isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), 5721 isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), 5722 }; 5723 } 5724 function getDayClassNames(meta, theme) { 5725 let classNames = [ 5726 'fc-day', 5727 `fc-day-${DAY_IDS[meta.dow]}`, 5728 ]; 5729 if (meta.isDisabled) { 5730 classNames.push('fc-day-disabled'); 5731 } 5732 else { 5733 if (meta.isToday) { 5734 classNames.push('fc-day-today'); 5735 classNames.push(theme.getClass('today')); 5736 } 5737 if (meta.isPast) { 5738 classNames.push('fc-day-past'); 5739 } 5740 if (meta.isFuture) { 5741 classNames.push('fc-day-future'); 5742 } 5743 if (meta.isOther) { 5744 classNames.push('fc-day-other'); 5745 } 5746 } 5747 return classNames; 5748 } 5749 function getSlotClassNames(meta, theme) { 5750 let classNames = [ 5751 'fc-slot', 5752 `fc-slot-${DAY_IDS[meta.dow]}`, 5753 ]; 5754 if (meta.isDisabled) { 5755 classNames.push('fc-slot-disabled'); 5756 } 5757 else { 5758 if (meta.isToday) { 5759 classNames.push('fc-slot-today'); 5760 classNames.push(theme.getClass('today')); 5761 } 5762 if (meta.isPast) { 5763 classNames.push('fc-slot-past'); 5764 } 5765 if (meta.isFuture) { 5766 classNames.push('fc-slot-future'); 5767 } 5768 } 5769 return classNames; 5770 } 5771 5772 const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' }); 5773 const WEEK_FORMAT = createFormatter({ week: 'long' });
vendor: 7,271 bytes, lines 5774-5954
5774 function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) { 5775 const { dateEnv, options, calendarApi } = context; 5776 let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT); 5777 if (options.navLinks) { 5778 let zonedDate = dateEnv.toDate(dateMarker); 5779 const handleInteraction = (ev) => { 5780 let customAction = viewType === 'day' ? options.navLinkDayClick : 5781 viewType === 'week' ? options.navLinkWeekClick : null; 5782 if (typeof customAction === 'function') { 5783 customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); 5784 } 5785 else { 5786 if (typeof customAction === 'string') { 5787 viewType = customAction; 5788 } 5789 calendarApi.zoomTo(dateMarker, viewType); 5790 } 5791 }; 5792 return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable 5793 ? createAriaClickAttrs(handleInteraction) 5794 : { onClick: handleInteraction })); 5795 } 5796 return { 'aria-label': dateStr }; 5797 } 5798 5799 let _isRtlScrollbarOnLeft = null; 5800 function getIsRtlScrollbarOnLeft() { 5801 if (_isRtlScrollbarOnLeft === null) { 5802 _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); 5803 } 5804 return _isRtlScrollbarOnLeft; 5805 } 5806 function computeIsRtlScrollbarOnLeft() { 5807 let outerEl = document.createElement('div'); 5808 applyStyle(outerEl, { 5809 position: 'absolute', 5810 top: -1000, 5811 left: 0, 5812 border: 0, 5813 padding: 0, 5814 overflow: 'scroll', 5815 direction: 'rtl', 5816 }); 5817 outerEl.innerHTML = '<div></div>'; 5818 document.body.appendChild(outerEl); 5819 let innerEl = outerEl.firstChild; 5820 let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; 5821 removeElement(outerEl); 5822 return res; 5823 } 5824 5825 let _scrollbarWidths; 5826 function getScrollbarWidths() { 5827 if (!_scrollbarWidths) { 5828 _scrollbarWidths = computeScrollbarWidths(); 5829 } 5830 return _scrollbarWidths; 5831 } 5832 function computeScrollbarWidths() { 5833 let el = document.createElement('div'); 5834 el.style.overflow = 'scroll'; 5835 el.style.position = 'absolute'; 5836 el.style.top = '-9999px'; 5837 el.style.left = '-9999px'; 5838 document.body.appendChild(el); 5839 let res = computeScrollbarWidthsForEl(el); 5840 document.body.removeChild(el); 5841 return res; 5842 } 5843 // WARNING: will include border 5844 function computeScrollbarWidthsForEl(el) { 5845 return { 5846 x: el.offsetHeight - el.clientHeight, 5847 y: el.offsetWidth - el.clientWidth, 5848 }; 5849 } 5850 5851 function computeEdges(el, getPadding = false) { 5852 let computedStyle = window.getComputedStyle(el); 5853 let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; 5854 let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; 5855 let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; 5856 let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; 5857 let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! 5858 let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; 5859 let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; 5860 let res = { 5861 borderLeft, 5862 borderRight, 5863 borderTop, 5864 borderBottom, 5865 scrollbarBottom, 5866 scrollbarLeft: 0, 5867 scrollbarRight: 0, 5868 }; 5869 if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? 5870 res.scrollbarLeft = scrollbarLeftRight; 5871 } 5872 else { 5873 res.scrollbarRight = scrollbarLeftRight; 5874 } 5875 if (getPadding) { 5876 res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; 5877 res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; 5878 res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; 5879 res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; 5880 } 5881 return res; 5882 } 5883 function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) { 5884 let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); 5885 let edges = computeEdges(el, goWithinPadding); 5886 let res = { 5887 left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, 5888 right: outerRect.right - edges.borderRight - edges.scrollbarRight, 5889 top: outerRect.top + edges.borderTop, 5890 bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, 5891 }; 5892 if (goWithinPadding) { 5893 res.left += edges.paddingLeft; 5894 res.right -= edges.paddingRight; 5895 res.top += edges.paddingTop; 5896 res.bottom -= edges.paddingBottom; 5897 } 5898 return res; 5899 } 5900 function computeRect(el) { 5901 let rect = el.getBoundingClientRect(); 5902 return { 5903 left: rect.left + window.pageXOffset, 5904 top: rect.top + window.pageYOffset, 5905 right: rect.right + window.pageXOffset, 5906 bottom: rect.bottom + window.pageYOffset, 5907 }; 5908 } 5909 function computeClippedClientRect(el) { 5910 let clippingParents = getClippingParents(el); 5911 let rect = el.getBoundingClientRect(); 5912 for (let clippingParent of clippingParents) { 5913 let intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); 5914 if (intersection) { 5915 rect = intersection; 5916 } 5917 else { 5918 return null; 5919 } 5920 } 5921 return rect; 5922 } 5923 // does not return window 5924 function getClippingParents(el) { 5925 let parents = []; 5926 while (el instanceof HTMLElement) { // will stop when gets to document or null 5927 let computedStyle = window.getComputedStyle(el); 5928 if (computedStyle.position === 'fixed') { 5929 break; 5930 } 5931 if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { 5932 parents.push(el); 5933 } 5934 el = el.parentNode; 5935 } 5936 return parents; 5937 } 5938 5939 /* 5940 Records offset information for a set of elements, relative to an origin element. 5941 Can record the left/right OR the top/bottom OR both. 5942 Provides methods for querying the cache by position. 5943 */ 5944 class PositionCache { 5945 constructor(originEl, els, isHorizontal, isVertical) { 5946 this.els = els; 5947 let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left 5948 if (isHorizontal) { 5949 this.buildElHorizontals(originClientRect.left); 5950 } 5951 if (isVertical) { 5952 this.buildElVerticals(originClientRect.top); 5953 } 5954 }
vendor: 15,444 bytes, lines 5955-6351
5955 // Populates the left/right internal coordinate arrays 5956 buildElHorizontals(originClientLeft) { 5957 let lefts = []; 5958 let rights = []; 5959 for (let el of this.els) { 5960 let rect = el.getBoundingClientRect(); 5961 lefts.push(rect.left - originClientLeft); 5962 rights.push(rect.right - originClientLeft); 5963 } 5964 this.lefts = lefts; 5965 this.rights = rights; 5966 } 5967 // Populates the top/bottom internal coordinate arrays 5968 buildElVerticals(originClientTop) { 5969 let tops = []; 5970 let bottoms = []; 5971 for (let el of this.els) { 5972 let rect = el.getBoundingClientRect(); 5973 tops.push(rect.top - originClientTop); 5974 bottoms.push(rect.bottom - originClientTop); 5975 } 5976 this.tops = tops; 5977 this.bottoms = bottoms; 5978 } 5979 // Given a left offset (from document left), returns the index of the el that it horizontally intersects. 5980 // If no intersection is made, returns undefined. 5981 leftToIndex(leftPosition) { 5982 let { lefts, rights } = this; 5983 let len = lefts.length; 5984 let i; 5985 for (i = 0; i < len; i += 1) { 5986 if (leftPosition >= lefts[i] && leftPosition < rights[i]) { 5987 return i; 5988 } 5989 } 5990 return undefined; // TODO: better 5991 } 5992 // Given a top offset (from document top), returns the index of the el that it vertically intersects. 5993 // If no intersection is made, returns undefined. 5994 topToIndex(topPosition) { 5995 let { tops, bottoms } = this; 5996 let len = tops.length; 5997 let i; 5998 for (i = 0; i < len; i += 1) { 5999 if (topPosition >= tops[i] && topPosition < bottoms[i]) { 6000 return i; 6001 } 6002 } 6003 return undefined; // TODO: better 6004 } 6005 // Gets the width of the element at the given index 6006 getWidth(leftIndex) { 6007 return this.rights[leftIndex] - this.lefts[leftIndex]; 6008 } 6009 // Gets the height of the element at the given index 6010 getHeight(topIndex) { 6011 return this.bottoms[topIndex] - this.tops[topIndex]; 6012 } 6013 } 6014 6015 /* eslint max-classes-per-file: "off" */ 6016 /* 6017 An object for getting/setting scroll-related information for an element. 6018 Internally, this is done very differently for window versus DOM element, 6019 so this object serves as a common interface. 6020 */ 6021 class ScrollController { 6022 getMaxScrollTop() { 6023 return this.getScrollHeight() - this.getClientHeight(); 6024 } 6025 getMaxScrollLeft() { 6026 return this.getScrollWidth() - this.getClientWidth(); 6027 } 6028 canScrollVertically() { 6029 return this.getMaxScrollTop() > 0; 6030 } 6031 canScrollHorizontally() { 6032 return this.getMaxScrollLeft() > 0; 6033 } 6034 canScrollUp() { 6035 return this.getScrollTop() > 0; 6036 } 6037 canScrollDown() { 6038 return this.getScrollTop() < this.getMaxScrollTop(); 6039 } 6040 canScrollLeft() { 6041 return this.getScrollLeft() > 0; 6042 } 6043 canScrollRight() { 6044 return this.getScrollLeft() < this.getMaxScrollLeft(); 6045 } 6046 } 6047 class ElementScrollController extends ScrollController { 6048 constructor(el) { 6049 super(); 6050 this.el = el; 6051 } 6052 getScrollTop() { 6053 return this.el.scrollTop; 6054 } 6055 getScrollLeft() { 6056 return this.el.scrollLeft; 6057 } 6058 setScrollTop(top) { 6059 this.el.scrollTop = top; 6060 } 6061 setScrollLeft(left) { 6062 this.el.scrollLeft = left; 6063 } 6064 getScrollWidth() { 6065 return this.el.scrollWidth; 6066 } 6067 getScrollHeight() { 6068 return this.el.scrollHeight; 6069 } 6070 getClientHeight() { 6071 return this.el.clientHeight; 6072 } 6073 getClientWidth() { 6074 return this.el.clientWidth; 6075 } 6076 } 6077 class WindowScrollController extends ScrollController { 6078 getScrollTop() { 6079 return window.pageYOffset; 6080 } 6081 getScrollLeft() { 6082 return window.pageXOffset; 6083 } 6084 setScrollTop(n) { 6085 window.scroll(window.pageXOffset, n); 6086 } 6087 setScrollLeft(n) { 6088 window.scroll(n, window.pageYOffset); 6089 } 6090 getScrollWidth() { 6091 return document.documentElement.scrollWidth; 6092 } 6093 getScrollHeight() { 6094 return document.documentElement.scrollHeight; 6095 } 6096 getClientHeight() { 6097 return document.documentElement.clientHeight; 6098 } 6099 getClientWidth() { 6100 return document.documentElement.clientWidth; 6101 } 6102 } 6103 6104 /* 6105 an INTERACTABLE date component 6106 6107 PURPOSES: 6108 - hook up to fg, fill, and mirror renderers 6109 - interface for dragging and hits 6110 */ 6111 class DateComponent extends BaseComponent { 6112 constructor() { 6113 super(...arguments); 6114 this.uid = guid(); 6115 } 6116 // Hit System 6117 // ----------------------------------------------------------------------------------------------------------------- 6118 prepareHits() { 6119 } 6120 queryHit(positionLeft, positionTop, elWidth, elHeight) { 6121 return null; // this should be abstract 6122 } 6123 // Pointer Interaction Utils 6124 // ----------------------------------------------------------------------------------------------------------------- 6125 isValidSegDownEl(el) { 6126 return !this.props.eventDrag && // HACK 6127 !this.props.eventResize && // HACK 6128 !elementClosest(el, '.fc-event-mirror'); 6129 } 6130 isValidDateDownEl(el) { 6131 return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && 6132 !elementClosest(el, '.fc-more-link') && // a "more.." link 6133 !elementClosest(el, 'a[data-navlink]') && // a clickable nav link 6134 !elementClosest(el, '.fc-popover'); // hack 6135 } 6136 } 6137 6138 class NamedTimeZoneImpl { 6139 constructor(timeZoneName) { 6140 this.timeZoneName = timeZoneName; 6141 } 6142 } 6143 6144 class SegHierarchy { 6145 constructor() { 6146 // settings 6147 this.strictOrder = false; 6148 this.allowReslicing = false; 6149 this.maxCoord = -1; // -1 means no max 6150 this.maxStackCnt = -1; // -1 means no max 6151 this.levelCoords = []; // ordered 6152 this.entriesByLevel = []; // parallel with levelCoords 6153 this.stackCnts = {}; // TODO: use better technique!? 6154 } 6155 addSegs(inputs) { 6156 let hiddenEntries = []; 6157 for (let input of inputs) { 6158 this.insertEntry(input, hiddenEntries); 6159 } 6160 return hiddenEntries; 6161 } 6162 insertEntry(entry, hiddenEntries) { 6163 let insertion = this.findInsertion(entry); 6164 if (this.isInsertionValid(insertion, entry)) { 6165 this.insertEntryAt(entry, insertion); 6166 return 1; 6167 } 6168 return this.handleInvalidInsertion(insertion, entry, hiddenEntries); 6169 } 6170 isInsertionValid(insertion, entry) { 6171 return (this.maxCoord === -1 || insertion.levelCoord + entry.thickness <= this.maxCoord) && 6172 (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); 6173 } 6174 // returns number of new entries inserted 6175 handleInvalidInsertion(insertion, entry, hiddenEntries) { 6176 if (this.allowReslicing && insertion.touchingEntry) { 6177 return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); 6178 } 6179 hiddenEntries.push(entry); 6180 return 0; 6181 } 6182 splitEntry(entry, barrier, hiddenEntries) { 6183 let partCnt = 0; 6184 let splitHiddenEntries = []; 6185 let entrySpan = entry.span; 6186 let barrierSpan = barrier.span; 6187 if (entrySpan.start < barrierSpan.start) { 6188 partCnt += this.insertEntry({ 6189 index: entry.index, 6190 thickness: entry.thickness, 6191 span: { start: entrySpan.start, end: barrierSpan.start }, 6192 }, splitHiddenEntries); 6193 } 6194 if (entrySpan.end > barrierSpan.end) { 6195 partCnt += this.insertEntry({ 6196 index: entry.index, 6197 thickness: entry.thickness, 6198 span: { start: barrierSpan.end, end: entrySpan.end }, 6199 }, splitHiddenEntries); 6200 } 6201 if (partCnt) { 6202 hiddenEntries.push({ 6203 index: entry.index, 6204 thickness: entry.thickness, 6205 span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect 6206 }, ...splitHiddenEntries); 6207 return partCnt; 6208 } 6209 hiddenEntries.push(entry); 6210 return 0; 6211 } 6212 insertEntryAt(entry, insertion) { 6213 let { entriesByLevel, levelCoords } = this; 6214 if (insertion.lateral === -1) { 6215 // create a new level 6216 insertAt(levelCoords, insertion.level, insertion.levelCoord); 6217 insertAt(entriesByLevel, insertion.level, [entry]); 6218 } 6219 else { 6220 // insert into existing level 6221 insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); 6222 } 6223 this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; 6224 } 6225 findInsertion(newEntry) { 6226 let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this; 6227 let levelCnt = levelCoords.length; 6228 let candidateCoord = 0; 6229 let touchingLevel = -1; 6230 let touchingLateral = -1; 6231 let touchingEntry = null; 6232 let stackCnt = 0; 6233 for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { 6234 let trackingCoord = levelCoords[trackingLevel]; 6235 // if the current level is past the placed entry, we have found a good empty space and can stop. 6236 // if strictOrder, keep finding more lateral intersections. 6237 if (!strictOrder && trackingCoord >= candidateCoord + newEntry.thickness) { 6238 break; 6239 } 6240 let trackingEntries = entriesByLevel[trackingLevel]; 6241 let trackingEntry; 6242 let searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end 6243 let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one 6244 while ( // loop through entries that horizontally intersect 6245 (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list 6246 trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry 6247 ) { 6248 let trackingEntryBottom = trackingCoord + trackingEntry.thickness; 6249 // intersects into the top of the candidate? 6250 if (trackingEntryBottom > candidateCoord) { 6251 candidateCoord = trackingEntryBottom; 6252 touchingEntry = trackingEntry; 6253 touchingLevel = trackingLevel; 6254 touchingLateral = lateralIndex; 6255 } 6256 // butts up against top of candidate? (will happen if just intersected as well) 6257 if (trackingEntryBottom === candidateCoord) { 6258 // accumulate the highest possible stackCnt of the trackingEntries that butt up 6259 stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); 6260 } 6261 lateralIndex += 1; 6262 } 6263 } 6264 // the destination level will be after touchingEntry's level. find it 6265 let destLevel = 0; 6266 if (touchingEntry) { 6267 destLevel = touchingLevel + 1; 6268 while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { 6269 destLevel += 1; 6270 } 6271 } 6272 // if adding to an existing level, find where to insert 6273 let destLateral = -1; 6274 if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { 6275 destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; 6276 } 6277 return { 6278 touchingLevel, 6279 touchingLateral, 6280 touchingEntry, 6281 stackCnt, 6282 levelCoord: candidateCoord, 6283 level: destLevel, 6284 lateral: destLateral, 6285 }; 6286 } 6287 // sorted by levelCoord (lowest to highest) 6288 toRects() { 6289 let { entriesByLevel, levelCoords } = this; 6290 let levelCnt = entriesByLevel.length; 6291 let rects = []; 6292 for (let level = 0; level < levelCnt; level += 1) { 6293 let entries = entriesByLevel[level]; 6294 let levelCoord = levelCoords[level]; 6295 for (let entry of entries) { 6296 rects.push(Object.assign(Object.assign({}, entry), { levelCoord })); 6297 } 6298 } 6299 return rects; 6300 } 6301 } 6302 function getEntrySpanEnd(entry) { 6303 return entry.span.end; 6304 } 6305 function buildEntryKey(entry) { 6306 return entry.index + ':' + entry.span.start; 6307 } 6308 // returns groups with entries sorted by input order 6309 function groupIntersectingEntries(entries) { 6310 let merges = []; 6311 for (let entry of entries) { 6312 let filteredMerges = []; 6313 let hungryMerge = { 6314 span: entry.span, 6315 entries: [entry], 6316 }; 6317 for (let merge of merges) { 6318 if (intersectSpans(merge.span, hungryMerge.span)) { 6319 hungryMerge = { 6320 entries: merge.entries.concat(hungryMerge.entries), 6321 span: joinSpans(merge.span, hungryMerge.span), 6322 }; 6323 } 6324 else { 6325 filteredMerges.push(merge); 6326 } 6327 } 6328 filteredMerges.push(hungryMerge); 6329 merges = filteredMerges; 6330 } 6331 return merges; 6332 } 6333 function joinSpans(span0, span1) { 6334 return { 6335 start: Math.min(span0.start, span1.start), 6336 end: Math.max(span0.end, span1.end), 6337 }; 6338 } 6339 function intersectSpans(span0, span1) { 6340 let start = Math.max(span0.start, span1.start); 6341 let end = Math.min(span0.end, span1.end); 6342 if (start < end) { 6343 return { start, end }; 6344 } 6345 return null; 6346 } 6347 // general util 6348 // --------------------------------------------------------------------------------------------------------------------- 6349 function insertAt(arr, index, item) { 6350 arr.splice(index, 0, item); 6351 }
vendor: 10,429 bytes, lines 6352-6578
6352 function binarySearch(a, searchVal, getItemVal) { 6353 let startIndex = 0; 6354 let endIndex = a.length; // exclusive 6355 if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item 6356 return [0, 0]; 6357 } 6358 if (searchVal > getItemVal(a[endIndex - 1])) { // after last item 6359 return [endIndex, 0]; 6360 } 6361 while (startIndex < endIndex) { 6362 let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); 6363 let middleVal = getItemVal(a[middleIndex]); 6364 if (searchVal < middleVal) { 6365 endIndex = middleIndex; 6366 } 6367 else if (searchVal > middleVal) { 6368 startIndex = middleIndex + 1; 6369 } 6370 else { // equal! 6371 return [middleIndex, 1]; 6372 } 6373 } 6374 return [startIndex, 0]; 6375 } 6376 6377 /* 6378 An abstraction for a dragging interaction originating on an event. 6379 Does higher-level things than PointerDragger, such as possibly: 6380 - a "mirror" that moves with the pointer 6381 - a minimum number of pixels or other criteria for a true drag to begin 6382 6383 subclasses must emit: 6384 - pointerdown 6385 - dragstart 6386 - dragmove 6387 - pointerup 6388 - dragend 6389 */ 6390 class ElementDragging { 6391 constructor(el, selector) { 6392 this.emitter = new Emitter(); 6393 } 6394 destroy() { 6395 } 6396 setMirrorIsVisible(bool) { 6397 // optional if subclass doesn't want to support a mirror 6398 } 6399 setMirrorNeedsRevert(bool) { 6400 // optional if subclass doesn't want to support a mirror 6401 } 6402 setAutoScrollEnabled(bool) { 6403 // optional 6404 } 6405 } 6406 6407 // TODO: get rid of this in favor of options system, 6408 // tho it's really easy to access this globally rather than pass thru options. 6409 const config = {}; 6410 6411 /* 6412 Information about what will happen when an external element is dragged-and-dropped 6413 onto a calendar. Contains information for creating an event. 6414 */ 6415 const DRAG_META_REFINERS = { 6416 startTime: createDuration, 6417 duration: createDuration, 6418 create: Boolean, 6419 sourceId: String, 6420 }; 6421 function parseDragMeta(raw) { 6422 let { refined, extra } = refineProps(raw, DRAG_META_REFINERS); 6423 return { 6424 startTime: refined.startTime || null, 6425 duration: refined.duration || null, 6426 create: refined.create != null ? refined.create : true, 6427 sourceId: refined.sourceId, 6428 leftoverProps: extra, 6429 }; 6430 } 6431 6432 // Computes a default column header formatting string if `colFormat` is not explicitly defined 6433 function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { 6434 // if more than one week row, or if there are a lot of columns with not much space, 6435 // put just the day numbers will be in each cell 6436 if (!datesRepDistinctDays || dayCnt > 10) { 6437 return createFormatter({ weekday: 'short' }); // "Sat" 6438 } 6439 if (dayCnt > 1) { 6440 return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" 6441 } 6442 return createFormatter({ weekday: 'long' }); // "Saturday" 6443 } 6444 6445 const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no 6446 function renderInner$1(renderProps) { 6447 return renderProps.text; 6448 } 6449 6450 // BAD name for this class now. used in the Header 6451 class TableDateCell extends BaseComponent { 6452 render() { 6453 let { dateEnv, options, theme, viewApi } = this.context; 6454 let { props } = this; 6455 let { date, dateProfile } = props; 6456 let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); 6457 let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); 6458 let text = dateEnv.format(date, props.dayHeaderFormat); 6459 // if colCnt is 1, we are already in a day-view and don't need a navlink 6460 let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1) 6461 ? buildNavLinkAttrs(this.context, date) 6462 : {}; 6463 let renderProps = Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraRenderProps), { text }), dayMeta); 6464 return (h(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", generator: options.dayHeaderContent || renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContainer) => (h("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (h(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [ 6465 'fc-col-header-cell-cushion', 6466 props.isSticky && 'fc-sticky', 6467 ] })))))); 6468 } 6469 } 6470 6471 const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' }); 6472 class TableDowCell extends BaseComponent { 6473 render() { 6474 let { props } = this; 6475 let { dateEnv, theme, viewApi, options } = this.context; 6476 let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT 6477 let dateMeta = { 6478 dow: props.dow, 6479 isDisabled: false, 6480 isFuture: false, 6481 isPast: false, 6482 isToday: false, 6483 isOther: false, 6484 }; 6485 let text = dateEnv.format(date, props.dayHeaderFormat); 6486 let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public? 6487 date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text }); 6488 return (h(ContentContainer, { elTag: "th", elClasses: [ 6489 CLASS_NAME, 6490 ...getDayClassNames(dateMeta, theme), 6491 ...(props.extraClassNames || []), 6492 ], elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", generator: options.dayHeaderContent || renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => (h("div", { className: "fc-scrollgrid-sync-inner" }, 6493 h(InnerContent, { elTag: "a", elClasses: [ 6494 'fc-col-header-cell-cushion', 6495 props.isSticky && 'fc-sticky', 6496 ], elAttrs: { 6497 'aria-label': dateEnv.format(date, WEEKDAY_FORMAT), 6498 } }))))); 6499 } 6500 } 6501 6502 class NowTimer extends d { 6503 constructor(props, context) { 6504 super(props, context); 6505 this.initialNowDate = getNow(context.options.now, context.dateEnv); 6506 this.initialNowQueriedMs = new Date().valueOf(); 6507 this.state = this.computeTiming().currentState; 6508 } 6509 render() { 6510 let { props, state } = this; 6511 return props.children(state.nowDate, state.todayRange); 6512 } 6513 componentDidMount() { 6514 this.setTimeout(); 6515 } 6516 componentDidUpdate(prevProps) { 6517 if (prevProps.unit !== this.props.unit) { 6518 this.clearTimeout(); 6519 this.setTimeout(); 6520 } 6521 } 6522 componentWillUnmount() { 6523 this.clearTimeout(); 6524 } 6525 computeTiming() { 6526 let { props, context } = this; 6527 let unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); 6528 let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); 6529 let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); 6530 let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); 6531 // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) 6532 // ensure no longer than a day 6533 waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); 6534 return { 6535 currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, 6536 nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, 6537 waitMs, 6538 }; 6539 } 6540 setTimeout() { 6541 let { nextState, waitMs } = this.computeTiming(); 6542 this.timeoutId = setTimeout(() => { 6543 this.setState(nextState, () => { 6544 this.setTimeout(); 6545 }); 6546 }, waitMs); 6547 } 6548 clearTimeout() { 6549 if (this.timeoutId) { 6550 clearTimeout(this.timeoutId); 6551 } 6552 } 6553 } 6554 NowTimer.contextType = ViewContextType; 6555 function buildDayRange(date) { 6556 let start = startOfDay(date); 6557 let end = addDays(start, 1); 6558 return { start, end }; 6559 } 6560 6561 class DayHeader extends BaseComponent { 6562 constructor() { 6563 super(...arguments); 6564 this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); 6565 } 6566 render() { 6567 let { context } = this; 6568 let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props; 6569 let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); 6570 return (h(NowTimer, { unit: "day" }, (nowDate, todayRange) => (h("tr", { role: "row" }, 6571 renderIntro && renderIntro('day'), 6572 dates.map((date) => (datesRepDistinctDays ? (h(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (h(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat })))))))); 6573 } 6574 } 6575 function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { 6576 return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); 6577 } 6578
vendor: 16,258 bytes, lines 6579-6918
6579 class DaySeriesModel { 6580 constructor(range, dateProfileGenerator) { 6581 let date = range.start; 6582 let { end } = range; 6583 let indices = []; 6584 let dates = []; 6585 let dayIndex = -1; 6586 while (date < end) { // loop each day from start to end 6587 if (dateProfileGenerator.isHiddenDay(date)) { 6588 indices.push(dayIndex + 0.5); // mark that it's between indices 6589 } 6590 else { 6591 dayIndex += 1; 6592 indices.push(dayIndex); 6593 dates.push(date); 6594 } 6595 date = addDays(date, 1); 6596 } 6597 this.dates = dates; 6598 this.indices = indices; 6599 this.cnt = dates.length; 6600 } 6601 sliceRange(range) { 6602 let firstIndex = this.getDateDayIndex(range.start); // inclusive first index 6603 let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index 6604 let clippedFirstIndex = Math.max(0, firstIndex); 6605 let clippedLastIndex = Math.min(this.cnt - 1, lastIndex); 6606 // deal with in-between indices 6607 clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell 6608 clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell 6609 if (clippedFirstIndex <= clippedLastIndex) { 6610 return { 6611 firstIndex: clippedFirstIndex, 6612 lastIndex: clippedLastIndex, 6613 isStart: firstIndex === clippedFirstIndex, 6614 isEnd: lastIndex === clippedLastIndex, 6615 }; 6616 } 6617 return null; 6618 } 6619 // Given a date, returns its chronolocial cell-index from the first cell of the grid. 6620 // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. 6621 // If before the first offset, returns a negative number. 6622 // If after the last offset, returns an offset past the last cell offset. 6623 // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. 6624 getDateDayIndex(date) { 6625 let { indices } = this; 6626 let dayOffset = Math.floor(diffDays(this.dates[0], date)); 6627 if (dayOffset < 0) { 6628 return indices[0] - 1; 6629 } 6630 if (dayOffset >= indices.length) { 6631 return indices[indices.length - 1] + 1; 6632 } 6633 return indices[dayOffset]; 6634 } 6635 } 6636 6637 class DayTableModel { 6638 constructor(daySeries, breakOnWeeks) { 6639 let { dates } = daySeries; 6640 let daysPerRow; 6641 let firstDay; 6642 let rowCnt; 6643 if (breakOnWeeks) { 6644 // count columns until the day-of-week repeats 6645 firstDay = dates[0].getUTCDay(); 6646 for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { 6647 if (dates[daysPerRow].getUTCDay() === firstDay) { 6648 break; 6649 } 6650 } 6651 rowCnt = Math.ceil(dates.length / daysPerRow); 6652 } 6653 else { 6654 rowCnt = 1; 6655 daysPerRow = dates.length; 6656 } 6657 this.rowCnt = rowCnt; 6658 this.colCnt = daysPerRow; 6659 this.daySeries = daySeries; 6660 this.cells = this.buildCells(); 6661 this.headerDates = this.buildHeaderDates(); 6662 } 6663 buildCells() { 6664 let rows = []; 6665 for (let row = 0; row < this.rowCnt; row += 1) { 6666 let cells = []; 6667 for (let col = 0; col < this.colCnt; col += 1) { 6668 cells.push(this.buildCell(row, col)); 6669 } 6670 rows.push(cells); 6671 } 6672 return rows; 6673 } 6674 buildCell(row, col) { 6675 let date = this.daySeries.dates[row * this.colCnt + col]; 6676 return { 6677 key: date.toISOString(), 6678 date, 6679 }; 6680 } 6681 buildHeaderDates() { 6682 let dates = []; 6683 for (let col = 0; col < this.colCnt; col += 1) { 6684 dates.push(this.cells[0][col].date); 6685 } 6686 return dates; 6687 } 6688 sliceRange(range) { 6689 let { colCnt } = this; 6690 let seriesSeg = this.daySeries.sliceRange(range); 6691 let segs = []; 6692 if (seriesSeg) { 6693 let { firstIndex, lastIndex } = seriesSeg; 6694 let index = firstIndex; 6695 while (index <= lastIndex) { 6696 let row = Math.floor(index / colCnt); 6697 let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); 6698 segs.push({ 6699 row, 6700 firstCol: index % colCnt, 6701 lastCol: (nextIndex - 1) % colCnt, 6702 isStart: seriesSeg.isStart && index === firstIndex, 6703 isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, 6704 }); 6705 index = nextIndex; 6706 } 6707 } 6708 return segs; 6709 } 6710 } 6711 6712 class Slicer { 6713 constructor() { 6714 this.sliceBusinessHours = memoize(this._sliceBusinessHours); 6715 this.sliceDateSelection = memoize(this._sliceDateSpan); 6716 this.sliceEventStore = memoize(this._sliceEventStore); 6717 this.sliceEventDrag = memoize(this._sliceInteraction); 6718 this.sliceEventResize = memoize(this._sliceInteraction); 6719 this.forceDayIfListItem = false; // hack 6720 } 6721 sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) { 6722 let { eventUiBases } = props; 6723 let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs); 6724 return { 6725 dateSelectionSegs: this.sliceDateSelection(props.dateSelection, eventUiBases, context, ...extraArgs), 6726 businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs), 6727 fgEventSegs: eventSegs.fg, 6728 bgEventSegs: eventSegs.bg, 6729 eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), 6730 eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), 6731 eventSelection: props.eventSelection, 6732 }; // TODO: give interactionSegs? 6733 } 6734 sliceNowDate(// does not memoize 6735 date, context, ...extraArgs) { 6736 return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range 6737 {}, context, ...extraArgs); 6738 } 6739 _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) { 6740 if (!businessHours) { 6741 return []; 6742 } 6743 return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg; 6744 } 6745 _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { 6746 if (eventStore) { 6747 let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); 6748 return { 6749 bg: this.sliceEventRanges(rangeRes.bg, extraArgs), 6750 fg: this.sliceEventRanges(rangeRes.fg, extraArgs), 6751 }; 6752 } 6753 return { bg: [], fg: [] }; 6754 } 6755 _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { 6756 if (!interaction) { 6757 return null; 6758 } 6759 let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); 6760 return { 6761 segs: this.sliceEventRanges(rangeRes.fg, extraArgs), 6762 affectedInstances: interaction.affectedEvents.instances, 6763 isEvent: interaction.isEvent, 6764 }; 6765 } 6766 _sliceDateSpan(dateSpan, eventUiBases, context, ...extraArgs) { 6767 if (!dateSpan) { 6768 return []; 6769 } 6770 let eventRange = fabricateEventRange(dateSpan, eventUiBases, context); 6771 let segs = this.sliceRange(dateSpan.range, ...extraArgs); 6772 for (let seg of segs) { 6773 seg.eventRange = eventRange; 6774 } 6775 return segs; 6776 } 6777 /* 6778 "complete" seg means it has component and eventRange 6779 */ 6780 sliceEventRanges(eventRanges, extraArgs) { 6781 let segs = []; 6782 for (let eventRange of eventRanges) { 6783 segs.push(...this.sliceEventRange(eventRange, extraArgs)); 6784 } 6785 return segs; 6786 } 6787 /* 6788 "complete" seg means it has component and eventRange 6789 */ 6790 sliceEventRange(eventRange, extraArgs) { 6791 let dateRange = eventRange.range; 6792 // hack to make multi-day events that are being force-displayed as list-items to take up only one day 6793 if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { 6794 dateRange = { 6795 start: dateRange.start, 6796 end: addDays(dateRange.start, 1), 6797 }; 6798 } 6799 let segs = this.sliceRange(dateRange, ...extraArgs); 6800 for (let seg of segs) { 6801 seg.eventRange = eventRange; 6802 seg.isStart = eventRange.isStart && seg.isStart; 6803 seg.isEnd = eventRange.isEnd && seg.isEnd; 6804 } 6805 return segs; 6806 } 6807 } 6808 /* 6809 for incorporating slotMinTime/slotMaxTime if appropriate 6810 TODO: should be part of DateProfile! 6811 TimelineDateProfile already does this btw 6812 */ 6813 function computeActiveRange(dateProfile, isComponentAllDay) { 6814 let range = dateProfile.activeRange; 6815 if (isComponentAllDay) { 6816 return range; 6817 } 6818 return { 6819 start: addMs(range.start, dateProfile.slotMinTime.milliseconds), 6820 end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day 6821 }; 6822 } 6823 6824 // high-level segmenting-aware tester functions 6825 // ------------------------------------------------------------------------------------------------------------------------ 6826 function isInteractionValid(interaction, dateProfile, context) { 6827 let { instances } = interaction.mutatedEvents; 6828 for (let instanceId in instances) { 6829 if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { 6830 return false; 6831 } 6832 } 6833 return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions 6834 } 6835 function isDateSelectionValid(dateSelection, dateProfile, context) { 6836 if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { 6837 return false; 6838 } 6839 return isNewPropsValid({ dateSelection }, context); 6840 } 6841 function isNewPropsValid(newProps, context) { 6842 let calendarState = context.getCurrentData(); 6843 let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); 6844 return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); 6845 } 6846 function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) { 6847 if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { 6848 return false; 6849 } 6850 if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { 6851 return false; 6852 } 6853 return true; 6854 } 6855 // Moving Event Validation 6856 // ------------------------------------------------------------------------------------------------------------------------ 6857 function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { 6858 let currentState = context.getCurrentData(); 6859 let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions 6860 let subjectEventStore = interaction.mutatedEvents; 6861 let subjectDefs = subjectEventStore.defs; 6862 let subjectInstances = subjectEventStore.instances; 6863 let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? 6864 state.eventUiBases : 6865 { '': currentState.selectionConfig }); 6866 if (filterConfig) { 6867 subjectConfigs = mapHash(subjectConfigs, filterConfig); 6868 } 6869 // exclude the subject events. TODO: exclude defs too? 6870 let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); 6871 let otherDefs = otherEventStore.defs; 6872 let otherInstances = otherEventStore.instances; 6873 let otherConfigs = compileEventUis(otherDefs, state.eventUiBases); 6874 for (let subjectInstanceId in subjectInstances) { 6875 let subjectInstance = subjectInstances[subjectInstanceId]; 6876 let subjectRange = subjectInstance.range; 6877 let subjectConfig = subjectConfigs[subjectInstance.defId]; 6878 let subjectDef = subjectDefs[subjectInstance.defId]; 6879 // constraint 6880 if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { 6881 return false; 6882 } 6883 // overlap 6884 let { eventOverlap } = context.options; 6885 let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; 6886 for (let otherInstanceId in otherInstances) { 6887 let otherInstance = otherInstances[otherInstanceId]; 6888 // intersect! evaluate 6889 if (rangesIntersect(subjectRange, otherInstance.range)) { 6890 let otherOverlap = otherConfigs[otherInstance.defId].overlap; 6891 // consider the other event's overlap. only do this if the subject event is a "real" event 6892 if (otherOverlap === false && interaction.isEvent) { 6893 return false; 6894 } 6895 if (subjectConfig.overlap === false) { 6896 return false; 6897 } 6898 if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event 6899 new EventImpl(context, subjectDef, subjectInstance))) { 6900 return false; 6901 } 6902 } 6903 } 6904 // allow (a function) 6905 let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state 6906 for (let subjectAllow of subjectConfig.allows) { 6907 let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); 6908 let origDef = calendarEventStore.defs[subjectDef.defId]; 6909 let origInstance = calendarEventStore.instances[subjectInstanceId]; 6910 let eventApi; 6911 if (origDef) { // was previously in the calendar 6912 eventApi = new EventImpl(context, origDef, origInstance); 6913 } 6914 else { // was an external event 6915 eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates 6916 } 6917 if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { 6918 return false;
vendor: 40,285 bytes, lines 6919-7734
6919 } 6920 } 6921 } 6922 return true; 6923 } 6924 // Date Selection Validation 6925 // ------------------------------------------------------------------------------------------------------------------------ 6926 function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { 6927 let relevantEventStore = state.eventStore; 6928 let relevantDefs = relevantEventStore.defs; 6929 let relevantInstances = relevantEventStore.instances; 6930 let selection = state.dateSelection; 6931 let selectionRange = selection.range; 6932 let { selectionConfig } = context.getCurrentData(); 6933 if (filterConfig) { 6934 selectionConfig = filterConfig(selectionConfig); 6935 } 6936 // constraint 6937 if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { 6938 return false; 6939 } 6940 // overlap 6941 let { selectOverlap } = context.options; 6942 let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; 6943 for (let relevantInstanceId in relevantInstances) { 6944 let relevantInstance = relevantInstances[relevantInstanceId]; 6945 // intersect! evaluate 6946 if (rangesIntersect(selectionRange, relevantInstance.range)) { 6947 if (selectionConfig.overlap === false) { 6948 return false; 6949 } 6950 if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { 6951 return false; 6952 } 6953 } 6954 } 6955 // allow (a function) 6956 for (let selectionAllow of selectionConfig.allows) { 6957 let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection); 6958 if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { 6959 return false; 6960 } 6961 } 6962 return true; 6963 } 6964 // Constraint Utils 6965 // ------------------------------------------------------------------------------------------------------------------------ 6966 function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { 6967 for (let constraint of constraints) { 6968 if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { 6969 return false; 6970 } 6971 } 6972 return true; 6973 } 6974 function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours 6975 otherEventStore, // for if constraint is an even group ID 6976 businessHoursUnexpanded, // for if constraint is 'businessHours' 6977 context) { 6978 if (constraint === 'businessHours') { 6979 return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); 6980 } 6981 if (typeof constraint === 'string') { // an group ID 6982 return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint)); 6983 } 6984 if (typeof constraint === 'object' && constraint) { // non-null object 6985 return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); 6986 } 6987 return []; // if it's false 6988 } 6989 // TODO: move to event-store file? 6990 function eventStoreToRanges(eventStore) { 6991 let { instances } = eventStore; 6992 let ranges = []; 6993 for (let instanceId in instances) { 6994 ranges.push(instances[instanceId].range); 6995 } 6996 return ranges; 6997 } 6998 // TODO: move to geom file? 6999 function anyRangesContainRange(outerRanges, innerRange) { 7000 for (let outerRange of outerRanges) { 7001 if (rangeContainsRange(outerRange, innerRange)) { 7002 return true; 7003 } 7004 } 7005 return false; 7006 } 7007 7008 const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; 7009 class Scroller extends BaseComponent { 7010 constructor() { 7011 super(...arguments); 7012 this.handleEl = (el) => { 7013 this.el = el; 7014 setRef(this.props.elRef, el); 7015 }; 7016 } 7017 render() { 7018 let { props } = this; 7019 let { liquid, liquidIsAbsolute } = props; 7020 let isAbsolute = liquid && liquidIsAbsolute; 7021 let className = ['fc-scroller']; 7022 if (liquid) { 7023 if (liquidIsAbsolute) { 7024 className.push('fc-scroller-liquid-absolute'); 7025 } 7026 else { 7027 className.push('fc-scroller-liquid'); 7028 } 7029 } 7030 return (h("div", { ref: this.handleEl, className: className.join(' '), style: { 7031 overflowX: props.overflowX, 7032 overflowY: props.overflowY, 7033 left: (isAbsolute && -(props.overcomeLeft || 0)) || '', 7034 right: (isAbsolute && -(props.overcomeRight || 0)) || '', 7035 bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', 7036 marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', 7037 marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', 7038 marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', 7039 maxHeight: props.maxHeight || '', 7040 } }, props.children)); 7041 } 7042 needsXScrolling() { 7043 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { 7044 return false; 7045 } 7046 // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. 7047 // much more reliable to see if children are taller than the scroller, even tho doesn't account for 7048 // inner-child margins and absolute positioning 7049 let { el } = this; 7050 let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); 7051 let { children } = el; 7052 for (let i = 0; i < children.length; i += 1) { 7053 let childEl = children[i]; 7054 if (childEl.getBoundingClientRect().width > realClientWidth) { 7055 return true; 7056 } 7057 } 7058 return false; 7059 } 7060 needsYScrolling() { 7061 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { 7062 return false; 7063 } 7064 // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. 7065 // much more reliable to see if children are taller than the scroller, even tho doesn't account for 7066 // inner-child margins and absolute positioning 7067 let { el } = this; 7068 let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); 7069 let { children } = el; 7070 for (let i = 0; i < children.length; i += 1) { 7071 let childEl = children[i]; 7072 if (childEl.getBoundingClientRect().height > realClientHeight) { 7073 return true; 7074 } 7075 } 7076 return false; 7077 } 7078 getXScrollbarWidth() { 7079 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { 7080 return 0; 7081 } 7082 return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? 7083 } 7084 getYScrollbarWidth() { 7085 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { 7086 return 0; 7087 } 7088 return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? 7089 } 7090 } 7091 7092 /* 7093 TODO: somehow infer OtherArgs from masterCallback? 7094 TODO: infer RefType from masterCallback if provided 7095 */ 7096 class RefMap { 7097 constructor(masterCallback) { 7098 this.masterCallback = masterCallback; 7099 this.currentMap = {}; 7100 this.depths = {}; 7101 this.callbackMap = {}; 7102 this.handleValue = (val, key) => { 7103 let { depths, currentMap } = this; 7104 let removed = false; 7105 let added = false; 7106 if (val !== null) { 7107 // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore 7108 removed = (key in currentMap); 7109 currentMap[key] = val; 7110 depths[key] = (depths[key] || 0) + 1; 7111 added = true; 7112 } 7113 else { 7114 depths[key] -= 1; 7115 if (!depths[key]) { 7116 delete currentMap[key]; 7117 delete this.callbackMap[key]; 7118 removed = true; 7119 } 7120 } 7121 if (this.masterCallback) { 7122 if (removed) { 7123 this.masterCallback(null, String(key)); 7124 } 7125 if (added) { 7126 this.masterCallback(val, String(key)); 7127 } 7128 } 7129 }; 7130 } 7131 createRef(key) { 7132 let refCallback = this.callbackMap[key]; 7133 if (!refCallback) { 7134 refCallback = this.callbackMap[key] = (val) => { 7135 this.handleValue(val, String(key)); 7136 }; 7137 } 7138 return refCallback; 7139 } 7140 // TODO: check callers that don't care about order. should use getAll instead 7141 // NOTE: this method has become less valuable now that we are encouraged to map order by some other index 7142 // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" 7143 collect(startIndex, endIndex, step) { 7144 return collectFromHash(this.currentMap, startIndex, endIndex, step); 7145 } 7146 getAll() { 7147 return hashValuesToArray(this.currentMap); 7148 } 7149 } 7150 7151 function computeShrinkWidth(chunkEls) { 7152 let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); 7153 let largestWidth = 0; 7154 for (let shrinkCell of shrinkCells) { 7155 largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); 7156 } 7157 return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits 7158 } 7159 function getSectionHasLiquidHeight(props, sectionConfig) { 7160 return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well) 7161 } 7162 function getAllowYScrolling(props, sectionConfig) { 7163 return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars 7164 getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars 7165 } 7166 // TODO: ONLY use `arg`. force out internal function to use same API 7167 function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) { 7168 let { expandRows } = arg; 7169 let content = typeof chunkConfig.content === 'function' ? 7170 chunkConfig.content(arg) : 7171 h('table', { 7172 role: 'presentation', 7173 className: [ 7174 chunkConfig.tableClassName, 7175 sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '', 7176 ].join(' '), 7177 style: { 7178 minWidth: arg.tableMinWidth, 7179 width: arg.clientWidth, 7180 height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height 7181 }, 7182 }, arg.tableColGroupNode, h(isHeader ? 'thead' : 'tbody', { 7183 role: 'presentation', 7184 }, typeof chunkConfig.rowContent === 'function' 7185 ? chunkConfig.rowContent(arg) 7186 : chunkConfig.rowContent)); 7187 return content; 7188 } 7189 function isColPropsEqual(cols0, cols1) { 7190 return isArraysEqual(cols0, cols1, isPropsEqual); 7191 } 7192 function renderMicroColGroup(cols, shrinkWidth) { 7193 let colNodes = []; 7194 /* 7195 for ColProps with spans, it would have been great to make a single <col span=""> 7196 HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans. 7197 SOLUTION: making individual <col> elements makes Chrome behave. 7198 */ 7199 for (let colProps of cols) { 7200 let span = colProps.span || 1; 7201 for (let i = 0; i < span; i += 1) { 7202 colNodes.push(h("col", { style: { 7203 width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''), 7204 minWidth: colProps.minWidth || '', 7205 } })); 7206 } 7207 } 7208 return h('colgroup', {}, ...colNodes); 7209 } 7210 function sanitizeShrinkWidth(shrinkWidth) { 7211 /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth 7212 4 accounts for 2 2-pixel borders. TODO: better solution? */ 7213 return shrinkWidth == null ? 4 : shrinkWidth; 7214 } 7215 function hasShrinkWidth(cols) { 7216 for (let col of cols) { 7217 if (col.width === 'shrink') { 7218 return true; 7219 } 7220 } 7221 return false; 7222 } 7223 function getScrollGridClassNames(liquid, context) { 7224 let classNames = [ 7225 'fc-scrollgrid', 7226 context.theme.getClass('table'), 7227 ]; 7228 if (liquid) { 7229 classNames.push('fc-scrollgrid-liquid'); 7230 } 7231 return classNames; 7232 } 7233 function getSectionClassNames(sectionConfig, wholeTableVGrow) { 7234 let classNames = [ 7235 'fc-scrollgrid-section', 7236 `fc-scrollgrid-section-${sectionConfig.type}`, 7237 sectionConfig.className, // used? 7238 ]; 7239 if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) { 7240 classNames.push('fc-scrollgrid-section-liquid'); 7241 } 7242 if (sectionConfig.isSticky) { 7243 classNames.push('fc-scrollgrid-section-sticky'); 7244 } 7245 return classNames; 7246 } 7247 function renderScrollShim(arg) { 7248 return (h("div", { className: "fc-scrollgrid-sticky-shim", style: { 7249 width: arg.clientWidth, 7250 minWidth: arg.tableMinWidth, 7251 } })); 7252 } 7253 function getStickyHeaderDates(options) { 7254 let { stickyHeaderDates } = options; 7255 if (stickyHeaderDates == null || stickyHeaderDates === 'auto') { 7256 stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto'; 7257 } 7258 return stickyHeaderDates; 7259 } 7260 function getStickyFooterScrollbar(options) { 7261 let { stickyFooterScrollbar } = options; 7262 if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') { 7263 stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto'; 7264 } 7265 return stickyFooterScrollbar; 7266 } 7267 7268 class SimpleScrollGrid extends BaseComponent { 7269 constructor() { 7270 super(...arguments); 7271 this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time 7272 // yucky to memoize VNodes, but much more efficient for consumers 7273 this.renderMicroColGroup = memoize(renderMicroColGroup); 7274 this.scrollerRefs = new RefMap(); 7275 this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this)); 7276 this.state = { 7277 shrinkWidth: null, 7278 forceYScrollbars: false, 7279 scrollerClientWidths: {}, 7280 scrollerClientHeights: {}, 7281 }; 7282 // TODO: can do a really simple print-view. dont need to join rows 7283 this.handleSizing = () => { 7284 this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims())); 7285 }; 7286 } 7287 render() { 7288 let { props, state, context } = this; 7289 let sectionConfigs = props.sections || []; 7290 let cols = this.processCols(props.cols); 7291 let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth); 7292 let classNames = getScrollGridClassNames(props.liquid, context); 7293 if (props.collapsibleWidth) { 7294 classNames.push('fc-scrollgrid-collapsible'); 7295 } 7296 // TODO: make DRY 7297 let configCnt = sectionConfigs.length; 7298 let configI = 0; 7299 let currentConfig; 7300 let headSectionNodes = []; 7301 let bodySectionNodes = []; 7302 let footSectionNodes = []; 7303 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { 7304 headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true)); 7305 configI += 1; 7306 } 7307 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { 7308 bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false)); 7309 configI += 1; 7310 } 7311 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { 7312 footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true)); 7313 configI += 1; 7314 } 7315 // firefox bug: when setting height on table and there is a thead or tfoot, 7316 // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524) 7317 // use getCanVGrowWithinCell as a way to detect table-stupid firefox. 7318 // if so, use a simpler dom structure, jam everything into a lone tbody. 7319 let isBuggy = !getCanVGrowWithinCell(); 7320 const roleAttrs = { role: 'rowgroup' }; 7321 return h('table', { 7322 role: 'grid', 7323 className: classNames.join(' '), 7324 style: { height: props.height }, 7325 }, Boolean(!isBuggy && headSectionNodes.length) && h('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && h('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && h('tfoot', roleAttrs, ...footSectionNodes), isBuggy && h('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes)); 7326 } 7327 renderSection(sectionConfig, microColGroupNode, isHeader) { 7328 if ('outerContent' in sectionConfig) { 7329 return (h(p, { key: sectionConfig.key }, sectionConfig.outerContent)); 7330 } 7331 return (h("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader))); 7332 } 7333 renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) { 7334 if ('outerContent' in chunkConfig) { 7335 return chunkConfig.outerContent; 7336 } 7337 let { props } = this; 7338 let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state; 7339 let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config? 7340 let isLiquid = getSectionHasLiquidHeight(props, sectionConfig); 7341 // for `!props.liquid` - is WHOLE scrollgrid natural height? 7342 // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars 7343 let overflowY = !props.liquid ? 'visible' : 7344 forceYScrollbars ? 'scroll' : 7345 !needsYScrolling ? 'hidden' : 7346 'auto'; 7347 let sectionKey = sectionConfig.key; 7348 let content = renderChunkContent(sectionConfig, chunkConfig, { 7349 tableColGroupNode: microColGroupNode, 7350 tableMinWidth: '', 7351 clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null, 7352 clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null, 7353 expandRows: sectionConfig.expandRows, 7354 syncRowHeights: false, 7355 rowSyncHeights: [], 7356 reportRowHeightChange: () => { }, 7357 }, isHeader); 7358 return h(isHeader ? 'th' : 'td', { 7359 ref: chunkConfig.elRef, 7360 role: 'presentation', 7361 }, h("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` }, 7362 h(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 7363 : true }, content))); 7364 } 7365 _handleScrollerEl(scrollerEl, key) { 7366 let section = getSectionByKey(this.props.sections, key); 7367 if (section) { 7368 setRef(section.chunk.scrollerElRef, scrollerEl); 7369 } 7370 } 7371 componentDidMount() { 7372 this.handleSizing(); 7373 this.context.addResizeHandler(this.handleSizing); 7374 } 7375 componentDidUpdate() { 7376 // TODO: need better solution when state contains non-sizing things 7377 this.handleSizing(); 7378 } 7379 componentWillUnmount() { 7380 this.context.removeResizeHandler(this.handleSizing); 7381 } 7382 computeShrinkWidth() { 7383 return hasShrinkWidth(this.props.cols) 7384 ? computeShrinkWidth(this.scrollerElRefs.getAll()) 7385 : 0; 7386 } 7387 computeScrollerDims() { 7388 let scrollbarWidth = getScrollbarWidths(); 7389 let { scrollerRefs, scrollerElRefs } = this; 7390 let forceYScrollbars = false; 7391 let scrollerClientWidths = {}; 7392 let scrollerClientHeights = {}; 7393 for (let sectionKey in scrollerRefs.currentMap) { 7394 let scroller = scrollerRefs.currentMap[sectionKey]; 7395 if (scroller && scroller.needsYScrolling()) { 7396 forceYScrollbars = true; 7397 break; 7398 } 7399 } 7400 for (let section of this.props.sections) { 7401 let sectionKey = section.key; 7402 let scrollerEl = scrollerElRefs.currentMap[sectionKey]; 7403 if (scrollerEl) { 7404 let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders 7405 scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars 7406 ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future 7407 : 0)); 7408 scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height); 7409 } 7410 } 7411 return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights }; 7412 } 7413 } 7414 SimpleScrollGrid.addStateEquality({ 7415 scrollerClientWidths: isPropsEqual, 7416 scrollerClientHeights: isPropsEqual, 7417 }); 7418 function getSectionByKey(sections, key) { 7419 for (let section of sections) { 7420 if (section.key === key) { 7421 return section; 7422 } 7423 } 7424 return null; 7425 } 7426 7427 class EventContainer extends BaseComponent { 7428 constructor() { 7429 super(...arguments); 7430 this.handleEl = (el) => { 7431 if (el) { 7432 setElSeg(el, this.props.seg); 7433 } 7434 // TODO: when null, should unset to avoid memory leaks? 7435 }; 7436 } 7437 render() { 7438 const { props, context } = this; 7439 const { options } = context; 7440 const { seg } = props; 7441 const { eventRange } = seg; 7442 const { ui } = eventRange; 7443 const renderProps = { 7444 event: new EventImpl(context, eventRange.def, eventRange.instance), 7445 view: context.viewApi, 7446 timeText: props.timeText, 7447 textColor: ui.textColor, 7448 backgroundColor: ui.backgroundColor, 7449 borderColor: ui.borderColor, 7450 isDraggable: !props.disableDragging && computeSegDraggable(seg, context), 7451 isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context), 7452 isEndResizable: !props.disableResizing && computeSegEndResizable(seg), 7453 isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting), 7454 isStart: Boolean(seg.isStart), 7455 isEnd: Boolean(seg.isEnd), 7456 isPast: Boolean(props.isPast), 7457 isFuture: Boolean(props.isFuture), 7458 isToday: Boolean(props.isToday), 7459 isSelected: Boolean(props.isSelected), 7460 isDragging: Boolean(props.isDragging), 7461 isResizing: Boolean(props.isResizing), 7462 }; 7463 return (h(ContentContainer, Object.assign({}, props /* contains children */, { elRef: this.handleEl, elClasses: [ 7464 ...getEventClassNames(renderProps), 7465 ...seg.eventRange.ui.classNames, 7466 ...(props.elClasses || []), 7467 ], renderProps: renderProps, generatorName: "eventContent", generator: options.eventContent || props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount }))); 7468 } 7469 } 7470 7471 // should not be a purecomponent 7472 class StandardEvent extends BaseComponent { 7473 render() { 7474 let { props, context } = this; 7475 let { options } = context; 7476 let { seg } = props; 7477 let { ui } = seg.eventRange; 7478 let timeFormat = options.eventTimeFormat || props.defaultTimeFormat; 7479 let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd); 7480 return (h(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: { 7481 borderColor: ui.borderColor, 7482 backgroundColor: ui.backgroundColor, 7483 }, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1$1, timeText: timeText }), (InnerContent, eventContentArg) => (h(p, null, 7484 h(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }), 7485 Boolean(eventContentArg.isStartResizable) && (h("div", { className: "fc-event-resizer fc-event-resizer-start" })), 7486 Boolean(eventContentArg.isEndResizable) && (h("div", { className: "fc-event-resizer fc-event-resizer-end" })))))); 7487 } 7488 } 7489 function renderInnerContent$1$1(innerProps) { 7490 return (h("div", { className: "fc-event-main-frame" }, 7491 innerProps.timeText && (h("div", { className: "fc-event-time" }, innerProps.timeText)), 7492 h("div", { className: "fc-event-title-container" }, 7493 h("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || h(p, null, "\u00A0"))))); 7494 } 7495 7496 const NowIndicatorContainer = (props) => (h(ViewContextType.Consumer, null, (context) => { 7497 let { options } = context; 7498 let renderProps = { 7499 isAxis: props.isAxis, 7500 date: context.dateEnv.toDate(props.date), 7501 view: context.viewApi, 7502 }; 7503 return (h(ContentContainer, Object.assign({}, props /* includes children */, { elTag: props.elTag || 'div', renderProps: renderProps, generatorName: "nowIndicatorContent", generator: options.nowIndicatorContent, classNameGenerator: options.nowIndicatorClassNames, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }))); 7504 })); 7505 7506 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' }); 7507 class DayCellContainer extends BaseComponent { 7508 constructor() { 7509 super(...arguments); 7510 this.refineRenderProps = memoizeObjArg(refineRenderProps); 7511 } 7512 render() { 7513 let { props, context } = this; 7514 let { options } = context; 7515 let renderProps = this.refineRenderProps({ 7516 date: props.date, 7517 dateProfile: props.dateProfile, 7518 todayRange: props.todayRange, 7519 showDayNumber: props.showDayNumber, 7520 extraRenderProps: props.extraRenderProps, 7521 viewApi: context.viewApi, 7522 dateEnv: context.dateEnv, 7523 }); 7524 return (h(ContentContainer, Object.assign({}, props /* includes children */, { elClasses: [ 7525 ...getDayClassNames(renderProps, context.theme), 7526 ...(props.elClasses || []), 7527 ], elAttrs: Object.assign(Object.assign({}, props.elAttrs), (renderProps.isDisabled ? {} : { 'data-date': formatDayString(props.date) })), renderProps: renderProps, generatorName: "dayCellContent", generator: options.dayCellContent || props.defaultGenerator, classNameGenerator: 7528 // don't use custom classNames if disabled 7529 renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount }))); 7530 } 7531 } 7532 function hasCustomDayCellContent(options) { 7533 return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options)); 7534 } 7535 function refineRenderProps(raw) { 7536 let { date, dateEnv } = raw; 7537 let dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile); 7538 return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraRenderProps); 7539 } 7540 7541 class BgEvent extends BaseComponent { 7542 render() { 7543 let { props } = this; 7544 let { seg } = props; 7545 return (h(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 })); 7546 } 7547 } 7548 function renderInnerContent$3(props) { 7549 let { title } = props.event; 7550 return title && (h("div", { className: "fc-event-title" }, props.event.title)); 7551 } 7552 function renderFill(fillType) { 7553 return (h("div", { className: `fc-${fillType}` })); 7554 } 7555 7556 const WeekNumberContainer = (props) => (h(ViewContextType.Consumer, null, (context) => { 7557 let { dateEnv, options } = context; 7558 let { date } = props; 7559 let format = options.weekNumberFormat || props.defaultFormat; 7560 let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well? 7561 let text = dateEnv.format(date, format); 7562 let renderProps = { num, text, date }; 7563 return (h(ContentContainer // why isn't WeekNumberContentArg being auto-detected? 7564 , Object.assign({}, props /* includes children */, { renderProps: renderProps, generatorName: "weekNumberContent", generator: options.weekNumberContent || renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }))); 7565 })); 7566 function renderInner(innerProps) { 7567 return innerProps.text; 7568 } 7569 7570 const PADDING_FROM_VIEWPORT = 10; 7571 class Popover extends BaseComponent { 7572 constructor() { 7573 super(...arguments); 7574 this.state = { 7575 titleId: getUniqueDomId(), 7576 }; 7577 this.handleRootEl = (el) => { 7578 this.rootEl = el; 7579 if (this.props.elRef) { 7580 setRef(this.props.elRef, el); 7581 } 7582 }; 7583 // Triggered when the user clicks *anywhere* in the document, for the autoHide feature 7584 this.handleDocumentMouseDown = (ev) => { 7585 // only hide the popover if the click happened outside the popover 7586 const target = getEventTargetViaRoot(ev); 7587 if (!this.rootEl.contains(target)) { 7588 this.handleCloseClick(); 7589 } 7590 }; 7591 this.handleDocumentKeyDown = (ev) => { 7592 if (ev.key === 'Escape') { 7593 this.handleCloseClick(); 7594 } 7595 }; 7596 this.handleCloseClick = () => { 7597 let { onClose } = this.props; 7598 if (onClose) { 7599 onClose(); 7600 } 7601 }; 7602 } 7603 render() { 7604 let { theme, options } = this.context; 7605 let { props, state } = this; 7606 let classNames = [ 7607 'fc-popover', 7608 theme.getClass('popover'), 7609 ].concat(props.extraClassNames || []); 7610 return j(h("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }), 7611 h("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') }, 7612 h("span", { className: "fc-popover-title", id: state.titleId }, props.title), 7613 h("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })), 7614 h("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl); 7615 } 7616 componentDidMount() { 7617 document.addEventListener('mousedown', this.handleDocumentMouseDown); 7618 document.addEventListener('keydown', this.handleDocumentKeyDown); 7619 this.updateSize(); 7620 } 7621 componentWillUnmount() { 7622 document.removeEventListener('mousedown', this.handleDocumentMouseDown); 7623 document.removeEventListener('keydown', this.handleDocumentKeyDown); 7624 } 7625 updateSize() { 7626 let { isRtl } = this.context; 7627 let { alignmentEl, alignGridTop } = this.props; 7628 let { rootEl } = this; 7629 let alignmentRect = computeClippedClientRect(alignmentEl); 7630 if (alignmentRect) { 7631 let popoverDims = rootEl.getBoundingClientRect(); 7632 // position relative to viewport 7633 let popoverTop = alignGridTop 7634 ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top 7635 : alignmentRect.top; 7636 let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left; 7637 // constrain 7638 popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT); 7639 popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width); 7640 popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT); 7641 let origin = rootEl.offsetParent.getBoundingClientRect(); 7642 applyStyle(rootEl, { 7643 top: popoverTop - origin.top, 7644 left: popoverLeft - origin.left, 7645 }); 7646 } 7647 } 7648 } 7649 7650 class MorePopover extends DateComponent { 7651 constructor() { 7652 super(...arguments); 7653 this.handleRootEl = (rootEl) => { 7654 this.rootEl = rootEl; 7655 if (rootEl) { 7656 this.context.registerInteractiveComponent(this, { 7657 el: rootEl, 7658 useEventCenter: false, 7659 }); 7660 } 7661 else { 7662 this.context.unregisterInteractiveComponent(this); 7663 } 7664 }; 7665 } 7666 render() { 7667 let { options, dateEnv } = this.context; 7668 let { props } = this; 7669 let { startDate, todayRange, dateProfile } = props; 7670 let title = dateEnv.format(startDate, options.dayPopoverFormat); 7671 return (h(DayCellContainer, { elRef: this.handleRootEl, date: startDate, dateProfile: dateProfile, todayRange: todayRange }, (InnerContent, renderProps, elAttrs) => (h(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 }, 7672 hasCustomDayCellContent(options) && (h(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })), 7673 props.children)))); 7674 } 7675 queryHit(positionLeft, positionTop, elWidth, elHeight) { 7676 let { rootEl, props } = this; 7677 if (positionLeft >= 0 && positionLeft < elWidth && 7678 positionTop >= 0 && positionTop < elHeight) { 7679 return { 7680 dateProfile: props.dateProfile, 7681 dateSpan: Object.assign({ allDay: true, range: { 7682 start: props.startDate, 7683 end: props.endDate, 7684 } }, props.extraDateSpan), 7685 dayEl: rootEl, 7686 rect: { 7687 left: 0, 7688 top: 0, 7689 right: elWidth, 7690 bottom: elHeight, 7691 }, 7692 layer: 1, // important when comparing with hits from other components 7693 }; 7694 } 7695 return null; 7696 } 7697 } 7698 7699 class MoreLinkContainer extends BaseComponent { 7700 constructor() { 7701 super(...arguments); 7702 this.linkElRef = y(); 7703 this.state = { 7704 isPopoverOpen: false, 7705 popoverId: getUniqueDomId(), 7706 }; 7707 this.handleClick = (ev) => { 7708 let { props, context } = this; 7709 let { moreLinkClick } = context.options; 7710 let date = computeRange(props).start; 7711 function buildPublicSeg(seg) { 7712 let { def, instance, range } = seg.eventRange; 7713 return { 7714 event: new EventImpl(context, def, instance), 7715 start: context.dateEnv.toDate(range.start), 7716 end: context.dateEnv.toDate(range.end), 7717 isStart: seg.isStart, 7718 isEnd: seg.isEnd, 7719 }; 7720 } 7721 if (typeof moreLinkClick === 'function') { 7722 moreLinkClick = moreLinkClick({ 7723 date, 7724 allDay: Boolean(props.allDayDate), 7725 allSegs: props.allSegs.map(buildPublicSeg), 7726 hiddenSegs: props.hiddenSegs.map(buildPublicSeg), 7727 jsEvent: ev, 7728 view: context.viewApi, 7729 }); 7730 } 7731 if (!moreLinkClick || moreLinkClick === 'popover') { 7732 this.setState({ isPopoverOpen: true }); 7733 } 7734 else if (typeof moreLinkClick === 'string') { // a view n
7734ame 7735 context.calendarApi.zoomTo(date, moreLinkClick); 7736 } 7737 }; 7738 this.handlePopoverClose = () => { 7739 this.setState({ isPopoverOpen: false }); 7740 }; 7741 } 7742 render() { 7743 let { props, state } = this; 7744 return (h(ViewContextType.Consumer, null, (context) => { 7745 let { viewApi, options, calendarApi } = context; 7746 let { moreLinkText } = options; 7747 let { moreCnt } = props; 7748 let range = computeRange(props); 7749 let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals 7750 ? moreLinkText.call(calendarApi, moreCnt) 7751 : `+${moreCnt} ${moreLinkText}`; 7752 let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text); 7753 let renderProps = { 7754 num: moreCnt, 7755 shortText: `+${moreCnt}`, 7756 text, 7757 view: viewApi, 7758 }; 7759 return (h(p, null, 7760 Boolean(props.moreCnt) && (h(ContentContainer, { elTag: props.elTag || 'a', elRef: this.linkElRef, elClasses: [ 7761 ...(props.elClasses || []), 7762 'fc-more-link', 7763 ], 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", generator: options.moreLinkContent || props.defaultGenerator || renderMoreLinkInner$1, classNameGenerator: options.moreLinkClassNames, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, props.children)), 7764 state.isPopoverOpen && (h(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 ? 7765 props.alignmentElRef.current : 7766 this.linkElRef.current, alignGridTop: props.alignGridTop, onClose: this.handlePopoverClose }, props.popoverContent())))); 7767 })); 7768 } 7769 componentDidMount() { 7770 this.updateParentEl(); 7771 } 7772 componentDidUpdate() { 7773 this.updateParentEl(); 7774 } 7775 updateParentEl() { 7776 if (this.linkElRef.current) { 7777 this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness'); 7778 } 7779 } 7780 } 7781 function renderMoreLinkInner$1(props) { 7782 return props.text; 7783 } 7784 function computeRange(props) { 7785 if (props.allDayDate) { 7786 return { 7787 start: props.allDayDate, 7788 end: addDays(props.allDayDate, 1), 7789 }; 7790 } 7791 let { hiddenSegs } = props; 7792 return { 7793 start: computeEarliestSegStart(hiddenSegs), 7794 end: computeLatestSegEnd(hiddenSegs), 7795 }; 7796 } 7797 function computeEarliestSegStart(segs) { 7798 return segs.reduce(pickEarliestStart).eventRange.range.start; 7799 } 7800 function pickEarliestStart(seg0, seg1) { 7801 return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1; 7802 } 7803 function computeLatestSegEnd(segs) { 7804 return segs.reduce(pickLatestEnd).eventRange.range.end; 7805 } 7806 function pickLatestEnd(seg0, seg1) { 7807 return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1; 7808 } 7809 7810 class Store { 7811 constructor() { 7812 this.handlers = []; 7813 } 7814 set(value) { 7815 this.currentValue = value; 7816 for (let handler of this.handlers) { 7817 handler(value); 7818 } 7819 } 7820 subscribe(handler) { 7821 this.handlers.push(handler); 7822 if (this.currentValue !== undefined) { 7823 handler(this.currentValue); 7824 } 7825 } 7826 } 7827 7828 /* 7829 Subscribers will get a LIST of CustomRenderings 7830 */ 7831 class CustomRenderingStore extends Store { 7832 constructor() { 7833 super(...arguments); 7834 this.map = new Map(); 7835 } 7836 // for consistent order 7837 handle(customRendering) { 7838 const { map } = this; 7839 let updated = false;
7840 if (customRendering.isActive) { 7841 map.set(customRendering.id, customRendering); 7842 updated = true; 7843 } 7844 else if (map.has(customRendering.id)) { 7845 map.delete(customRendering.id); 7846 updated = true; 7847 } 7848 if (updated) { 7849 this.set(map); 7850 } 7851 } 7852 } 7853 7854 var internal = { 7855 __proto__: null, 7856 BASE_OPTION_DEFAULTS: BASE_OPTION_DEFAULTS, 7857 BaseComponent: BaseComponent, 7858 BgEvent: BgEvent, 7859 CalendarImpl: CalendarImpl, 7860 CalendarRoot: CalendarRoot, 7861 ContentContainer: ContentContainer, 7862 CustomRenderingStore: CustomRenderingStore, 7863 DateComponent: DateComponent, 7864 DateEnv: DateEnv, 7865 DateProfileGenerator: DateProfileGenerator, 7866 DayCellContainer: DayCellContainer, 7867 DayHeader: DayHeader, 7868 DaySeriesModel: DaySeriesModel, 7869 DayTableModel: DayTableModel, 7870 DelayedRunner: DelayedRunner, 7871 ElementDragging: ElementDragging, 7872 ElementScrollController: ElementScrollController, 7873 Emitter: Emitter, 7874 EventContainer: EventContainer, 7875 EventImpl: EventImpl, 7876 Interaction: Interaction, 7877 MoreLinkContainer: MoreLinkContainer, 7878 NamedTimeZoneImpl: NamedTimeZoneImpl, 7879 NowIndicatorContainer: NowIndicatorContainer, 7880 NowTimer: NowTimer, 7881 PositionCache: PositionCache, 7882 RefMap: RefMap, 7883 ScrollController: ScrollController, 7884 ScrollResponder: ScrollResponder, 7885 Scroller: Scroller, 7886 SegHierarchy: SegHierarchy, 7887 SimpleScrollGrid: SimpleScrollGrid, 7888 Slicer: Slicer, 7889 Splitter: Splitter, 7890 StandardEvent: StandardEvent, 7891 TableDateCell: TableDateCell, 7892 TableDowCell: TableDowCell, 7893 Theme: Theme, 7894 ViewContainer: ViewContainer$1, 7895 ViewContextType: ViewContextType, 7896 WeekNumberContainer: WeekNumberContainer, 7897 WindowScrollController: WindowScrollController, 7898 addDays: addDays, 7899 addDurations: addDurations, 7900 addMs: addMs, 7901 addWeeks: addWeeks, 7902 allowContextMenu: allowContextMenu, 7903 allowSelection: allowSelection, 7904 applyMutationToEventStore: applyMutationToEventStore, 7905 applyStyle: applyStyle, 7906 asCleanDays: asCleanDays, 7907 asRoughMinutes: asRoughMinutes, 7908 asRoughMs: asRoughMs, 7909 asRoughSeconds: asRoughSeconds, 7910 binarySearch: binarySearch, 7911 buildElAttrs: buildElAttrs, 7912 buildEntryKey: buildEntryKey, 7913 buildEventApis: buildEventApis, 7914 buildEventRangeKey: buildEventRangeKey, 7915 buildIsoString: buildIsoString, 7916 buildNavLinkAttrs: buildNavLinkAttrs, 7917 buildSegTimeText: buildSegTimeText, 7918 collectFromHash: collectFromHash, 7919 combineEventUis: combineEventUis, 7920 compareByFieldSpecs: compareByFieldSpecs, 7921 compareNumbers: compareNumbers, 7922 compareObjs: compareObjs, 7923 computeEarliestSegStart: computeEarliestSegStart, 7924 computeEdges: computeEdges, 7925 computeFallbackHeaderFormat: computeFallbackHeaderFormat, 7926 computeInnerRect: computeInnerRect, 7927 computeRect: computeRect, 7928 computeShrinkWidth: computeShrinkWidth, 7929 computeVisibleDayRange: computeVisibleDayRange, 7930 config: config, 7931 constrainPoint: constrainPoint, 7932 createDuration: createDuration, 7933 createEmptyEventStore: createEmptyEventStore, 7934 createEventInstance: createEventInstance, 7935 createEventUi: createEventUi, 7936 createFormatter: createFormatter, 7937 diffDates: diffDates, 7938 diffDayAndTime: diffDayAndTime, 7939 diffDays: diffDays, 7940 diffPoints: diffPoints, 7941 diffWeeks: diffWeeks, 7942 diffWholeDays: diffWholeDays, 7943 diffWholeWeeks: diffWholeWeeks, 7944 disableCursor: disableCursor, 7945 elementClosest: elementClosest, 7946 elementMatches: elementMatches, 7947 enableCursor: enableCursor, 7948 eventTupleToStore: eventTupleToStore, 7949 filterHash: filterHash, 7950 findDirectChildren: findDirectChildren, 7951 findElements: findElements, 7952 flexibleCompare: flexibleCompare, 7953 formatDayString: formatDayString, 7954 formatIsoTimeString: formatIsoTimeString, 7955 getAllowYScrolling: getAllowYScrolling, 7956 getCanVGrowWithinCell: getCanVGrowWithinCell, 7957 getClippingParents: getClippingParents, 7958 getDateMeta: getDateMeta, 7959 getDayClassNames: getDayClassNames, 7960 getDefaultEventEnd: getDefaultEventEnd, 7961 getElRoot: getElRoot, 7962 getElSeg: getElSeg, 7963 getEntrySpanEnd: getEntrySpanEnd, 7964 getEventTargetViaRoot: getEventTargetViaRoot, 7965 getIsRtlScrollbarOnLeft: getIsRtlScrollbarOnLeft, 7966 getRectCenter: getRectCenter, 7967 getRelevantEvents: getRelevantEvents, 7968 getScrollGridClassNames: getScrollGridClassNames, 7969 getScrollbarWidths: getScrollbarWidths, 7970 getSectionClassNames: getSectionClassNames, 7971 getSectionHasLiquidHeight: getSectionHasLiquidHeight, 7972 getSegAnchorAttrs: getSegAnchorAttrs, 7973 getSegMeta: getSegMeta, 7974 getSlotClassNames: getSlotClassNames, 7975 getStickyFooterScrollbar: getStickyFooterScrollbar, 7976 getStickyHeaderDates: getStickyHeaderDates, 7977 getUniqueDomId: getUniqueDomId, 7978 globalLocales: globalLocales, 7979 globalPlugins: globalPlugins, 7980 greatestDurationDenominator: greatestDurationDenominator, 7981 groupIntersectingEntries: groupIntersectingEntries, 7982 guid: guid, 7983 hasBgRendering: hasBgRendering, 7984 hasCustomDayCellContent: hasCustomDayCellContent, 7985 hasShrinkWidth: hasShrinkWidth, 7986 identity: identity, 7987 injectStyles: injectStyles, 7988 interactionSettingsStore: interactionSettingsStore, 7989 interactionSettingsToStore: interactionSettingsToStore, 7990 intersectRanges: intersectRanges,
7991 intersectRects: intersectRects, 7992 intersectSpans: intersectSpans, 7993 isArraysEqual: isArraysEqual, 7994 isColPropsEqual: isColPropsEqual, 7995 isDateSelectionValid: isDateSelectionValid, 7996 isDateSpansEqual: isDateSpansEqual, 7997 isInt: isInt, 7998 isInteractionValid: isInteractionValid, 7999 isMultiDayRange: isMultiDayRange, 8000 isPropsEqual: isPropsEqual, 8001 isPropsValid: isPropsValid, 8002 isValidDate: isValidDate, 8003 mapHash: mapHash, 8004 memoize: memoize, 8005 memoizeArraylike: memoizeArraylike, 8006 memoizeHashlike: memoizeHashlike, 8007 memoizeObjArg: memoizeObjArg, 8008 mergeEventStores: mergeEventStores, 8009 multiplyDuration: multiplyDuration, 8010 padStart: padStart, 8011 parseBusinessHours: parseBusinessHours, 8012 parseClassNames: parseClassNames, 8013 parseDragMeta: parseDragMeta, 8014 parseEventDef: parseEventDef, 8015 parseFieldSpecs: parseFieldSpecs, 8016 parseMarker: parse, 8017 pointInsideRect: pointInsideRect, 8018 preventContextMenu: preventContextMenu, 8019 preventDefault: preventDefault, 8020 preventSelection: preventSelection, 8021 rangeContainsMarker: rangeContainsMarker, 8022 rangeContainsRange: rangeContainsRange, 8023 rangesEqual: rangesEqual, 8024 rangesIntersect: rangesIntersect, 8025 refineEventDef: refineEventDef, 8026 refineProps: refineProps, 8027 removeElement: removeElement, 8028 removeExact: removeExact, 8029 renderChunkContent: renderChunkContent, 8030 renderFill: renderFill, 8031 renderMicroColGroup: renderMicroColGroup, 8032 renderScrollShim: renderScrollShim, 8033 requestJson: requestJson, 8034 sanitizeShrinkWidth: sanitizeShrinkWidth, 8035 setRef: setRef, 8036 sliceEventStore: sliceEventStore, 8037 sortEventSegs: sortEventSegs, 8038 startOfDay: startOfDay, 8039 translateRect: translateRect, 8040 triggerDateSelect: triggerDateSelect, 8041 unpromisify: unpromisify, 8042 whenTransitionDone: whenTransitionDone, 8043 wholeDivideDurations: wholeDivideDurations 8044 }; 8045 8046 var css_248z$3 = ":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-unselectable{-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-user-select:none;-moz-user-select:none;user-select:none}.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/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyY
8046iDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button::-moz-focus-inner{border-style:none;padding:0}.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
vendor: 8,355 bytes, line 8046
8046;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}";
vendor: 65,694 bytes, lines 8047-9433
8047 injectStyles(css_248z$3); 8048 8049 const MINIMAL_RAW_EN_LOCALE = { 8050 code: 'en', 8051 week: { 8052 dow: 0, 8053 doy: 4, // 4 days need to be within the year to be considered the first week 8054 }, 8055 direction: 'ltr', 8056 buttonText: { 8057 prev: 'prev', 8058 next: 'next', 8059 prevYear: 'prev year', 8060 nextYear: 'next year', 8061 year: 'year', 8062 today: 'today', 8063 month: 'month', 8064 week: 'week', 8065 day: 'day', 8066 list: 'list', 8067 }, 8068 weekText: 'W', 8069 weekTextLong: 'Week', 8070 closeHint: 'Close', 8071 timeHint: 'Time', 8072 eventHint: 'Event', 8073 allDayText: 'all-day', 8074 moreLinkText: 'more', 8075 noEventsText: 'No events to display', 8076 }; 8077 const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), { 8078 // Includes things we don't want other locales to inherit, 8079 // things that derive from other translatable strings. 8080 buttonHints: { 8081 prev: 'Previous $0', 8082 next: 'Next $0', 8083 today(buttonText, unit) { 8084 return (unit === 'day') 8085 ? 'Today' 8086 : `This ${buttonText}`; 8087 }, 8088 }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) { 8089 return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; 8090 } }); 8091 function organizeRawLocales(explicitRawLocales) { 8092 let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; 8093 let allRawLocales = globalLocales.concat(explicitRawLocales); 8094 let rawLocaleMap = { 8095 en: RAW_EN_LOCALE, 8096 }; 8097 for (let rawLocale of allRawLocales) { 8098 rawLocaleMap[rawLocale.code] = rawLocale; 8099 } 8100 return { 8101 map: rawLocaleMap, 8102 defaultCode, 8103 }; 8104 } 8105 function buildLocale(inputSingular, available) { 8106 if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { 8107 return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); 8108 } 8109 return queryLocale(inputSingular, available); 8110 } 8111 function queryLocale(codeArg, available) { 8112 let codes = [].concat(codeArg || []); // will convert to array 8113 let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; 8114 return parseLocale(codeArg, codes, raw); 8115 } 8116 function queryRawLocale(codes, available) { 8117 for (let i = 0; i < codes.length; i += 1) { 8118 let parts = codes[i].toLocaleLowerCase().split('-'); 8119 for (let j = parts.length; j > 0; j -= 1) { 8120 let simpleId = parts.slice(0, j).join('-'); 8121 if (available[simpleId]) { 8122 return available[simpleId]; 8123 } 8124 } 8125 } 8126 return null; 8127 } 8128 function parseLocale(codeArg, codes, raw) { 8129 let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']); 8130 delete merged.code; // don't want this part of the options 8131 let { week } = merged; 8132 delete merged.week; 8133 return { 8134 codeArg, 8135 codes, 8136 week, 8137 simpleNumberFormat: new Intl.NumberFormat(codeArg), 8138 options: merged, 8139 }; 8140 } 8141 8142 class StandardTheme extends Theme { 8143 } 8144 StandardTheme.prototype.classes = { 8145 root: 'fc-theme-standard', 8146 tableCellShaded: 'fc-cell-shaded', 8147 buttonGroup: 'fc-button-group', 8148 button: 'fc-button fc-button-primary', 8149 buttonActive: 'fc-button-active', 8150 }; 8151 StandardTheme.prototype.baseIconClass = 'fc-icon'; 8152 StandardTheme.prototype.iconClasses = { 8153 close: 'fc-icon-x', 8154 prev: 'fc-icon-chevron-left', 8155 next: 'fc-icon-chevron-right', 8156 prevYear: 'fc-icon-chevrons-left', 8157 nextYear: 'fc-icon-chevrons-right', 8158 }; 8159 StandardTheme.prototype.rtlIconClasses = { 8160 prev: 'fc-icon-chevron-right', 8161 next: 'fc-icon-chevron-left', 8162 prevYear: 'fc-icon-chevrons-right', 8163 nextYear: 'fc-icon-chevrons-left', 8164 }; 8165 StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly 8166 StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; 8167 StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; 8168 8169 function compileViewDefs(defaultConfigs, overrideConfigs) { 8170 let hash = {}; 8171 let viewType; 8172 for (viewType in defaultConfigs) { 8173 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); 8174 } 8175 for (viewType in overrideConfigs) { 8176 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); 8177 } 8178 return hash; 8179 } 8180 function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { 8181 if (hash[viewType]) { 8182 return hash[viewType]; 8183 } 8184 let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); 8185 if (viewDef) { 8186 hash[viewType] = viewDef; 8187 } 8188 return viewDef; 8189 } 8190 function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { 8191 let defaultConfig = defaultConfigs[viewType]; 8192 let overrideConfig = overrideConfigs[viewType]; 8193 let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : 8194 ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); 8195 let theComponent = queryProp('component'); 8196 let superType = queryProp('superType'); 8197 let superDef = null; 8198 if (superType) { 8199 if (superType === viewType) { 8200 throw new Error('Can\'t have a custom view type that references itself'); 8201 } 8202 superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); 8203 } 8204 if (!theComponent && superDef) { 8205 theComponent = superDef.component; 8206 } 8207 if (!theComponent) { 8208 return null; // don't throw a warning, might be settings for a single-unit view 8209 } 8210 return { 8211 type: viewType, 8212 component: theComponent, 8213 defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), 8214 overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), 8215 }; 8216 } 8217 8218 function parseViewConfigs(inputs) { 8219 return mapHash(inputs, parseViewConfig); 8220 } 8221 function parseViewConfig(input) { 8222 let rawOptions = typeof input === 'function' ? 8223 { component: input } : 8224 input; 8225 let { component } = rawOptions; 8226 if (rawOptions.content) { 8227 component = createViewHookComponent(rawOptions); 8228 // TODO: remove content/classNames/didMount/etc from options? 8229 } 8230 return { 8231 superType: rawOptions.type, 8232 component: component, 8233 rawOptions, // includes type and component too :( 8234 }; 8235 } 8236 function createViewHookComponent(options) { 8237 return (viewProps) => (h(ViewContextType.Consumer, null, (context) => (h(ContentContainer, { elTag: "div", elClasses: buildViewClassNames(context.viewSpec), renderProps: Object.assign(Object.assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }), generatorName: undefined, generator: options.content, classNameGenerator: options.classNames, didMount: options.didMount, willUnmount: options.willUnmount })))); 8238 } 8239 8240 function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { 8241 let defaultConfigs = parseViewConfigs(defaultInputs); 8242 let overrideConfigs = parseViewConfigs(optionOverrides.views); 8243 let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); 8244 return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults)); 8245 } 8246 function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { 8247 let durationInput = viewDef.overrides.duration || 8248 viewDef.defaults.duration || 8249 dynamicOptionOverrides.duration || 8250 optionOverrides.duration; 8251 let duration = null; 8252 let durationUnit = ''; 8253 let singleUnit = ''; 8254 let singleUnitOverrides = {}; 8255 if (durationInput) { 8256 duration = createDurationCached(durationInput); 8257 if (duration) { // valid? 8258 let denom = greatestDurationDenominator(duration); 8259 durationUnit = denom.unit; 8260 if (denom.value === 1) { 8261 singleUnit = durationUnit; 8262 singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; 8263 } 8264 } 8265 } 8266 let queryButtonText = (optionsSubset) => { 8267 let buttonTextMap = optionsSubset.buttonText || {}; 8268 let buttonTextKey = viewDef.defaults.buttonTextKey; 8269 if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { 8270 return buttonTextMap[buttonTextKey]; 8271 } 8272 if (buttonTextMap[viewDef.type] != null) { 8273 return buttonTextMap[viewDef.type]; 8274 } 8275 if (buttonTextMap[singleUnit] != null) { 8276 return buttonTextMap[singleUnit]; 8277 } 8278 return null; 8279 }; 8280 let queryButtonTitle = (optionsSubset) => { 8281 let buttonHints = optionsSubset.buttonHints || {}; 8282 let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text 8283 if (buttonKey != null && buttonHints[buttonKey] != null) { 8284 return buttonHints[buttonKey]; 8285 } 8286 if (buttonHints[viewDef.type] != null) { 8287 return buttonHints[viewDef.type]; 8288 } 8289 if (buttonHints[singleUnit] != null) { 8290 return buttonHints[singleUnit]; 8291 } 8292 return null; 8293 }; 8294 return { 8295 type: viewDef.type, 8296 component: viewDef.component, 8297 duration, 8298 durationUnit, 8299 singleUnit, 8300 optionDefaults: viewDef.defaults, 8301 optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides), 8302 buttonTextOverride: queryButtonText(dynamicOptionOverrides) || 8303 queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence 8304 viewDef.overrides.buttonText, 8305 buttonTextDefault: queryButtonText(localeDefaults) || 8306 viewDef.defaults.buttonText || 8307 queryButtonText(BASE_OPTION_DEFAULTS) || 8308 viewDef.type, 8309 // not DRY 8310 buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) || 8311 queryButtonTitle(optionOverrides) || 8312 viewDef.overrides.buttonHint, 8313 buttonTitleDefault: queryButtonTitle(localeDefaults) || 8314 viewDef.defaults.buttonHint || 8315 queryButtonTitle(BASE_OPTION_DEFAULTS), 8316 // will eventually fall back to buttonText 8317 }; 8318 } 8319 // hack to get memoization working 8320 let durationInputMap = {}; 8321 function createDurationCached(durationInput) { 8322 let json = JSON.stringify(durationInput); 8323 let res = durationInputMap[json]; 8324 if (res === undefined) { 8325 res = createDuration(durationInput); 8326 durationInputMap[json] = res; 8327 } 8328 return res; 8329 } 8330 8331 function reduceViewType(viewType, action) { 8332 switch (action.type) { 8333 case 'CHANGE_VIEW_TYPE': 8334 viewType = action.viewType; 8335 } 8336 return viewType; 8337 } 8338 8339 function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { 8340 switch (action.type) { 8341 case 'SET_OPTION': 8342 return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue }); 8343 default: 8344 return dynamicOptionOverrides; 8345 } 8346 } 8347 8348 function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { 8349 let dp; 8350 switch (action.type) { 8351 case 'CHANGE_VIEW_TYPE': 8352 return dateProfileGenerator.build(action.dateMarker || currentDate); 8353 case 'CHANGE_DATE': 8354 return dateProfileGenerator.build(action.dateMarker); 8355 case 'PREV': 8356 dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); 8357 if (dp.isValid) { 8358 return dp; 8359 } 8360 break; 8361 case 'NEXT': 8362 dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); 8363 if (dp.isValid) { 8364 return dp; 8365 } 8366 break; 8367 } 8368 return currentDateProfile; 8369 } 8370 8371 function reduceDateSelection(currentSelection, action) { 8372 switch (action.type) { 8373 case 'UNSELECT_DATES': 8374 return null; 8375 case 'SELECT_DATES': 8376 return action.selection; 8377 default: 8378 return currentSelection; 8379 } 8380 } 8381 8382 function reduceSelectedEvent(currentInstanceId, action) { 8383 switch (action.type) { 8384 case 'UNSELECT_EVENT': 8385 return ''; 8386 case 'SELECT_EVENT': 8387 return action.eventInstanceId; 8388 default: 8389 return currentInstanceId; 8390 } 8391 } 8392 8393 function reduceEventDrag(currentDrag, action) { 8394 let newDrag; 8395 switch (action.type) { 8396 case 'UNSET_EVENT_DRAG': 8397 return null; 8398 case 'SET_EVENT_DRAG': 8399 newDrag = action.state; 8400 return { 8401 affectedEvents: newDrag.affectedEvents, 8402 mutatedEvents: newDrag.mutatedEvents, 8403 isEvent: newDrag.isEvent, 8404 }; 8405 default: 8406 return currentDrag; 8407 } 8408 } 8409 8410 function reduceEventResize(currentResize, action) { 8411 let newResize; 8412 switch (action.type) { 8413 case 'UNSET_EVENT_RESIZE': 8414 return null; 8415 case 'SET_EVENT_RESIZE': 8416 newResize = action.state; 8417 return { 8418 affectedEvents: newResize.affectedEvents, 8419 mutatedEvents: newResize.mutatedEvents, 8420 isEvent: newResize.isEvent, 8421 }; 8422 default: 8423 return currentResize; 8424 } 8425 } 8426 8427 function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { 8428 let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null; 8429 let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null; 8430 return { header, footer }; 8431 } 8432 function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { 8433 let sectionWidgets = {}; 8434 let viewsWithButtons = []; 8435 let hasTitle = false; 8436 for (let sectionName in sectionStrHash) { 8437 let sectionStr = sectionStrHash[sectionName]; 8438 let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi); 8439 sectionWidgets[sectionName] = sectionRes.widgets; 8440 viewsWithButtons.push(...sectionRes.viewsWithButtons); 8441 hasTitle = hasTitle || sectionRes.hasTitle; 8442 } 8443 return { sectionWidgets, viewsWithButtons, hasTitle }; 8444 } 8445 /* 8446 BAD: querying icons and text here. should be done at render time 8447 */ 8448 function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined 8449 calendarOptionOverrides, // overrides only!, unrefined :( 8450 theme, viewSpecs, calendarApi) { 8451 let isRtl = calendarOptions.direction === 'rtl'; 8452 let calendarCustomButtons = calendarOptions.customButtons || {}; 8453 let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; 8454 let calendarButtonText = calendarOptions.buttonText || {}; 8455 let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {}; 8456 let calendarButtonHints = calendarOptions.buttonHints || {}; 8457 let sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; 8458 let viewsWithButtons = []; 8459 let hasTitle = false; 8460 let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => { 8461 if (buttonName === 'title') { 8462 hasTitle = true; 8463 return { buttonName }; 8464 } 8465 let customButtonProps; 8466 let viewSpec; 8467 let buttonClick; 8468 let buttonIcon; // only one of these will be set 8469 let buttonText; // " 8470 let buttonHint; 8471 // ^ for the title="" attribute, for accessibility 8472 if ((customButtonProps = calendarCustomButtons[buttonName])) { 8473 buttonClick = (ev) => { 8474 if (customButtonProps.click) { 8475 customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? 8476 } 8477 }; 8478 (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || 8479 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8480 (buttonText = customButtonProps.text); 8481 buttonHint = customButtonProps.hint || customButtonProps.text; 8482 } 8483 else if ((viewSpec = viewSpecs[buttonName])) { 8484 viewsWithButtons.push(buttonName); 8485 buttonClick = () => { 8486 calendarApi.changeView(buttonName); 8487 }; 8488 (buttonText = viewSpec.buttonTextOverride) || 8489 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8490 (buttonText = viewSpec.buttonTextDefault); 8491 let textFallback = viewSpec.buttonTextOverride || 8492 viewSpec.buttonTextDefault; 8493 buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride || 8494 viewSpec.buttonTitleDefault || 8495 calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName 8496 textFallback); 8497 } 8498 else if (calendarApi[buttonName]) { // a calendarApi method 8499 buttonClick = () => { 8500 calendarApi[buttonName](); 8501 }; 8502 (buttonText = calendarButtonTextOverrides[buttonName]) || 8503 (buttonIcon = theme.getIconClass(buttonName, isRtl)) || 8504 (buttonText = calendarButtonText[buttonName]); // everything else is considered default 8505 if (buttonName === 'prevYear' || buttonName === 'nextYear') { 8506 let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next'; 8507 buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] || 8508 calendarButtonHints[prevOrNext], [ 8509 calendarButtonText.year || 'year', 8510 'year', 8511 ], calendarButtonText[buttonName]); 8512 } 8513 else { 8514 buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] || 8515 calendarButtonHints[buttonName], [ 8516 calendarButtonText[navUnit] || navUnit, 8517 navUnit, 8518 ], calendarButtonText[buttonName]); 8519 } 8520 } 8521 return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint }; 8522 }))); 8523 return { widgets, viewsWithButtons, hasTitle }; 8524 } 8525 8526 // always represents the current view. otherwise, it'd need to change value every time date changes 8527 class ViewImpl { 8528 constructor(type, getCurrentData, dateEnv) { 8529 this.type = type; 8530 this.getCurrentData = getCurrentData; 8531 this.dateEnv = dateEnv; 8532 } 8533 get calendar() { 8534 return this.getCurrentData().calendarApi; 8535 } 8536 get title() { 8537 return this.getCurrentData().viewTitle; 8538 } 8539 get activeStart() { 8540 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); 8541 } 8542 get activeEnd() { 8543 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); 8544 } 8545 get currentStart() { 8546 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); 8547 } 8548 get currentEnd() { 8549 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); 8550 } 8551 getOption(name) { 8552 return this.getCurrentData().options[name]; // are the view-specific options 8553 } 8554 } 8555 8556 class TaskRunner { 8557 constructor(runTaskOption, drainedOption) { 8558 this.runTaskOption = runTaskOption; 8559 this.drainedOption = drainedOption; 8560 this.queue = []; 8561 this.delayedRunner = new DelayedRunner(this.drain.bind(this)); 8562 } 8563 request(task, delay) { 8564 this.queue.push(task); 8565 this.delayedRunner.request(delay); 8566 } 8567 pause(scope) { 8568 this.delayedRunner.pause(scope); 8569 } 8570 resume(scope, force) { 8571 this.delayedRunner.resume(scope, force); 8572 } 8573 drain() { 8574 let { queue } = this; 8575 while (queue.length) { 8576 let completedTasks = []; 8577 let task; 8578 while ((task = queue.shift())) { 8579 this.runTask(task); 8580 completedTasks.push(task); 8581 } 8582 this.drained(completedTasks); 8583 } // keep going, in case new tasks were added in the drained handler 8584 } 8585 runTask(task) { 8586 if (this.runTaskOption) { 8587 this.runTaskOption(task); 8588 } 8589 } 8590 drained(completedTasks) { 8591 if (this.drainedOption) { 8592 this.drainedOption(completedTasks); 8593 } 8594 } 8595 } 8596 8597 // Computes what the title at the top of the calendarApi should be for this view 8598 function buildTitle(dateProfile, viewOptions, dateEnv) { 8599 let range; 8600 // for views that span a large unit of time, show the proper interval, ignoring stray days before and after 8601 if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { 8602 range = dateProfile.currentRange; 8603 } 8604 else { // for day units or smaller, use the actual day range 8605 range = dateProfile.activeRange; 8606 } 8607 return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { 8608 isEndExclusive: dateProfile.isRangeAllDay, 8609 defaultSeparator: viewOptions.titleRangeSeparator, 8610 }); 8611 } 8612 // Generates the format string that should be used to generate the title for the current date range. 8613 // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. 8614 function buildTitleFormat(dateProfile) { 8615 let { currentRangeUnit } = dateProfile; 8616 if (currentRangeUnit === 'year') { 8617 return { year: 'numeric' }; 8618 } 8619 if (currentRangeUnit === 'month') { 8620 return { year: 'numeric', month: 'long' }; // like "September 2014" 8621 } 8622 let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); 8623 if (days !== null && days > 1) { 8624 // multi-day range. shorter, like "Sep 9 - 10 2014" 8625 return { year: 'numeric', month: 'short', day: 'numeric' }; 8626 } 8627 // one day. longer, like "September 9 2014" 8628 return { year: 'numeric', month: 'long', day: 'numeric' }; 8629 } 8630 8631 // in future refactor, do the redux-style function(state=initial) for initial-state 8632 // also, whatever is happening in constructor, have it happen in action queue too 8633 class CalendarDataManager { 8634 constructor(props) { 8635 this.computeOptionsData = memoize(this._computeOptionsData); 8636 this.computeCurrentViewData = memoize(this._computeCurrentViewData); 8637 this.organizeRawLocales = memoize(organizeRawLocales); 8638 this.buildLocale = memoize(buildLocale); 8639 this.buildPluginHooks = buildBuildPluginHooks(); 8640 this.buildDateEnv = memoize(buildDateEnv$1); 8641 this.buildTheme = memoize(buildTheme); 8642 this.parseToolbars = memoize(parseToolbars); 8643 this.buildViewSpecs = memoize(buildViewSpecs); 8644 this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); 8645 this.buildViewApi = memoize(buildViewApi); 8646 this.buildViewUiProps = memoizeObjArg(buildViewUiProps); 8647 this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); 8648 this.buildEventUiBases = memoize(buildEventUiBases); 8649 this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); 8650 this.buildTitle = memoize(buildTitle); 8651 this.emitter = new Emitter(); 8652 this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); 8653 this.currentCalendarOptionsInput = {}; 8654 this.currentCalendarOptionsRefined = {}; 8655 this.currentViewOptionsInput = {}; 8656 this.currentViewOptionsRefined = {}; 8657 this.currentCalendarOptionsRefiners = {}; 8658 this.getCurrentData = () => this.data; 8659 this.dispatch = (action) => { 8660 this.actionRunner.request(action); // protects against recursive calls to _handleAction 8661 }; 8662 this.props = props; 8663 this.actionRunner.pause(); 8664 let dynamicOptionOverrides = {}; 8665 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); 8666 let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; 8667 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); 8668 // wire things up 8669 // TODO: not DRY 8670 props.calendarApi.currentDataManager = this; 8671 this.emitter.setThisContext(props.calendarApi); 8672 this.emitter.setOptions(currentViewData.options); 8673 let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); 8674 let dateProfile = currentViewData.dateProfileGenerator.build(currentDate); 8675 if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { 8676 currentDate = dateProfile.currentRange.start; 8677 } 8678 let calendarContext = { 8679 dateEnv: optionsData.dateEnv, 8680 options: optionsData.calendarOptions, 8681 pluginHooks: optionsData.pluginHooks, 8682 calendarApi: props.calendarApi, 8683 dispatch: this.dispatch, 8684 emitter: this.emitter, 8685 getCurrentData: this.getCurrentData, 8686 }; 8687 // needs to be after setThisContext 8688 for (let callback of optionsData.pluginHooks.contextInit) { 8689 callback(calendarContext); 8690 } 8691 // NOT DRY 8692 let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); 8693 let initialState = { 8694 dynamicOptionOverrides, 8695 currentViewType, 8696 currentDate, 8697 dateProfile, 8698 businessHours: this.parseContextBusinessHours(calendarContext), 8699 eventSources, 8700 eventUiBases: {}, 8701 eventStore: createEmptyEventStore(), 8702 renderableEventStore: createEmptyEventStore(), 8703 dateSelection: null, 8704 eventSelection: '', 8705 eventDrag: null, 8706 eventResize: null, 8707 selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, 8708 }; 8709 let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState); 8710 for (let reducer of optionsData.pluginHooks.reducers) { 8711 Object.assign(initialState, reducer(null, null, contextAndState)); 8712 } 8713 if (computeIsLoading(initialState, calendarContext)) { 8714 this.emitter.trigger('loading', true); // NOT DRY 8715 } 8716 this.state = initialState; 8717 this.updateData(); 8718 this.actionRunner.resume(); 8719 } 8720 resetOptions(optionOverrides, append) { 8721 let { props } = this; 8722 props.optionOverrides = append 8723 ? Object.assign(Object.assign({}, props.optionOverrides), optionOverrides) : optionOverrides; 8724 this.actionRunner.request({ 8725 type: 'NOTHING', 8726 }); 8727 } 8728 _handleAction(action) { 8729 let { props, state, emitter } = this; 8730 let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); 8731 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); 8732 let currentViewType = reduceViewType(state.currentViewType, action); 8733 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); 8734 // wire things up 8735 // TODO: not DRY 8736 props.calendarApi.currentDataManager = this; 8737 emitter.setThisContext(props.calendarApi); 8738 emitter.setOptions(currentViewData.options); 8739 let calendarContext = { 8740 dateEnv: optionsData.dateEnv, 8741 options: optionsData.calendarOptions, 8742 pluginHooks: optionsData.pluginHooks, 8743 calendarApi: props.calendarApi, 8744 dispatch: this.dispatch, 8745 emitter, 8746 getCurrentData: this.getCurrentData, 8747 }; 8748 let { currentDate, dateProfile } = state; 8749 if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack 8750 dateProfile = currentViewData.dateProfileGenerator.build(currentDate); 8751 } 8752 currentDate = reduceCurrentDate(currentDate, action); 8753 dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); 8754 if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator 8755 action.type === 'NEXT' || // " 8756 !rangeContainsMarker(dateProfile.currentRange, currentDate)) { 8757 currentDate = dateProfile.currentRange.start; 8758 } 8759 let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); 8760 let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); 8761 let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading 8762 let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? 8763 (state.renderableEventStore || eventStore) : // try from previous state 8764 eventStore; 8765 let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj 8766 let eventUiBySource = this.buildEventUiBySource(eventSources); 8767 let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); 8768 let newState = { 8769 dynamicOptionOverrides, 8770 currentViewType, 8771 currentDate, 8772 dateProfile, 8773 eventSources, 8774 eventStore, 8775 renderableEventStore, 8776 selectionConfig, 8777 eventUiBases, 8778 businessHours: this.parseContextBusinessHours(calendarContext), 8779 dateSelection: reduceDateSelection(state.dateSelection, action), 8780 eventSelection: reduceSelectedEvent(state.eventSelection, action), 8781 eventDrag: reduceEventDrag(state.eventDrag, action), 8782 eventResize: reduceEventResize(state.eventResize, action), 8783 }; 8784 let contextAndState = Object.assign(Object.assign({}, calendarContext), newState); 8785 for (let reducer of optionsData.pluginHooks.reducers) { 8786 Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value 8787 } 8788 let wasLoading = computeIsLoading(state, calendarContext); 8789 let isLoading = computeIsLoading(newState, calendarContext); 8790 // TODO: use propSetHandlers in plugin system 8791 if (!wasLoading && isLoading) { 8792 emitter.trigger('loading', true); 8793 } 8794 else if (wasLoading && !isLoading) { 8795 emitter.trigger('loading', false); 8796 } 8797 this.state = newState; 8798 if (props.onAction) { 8799 props.onAction(action); 8800 } 8801 } 8802 updateData() { 8803 let { props, state } = this; 8804 let oldData = this.data; 8805 let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); 8806 let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); 8807 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); 8808 let changeHandlers = optionsData.pluginHooks.optionChangeHandlers; 8809 let oldCalendarOptions = oldData && oldData.calendarOptions; 8810 let newCalendarOptions = optionsData.calendarOptions; 8811 if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { 8812 if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { 8813 // hack 8814 state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); 8815 state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); 8816 } 8817 for (let optionName in changeHandlers) { 8818 if (oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { 8819 changeHandlers[optionName](newCalendarOptions[optionName], data); 8820 } 8821 } 8822 } 8823 if (props.onData) { 8824 props.onData(data); 8825 } 8826 } 8827 _computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) { 8828 // TODO: blacklist options that are handled by optionChangeHandlers 8829 let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides); 8830 warnUnknownOptions(extra); 8831 let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); 8832 let viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults); 8833 let theme = this.buildTheme(refinedOptions, pluginHooks); 8834 let toolbarConfig = this.parseToolbars(refinedOptions, optionOverrides, theme, viewSpecs, calendarApi); 8835 return { 8836 calendarOptions: refinedOptions, 8837 pluginHooks, 8838 dateEnv, 8839 viewSpecs, 8840 theme, 8841 toolbarConfig, 8842 localeDefaults, 8843 availableRawLocales: availableLocaleData.map, 8844 }; 8845 } 8846 // always called from behind a memoizer 8847 processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) { 8848 let { locales, locale } = mergeRawOptions([ 8849 BASE_OPTION_DEFAULTS, 8850 optionOverrides, 8851 dynamicOptionOverrides, 8852 ]); 8853 let availableLocaleData = this.organizeRawLocales(locales); 8854 let availableRawLocales = availableLocaleData.map; 8855 let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; 8856 let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); 8857 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); 8858 let extra = {}; 8859 let raw = mergeRawOptions([ 8860 BASE_OPTION_DEFAULTS, 8861 localeDefaults, 8862 optionOverrides, 8863 dynamicOptionOverrides, 8864 ]); 8865 let refined = {}; 8866 let currentRaw = this.currentCalendarOptionsInput; 8867 let currentRefined = this.currentCalendarOptionsRefined; 8868 let anyChanges = false; 8869 for (let optionName in raw) { 8870 if (optionName !== 'plugins') { // because plugins is special-cased 8871 if (raw[optionName] === currentRaw[optionName] || 8872 (COMPLEX_OPTION_COMPARATORS[optionName] && 8873 (optionName in currentRaw) && 8874 COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName]))) { 8875 refined[optionName] = currentRefined[optionName]; 8876 } 8877 else if (refiners[optionName]) { 8878 refined[optionName] = refiners[optionName](raw[optionName]); 8879 anyChanges = true; 8880 } 8881 else { 8882 extra[optionName] = currentRaw[optionName]; 8883 } 8884 } 8885 } 8886 if (anyChanges) { 8887 this.currentCalendarOptionsInput = raw; 8888 this.currentCalendarOptionsRefined = refined; 8889 } 8890 return { 8891 rawOptions: this.currentCalendarOptionsInput, 8892 refinedOptions: this.currentCalendarOptionsRefined, 8893 pluginHooks, 8894 availableLocaleData, 8895 localeDefaults, 8896 extra, 8897 }; 8898 } 8899 _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) { 8900 let viewSpec = optionsData.viewSpecs[viewType]; 8901 if (!viewSpec) { 8902 throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`); 8903 } 8904 let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides); 8905 warnUnknownOptions(extra); 8906 let dateProfileGenerator = this.buildDateProfileGenerator({ 8907 dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, 8908 duration: viewSpec.duration, 8909 durationUnit: viewSpec.durationUnit, 8910 usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, 8911 dateEnv: optionsData.dateEnv, 8912 calendarApi: this.props.calendarApi, 8913 slotMinTime: refinedOptions.slotMinTime, 8914 slotMaxTime: refinedOptions.slotMaxTime, 8915 showNonCurrentDates: refinedOptions.showNonCurrentDates, 8916 dayCount: refinedOptions.dayCount, 8917 dateAlignment: refinedOptions.dateAlignment, 8918 dateIncrement: refinedOptions.dateIncrement, 8919 hiddenDays: refinedOptions.hiddenDays, 8920 weekends: refinedOptions.weekends, 8921 nowInput: refinedOptions.now, 8922 validRangeInput: refinedOptions.validRange, 8923 visibleRangeInput: refinedOptions.visibleRange, 8924 monthMode: refinedOptions.monthMode, 8925 fixedWeekCount: refinedOptions.fixedWeekCount, 8926 }); 8927 let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); 8928 return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi }; 8929 } 8930 processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { 8931 let raw = mergeRawOptions([ 8932 BASE_OPTION_DEFAULTS, 8933 viewSpec.optionDefaults, 8934 localeDefaults, 8935 optionOverrides, 8936 viewSpec.optionOverrides, 8937 dynamicOptionOverrides, 8938 ]); 8939 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); 8940 let refined = {}; 8941 let currentRaw = this.currentViewOptionsInput; 8942 let currentRefined = this.currentViewOptionsRefined; 8943 let anyChanges = false; 8944 let extra = {}; 8945 for (let optionName in raw) { 8946 if (raw[optionName] === currentRaw[optionName] || 8947 (COMPLEX_OPTION_COMPARATORS[optionName] && 8948 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) { 8949 refined[optionName] = currentRefined[optionName]; 8950 } 8951 else { 8952 if (raw[optionName] === this.currentCalendarOptionsInput[optionName] || 8953 (COMPLEX_OPTION_COMPARATORS[optionName] && 8954 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) { 8955 if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop 8956 refined[optionName] = this.currentCalendarOptionsRefined[optionName]; 8957 } 8958 } 8959 else if (refiners[optionName]) { 8960 refined[optionName] = refiners[optionName](raw[optionName]); 8961 } 8962 else { 8963 extra[optionName] = raw[optionName]; 8964 } 8965 anyChanges = true; 8966 } 8967 } 8968 if (anyChanges) { 8969 this.currentViewOptionsInput = raw; 8970 this.currentViewOptionsRefined = refined; 8971 } 8972 return { 8973 rawOptions: this.currentViewOptionsInput, 8974 refinedOptions: this.currentViewOptionsRefined, 8975 extra, 8976 }; 8977 } 8978 } 8979 function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { 8980 let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); 8981 return new DateEnv({ 8982 calendarSystem: 'gregory', 8983 timeZone, 8984 namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, 8985 locale, 8986 weekNumberCalculation, 8987 firstDay, 8988 weekText, 8989 cmdFormatter: pluginHooks.cmdFormatter, 8990 defaultSeparator, 8991 }); 8992 } 8993 function buildTheme(options, pluginHooks) { 8994 let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; 8995 return new ThemeClass(options); 8996 } 8997 function buildDateProfileGenerator(props) { 8998 let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; 8999 return new DateProfileGeneratorClass(props); 9000 } 9001 function buildViewApi(type, getCurrentData, dateEnv) { 9002 return new ViewImpl(type, getCurrentData, dateEnv); 9003 } 9004 function buildEventUiBySource(eventSources) { 9005 return mapHash(eventSources, (eventSource) => eventSource.ui); 9006 } 9007 function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { 9008 let eventUiBases = { '': eventUiSingleBase }; 9009 for (let defId in eventDefs) { 9010 let def = eventDefs[defId]; 9011 if (def.sourceId && eventUiBySource[def.sourceId]) { 9012 eventUiBases[defId] = eventUiBySource[def.sourceId]; 9013 } 9014 } 9015 return eventUiBases; 9016 } 9017 function buildViewUiProps(calendarContext) { 9018 let { options } = calendarContext; 9019 return { 9020 eventUiSingleBase: createEventUi({ 9021 display: options.eventDisplay, 9022 editable: options.editable, 9023 startEditable: options.eventStartEditable, 9024 durationEditable: options.eventDurationEditable, 9025 constraint: options.eventConstraint, 9026 overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, 9027 allow: options.eventAllow, 9028 backgroundColor: options.eventBackgroundColor, 9029 borderColor: options.eventBorderColor, 9030 textColor: options.eventTextColor, 9031 color: options.eventColor, 9032 // classNames: options.eventClassNames // render hook will handle this 9033 }, calendarContext), 9034 selectionConfig: createEventUi({ 9035 constraint: options.selectConstraint, 9036 overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, 9037 allow: options.selectAllow, 9038 }, calendarContext), 9039 }; 9040 } 9041 function computeIsLoading(state, context) { 9042 for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) { 9043 if (isLoadingFunc(state)) { 9044 return true; 9045 } 9046 } 9047 return false; 9048 } 9049 function parseContextBusinessHours(calendarContext) { 9050 return parseBusinessHours(calendarContext.options.businessHours, calendarContext); 9051 } 9052 function warnUnknownOptions(options, viewName) { 9053 for (let optionName in options) { 9054 console.warn(`Unknown option '${optionName}'` + 9055 (viewName ? ` for view '${viewName}'` : '')); 9056 } 9057 } 9058 9059 class ToolbarSection extends BaseComponent { 9060 render() { 9061 let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup)); 9062 return h('div', { className: 'fc-toolbar-chunk' }, ...children); 9063 } 9064 renderWidgetGroup(widgetGroup) { 9065 let { props } = this; 9066 let { theme } = this.context; 9067 let children = []; 9068 let isOnlyButtons = true; 9069 for (let widget of widgetGroup) { 9070 let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget; 9071 if (buttonName === 'title') { 9072 isOnlyButtons = false; 9073 children.push(h("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title)); 9074 } 9075 else { 9076 let isPressed = buttonName === props.activeButton; 9077 let isDisabled = (!props.isTodayEnabled && buttonName === 'today') || 9078 (!props.isPrevEnabled && buttonName === 'prev') || 9079 (!props.isNextEnabled && buttonName === 'next'); 9080 let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')]; 9081 if (isPressed) { 9082 buttonClasses.push(theme.getClass('buttonActive')); 9083 } 9084 children.push(h("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? h("span", { className: buttonIcon }) : ''))); 9085 } 9086 } 9087 if (children.length > 1) { 9088 let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; 9089 return h('div', { className: groupClassName }, ...children); 9090 } 9091 return children[0]; 9092 } 9093 } 9094 9095 class Toolbar extends BaseComponent { 9096 render() { 9097 let { model, extraClassName } = this.props; 9098 let forceLtr = false; 9099 let startContent; 9100 let endContent; 9101 let sectionWidgets = model.sectionWidgets; 9102 let centerContent = sectionWidgets.center; 9103 if (sectionWidgets.left) { 9104 forceLtr = true; 9105 startContent = sectionWidgets.left; 9106 } 9107 else { 9108 startContent = sectionWidgets.start; 9109 } 9110 if (sectionWidgets.right) { 9111 forceLtr = true; 9112 endContent = sectionWidgets.right; 9113 } 9114 else { 9115 endContent = sectionWidgets.end; 9116 } 9117 let classNames = [ 9118 extraClassName || '', 9119 'fc-toolbar', 9120 forceLtr ? 'fc-toolbar-ltr' : '', 9121 ]; 9122 return (h("div", { className: classNames.join(' ') }, 9123 this.renderSection('start', startContent || []), 9124 this.renderSection('center', centerContent || []), 9125 this.renderSection('end', endContent || []))); 9126 } 9127 renderSection(key, widgetGroups) { 9128 let { props } = this; 9129 return (h(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 })); 9130 } 9131 } 9132 9133 // TODO: do function component? 9134 class ViewContainer extends BaseComponent { 9135 constructor() { 9136 super(...arguments); 9137 this.state = { 9138 availableWidth: null, 9139 }; 9140 this.handleEl = (el) => { 9141 this.el = el; 9142 setRef(this.props.elRef, el); 9143 this.updateAvailableWidth(); 9144 }; 9145 this.handleResize = () => { 9146 this.updateAvailableWidth(); 9147 }; 9148 } 9149 render() { 9150 let { props, state } = this; 9151 let { aspectRatio } = props; 9152 let classNames = [ 9153 'fc-view-harness', 9154 (aspectRatio || props.liquid || props.height) 9155 ? 'fc-view-harness-active' // harness controls the height 9156 : 'fc-view-harness-passive', // let the view do the height 9157 ]; 9158 let height = ''; 9159 let paddingBottom = ''; 9160 if (aspectRatio) { 9161 if (state.availableWidth !== null) { 9162 height = state.availableWidth / aspectRatio; 9163 } 9164 else { 9165 // while waiting to know availableWidth, we can't set height to *zero* 9166 // because will cause lots of unnecessary scrollbars within scrollgrid. 9167 // BETTER: don't start rendering ANYTHING yet until we know container width 9168 // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) 9169 paddingBottom = `${(1 / aspectRatio) * 100}%`; 9170 } 9171 } 9172 else { 9173 height = props.height || ''; 9174 } 9175 return (h("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children)); 9176 } 9177 componentDidMount() { 9178 this.context.addResizeHandler(this.handleResize); 9179 } 9180 componentWillUnmount() { 9181 this.context.removeResizeHandler(this.handleResize); 9182 } 9183 updateAvailableWidth() { 9184 if (this.el && // needed. but why? 9185 this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth 9186 ) { 9187 this.setState({ availableWidth: this.el.offsetWidth }); 9188 } 9189 } 9190 } 9191 9192 /* 9193 Detects when the user clicks on an event within a DateComponent 9194 */ 9195 class EventClicking extends Interaction { 9196 constructor(settings) { 9197 super(settings); 9198 this.handleSegClick = (ev, segEl) => { 9199 let { component } = this; 9200 let { context } = component; 9201 let seg = getElSeg(segEl); 9202 if (seg && // might be the <div> surrounding the more link 9203 component.isValidSegDownEl(ev.target)) { 9204 // our way to simulate a link click for elements that can't be <a> tags 9205 // grab before trigger fired in case trigger trashes DOM thru rerendering 9206 let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); 9207 let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; 9208 context.emitter.trigger('eventClick', { 9209 el: segEl, 9210 event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance), 9211 jsEvent: ev, 9212 view: context.viewApi, 9213 }); 9214 if (url && !ev.defaultPrevented) { 9215 window.location.href = url; 9216 } 9217 } 9218 }; 9219 this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events 9220 this.handleSegClick); 9221 } 9222 } 9223 9224 /* 9225 Triggers events and adds/removes core classNames when the user's pointer 9226 enters/leaves event-elements of a component. 9227 */ 9228 class EventHovering extends Interaction { 9229 constructor(settings) { 9230 super(settings); 9231 // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it 9232 this.handleEventElRemove = (el) => { 9233 if (el === this.currentSegEl) { 9234 this.handleSegLeave(null, this.currentSegEl); 9235 } 9236 }; 9237 this.handleSegEnter = (ev, segEl) => { 9238 if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper 9239 this.currentSegEl = segEl; 9240 this.triggerEvent('eventMouseEnter', ev, segEl); 9241 } 9242 }; 9243 this.handleSegLeave = (ev, segEl) => { 9244 if (this.currentSegEl) { 9245 this.currentSegEl = null; 9246 this.triggerEvent('eventMouseLeave', ev, segEl); 9247 } 9248 }; 9249 this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events 9250 this.handleSegEnter, this.handleSegLeave); 9251 } 9252 destroy() { 9253 this.removeHoverListeners(); 9254 } 9255 triggerEvent(publicEvName, ev, segEl) { 9256 let { component } = this; 9257 let { context } = component; 9258 let seg = getElSeg(segEl); 9259 if (!ev || component.isValidSegDownEl(ev.target)) { 9260 context.emitter.trigger(publicEvName, { 9261 el: segEl, 9262 event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance), 9263 jsEvent: ev, 9264 view: context.viewApi, 9265 }); 9266 } 9267 } 9268 } 9269 9270 class CalendarContent extends PureComponent { 9271 constructor() { 9272 super(...arguments); 9273 this.buildViewContext = memoize(buildViewContext); 9274 this.buildViewPropTransformers = memoize(buildViewPropTransformers); 9275 this.buildToolbarProps = memoize(buildToolbarProps); 9276 this.headerRef = y(); 9277 this.footerRef = y(); 9278 this.interactionsStore = {}; 9279 // eslint-disable-next-line 9280 this.state = { 9281 viewLabelId: getUniqueDomId(), 9282 }; 9283 // Component Registration 9284 // ----------------------------------------------------------------------------------------------------------------- 9285 this.registerInteractiveComponent = (component, settingsInput) => { 9286 let settings = parseInteractionSettings(component, settingsInput); 9287 let DEFAULT_INTERACTIONS = [ 9288 EventClicking, 9289 EventHovering, 9290 ]; 9291 let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions); 9292 let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings)); 9293 this.interactionsStore[component.uid] = interactions; 9294 interactionSettingsStore[component.uid] = settings; 9295 }; 9296 this.unregisterInteractiveComponent = (component) => { 9297 let listeners = this.interactionsStore[component.uid]; 9298 if (listeners) { 9299 for (let listener of listeners) { 9300 listener.destroy(); 9301 } 9302 delete this.interactionsStore[component.uid]; 9303 } 9304 delete interactionSettingsStore[component.uid]; 9305 }; 9306 // Resizing 9307 // ----------------------------------------------------------------------------------------------------------------- 9308 this.resizeRunner = new DelayedRunner(() => { 9309 this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? 9310 this.props.emitter.trigger('windowResize', { view: this.props.viewApi }); 9311 }); 9312 this.handleWindowResize = (ev) => { 9313 let { options } = this.props; 9314 if (options.handleWindowResize && 9315 ev.target === window // avoid jqui events 9316 ) { 9317 this.resizeRunner.request(options.windowResizeDelay); 9318 } 9319 }; 9320 } 9321 /* 9322 renders INSIDE of an outer div 9323 */ 9324 render() { 9325 let { props } = this; 9326 let { toolbarConfig, options } = props; 9327 let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? 9328 props.viewTitle); 9329 let viewVGrow = false; 9330 let viewHeight = ''; 9331 let viewAspectRatio; 9332 if (props.isHeightAuto || props.forPrint) { 9333 viewHeight = ''; 9334 } 9335 else if (options.height != null) { 9336 viewVGrow = true; 9337 } 9338 else if (options.contentHeight != null) { 9339 viewHeight = options.contentHeight; 9340 } 9341 else { 9342 viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall 9343 } 9344 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); 9345 let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle) 9346 ? this.state.viewLabelId 9347 : ''; 9348 return (h(ViewContextType.Provider, { value: viewContext }, 9349 toolbarConfig.header && (h(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))), 9350 h(ViewContainer, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId }, 9351 this.renderView(props), 9352 this.buildAppendContent()), 9353 toolbarConfig.footer && (h(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps))))); 9354 } 9355 componentDidMount() { 9356 let { props } = this; 9357 this.calendarInteractions = props.pluginHooks.calendarInteractions 9358 .map((CalendarInteractionClass) => new CalendarInteractionClass(props)); 9359 window.addEventListener('resize', this.handleWindowResize); 9360 let { propSetHandlers } = props.pluginHooks; 9361 for (let propName in propSetHandlers) { 9362 propSetHandlers[propName](props[propName], props); 9363 } 9364 } 9365 componentDidUpdate(prevProps) { 9366 let { props } = this; 9367 let { propSetHandlers } = props.pluginHooks; 9368 for (let propName in propSetHandlers) { 9369 if (props[propName] !== prevProps[propName]) { 9370 propSetHandlers[propName](props[propName], props); 9371 } 9372 } 9373 } 9374 componentWillUnmount() { 9375 window.removeEventListener('resize', this.handleWindowResize); 9376 this.resizeRunner.clear(); 9377 for (let interaction of this.calendarInteractions) { 9378 interaction.destroy(); 9379 } 9380 this.props.emitter.trigger('_unmount'); 9381 } 9382 buildAppendContent() { 9383 let { props } = this; 9384 let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props)); 9385 return h(p, {}, ...children); 9386 } 9387 renderView(props) { 9388 let { pluginHooks } = props; 9389 let { viewSpec } = props; 9390 let viewProps = { 9391 dateProfile: props.dateProfile, 9392 businessHours: props.businessHours, 9393 eventStore: props.renderableEventStore, 9394 eventUiBases: props.eventUiBases, 9395 dateSelection: props.dateSelection, 9396 eventSelection: props.eventSelection, 9397 eventDrag: props.eventDrag, 9398 eventResize: props.eventResize, 9399 isHeightAuto: props.isHeightAuto, 9400 forPrint: props.forPrint, 9401 }; 9402 let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); 9403 for (let transformer of transformers) { 9404 Object.assign(viewProps, transformer.transform(viewProps, props)); 9405 } 9406 let ViewComponent = viewSpec.component; 9407 return (h(ViewComponent, Object.assign({}, viewProps))); 9408 } 9409 } 9410 function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { 9411 // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid 9412 let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason 9413 let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); 9414 let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); 9415 return { 9416 title, 9417 activeButton: viewSpec.type, 9418 navUnit: viewSpec.singleUnit, 9419 isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), 9420 isPrevEnabled: prevInfo.isValid, 9421 isNextEnabled: nextInfo.isValid, 9422 }; 9423 } 9424 // Plugin 9425 // ----------------------------------------------------------------------------------------------------------------- 9426 function buildViewPropTransformers(theClasses) { 9427 return theClasses.map((TheClass) => new TheClass()); 9428 } 9429 9430 class Calendar extends CalendarImpl { 9431 constructor(el, optionOverrides = {}) { 9432 super(); 9433 this.isRendering = false;
9434 this.isRendered = false; 9435 this.currentClassNames = []; 9436 this.customContentRenderId = 0; 9437 this.handleAction = (action) => { 9438 // actions we know we want to render immediately 9439 switch (action.type) { 9440 case 'SET_EVENT_DRAG': 9441 case 'SET_EVENT_RESIZE': 9442 this.renderRunner.tryDrain(); 9443 } 9444 }; 9445 this.handleData = (data) => { 9446 this.currentData = data; 9447 this.renderRunner.request(data.calendarOptions.rerenderDelay); 9448 }; 9449 this.handleRenderRequest = () => { 9450 if (this.isRendering) { 9451 this.isRendered = true; 9452 let { currentData } = this; 9453 flushSync(() => { 9454 P$1(h(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => { 9455 this.setClassNames(classNames); 9456 this.setHeight(height); 9457 return (h(RenderId.Provider, { value: this.customContentRenderId }, 9458 h(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData)))); 9459 }), this.el); 9460 }); 9461 } 9462 else if (this.isRendered) { 9463 this.isRendered = false; 9464 P$1(null, this.el); 9465 this.setClassNames([]); 9466 this.setHeight(''); 9467 } 9468 }; 9469 this.el = el; 9470 this.renderRunner = new DelayedRunner(this.handleRenderRequest); 9471 new CalendarDataManager({ 9472 optionOverrides, 9473 calendarApi: this, 9474 onAction: this.handleAction, 9475 onData: this.handleData, 9476 }); 9477 } 9478 render() { 9479 let wasRendering = this.isRendering; 9480 if (!wasRendering) { 9481 this.isRendering = true; 9482 } 9483 else { 9484 this.customContentRenderId += 1; 9485 } 9486 this.renderRunner.request(); 9487 if (wasRendering) { 9488 this.updateSize(); 9489 } 9490 } 9491 destroy() { 9492 if (this.isRendering) { 9493 this.isRendering = false; 9494 this.renderRunner.request(); 9495 } 9496 } 9497 updateSize() { 9498 flushSync(() => { 9499 super.updateSize(); 9500 }); 9501 } 9502 batchRendering(func) { 9503 this.renderRunner.pause('batchRendering'); 9504 func(); 9505 this.renderRunner.resume('batchRendering'); 9506 } 9507 pauseRendering() { 9508 this.renderRunner.pause('pauseRendering'); 9509 } 9510 resumeRendering() { 9511 this.renderRunner.resume('pauseRendering', true); 9512 } 9513 resetOptions(optionOverrides, append) { 9514 this.currentDataManager.resetOptions(optionOverrides, append); 9515 } 9516 setClassNames(classNames) { 9517 if (!isArraysEqual(classNames, this.currentClassNames)) { 9518 let { classList } = this.el; 9519 for (let className of this.currentClassNames) { 9520 classList.remove(className); 9521 } 9522 for (let className of classNames) { 9523 classList.add(className); 9524 } 9525 this.currentClassNames = classNames; 9526 } 9527 } 9528 setHeight(height) { 9529 applyStyleProp(this.el, 'height', height); 9530 } 9531 } 9532 9533 function formatDate(dateInput, options = {}) { 9534 let dateEnv = buildDateEnv(options); 9535 let formatter = createFormatter(options); 9536 let dateMeta = dateEnv.createMarkerMeta(dateInput); 9537 if (!dateMeta) { // TODO: warning? 9538 return ''; 9539 } 9540 return dateEnv.format(dateMeta.marker, formatter, { 9541 forcedTzo: dateMeta.forcedTzo, 9542 }); 9543 } 9544 function formatRange(startInput, endInput, options) { 9545 let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object 9546 let formatter = createFormatter(options); 9547 let startMeta = dateEnv.createMarkerMeta(startInput); 9548 let endMeta = dateEnv.createMarkerMeta(endInput); 9549 if (!startMeta || !endMeta) { // TODO: warning? 9550 return ''; 9551 } 9552 return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { 9553 forcedStartTzo: startMeta.forcedTzo, 9554 forcedEndTzo: endMeta.forcedTzo, 9555 isEndExclusive: options.isEndExclusive, 9556 defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, 9557 }); 9558 } 9559 // TODO: more DRY and optimized 9560 function buildDateEnv(settings) { 9561 let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere 9562 return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale })); 9563 } 9564 9565 // HELPERS 9566 /* 9567 if nextDayThreshold is specified, slicing is done in an all-day fashion. 9568 you can get nextDayThreshold from context.nextDayThreshold 9569 */ 9570 function sliceEvents(props, allDay) { 9571 return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; 9572 } 9573 9574 const version = '6.0.0'; 9575 9576 config.touchMouseIgnoreWait = 500; 9577 let ignoreMouseDepth = 0; 9578 let listenerCnt = 0; 9579 let isWindowTouchMoveCancelled = false;
vendor: 4,736 bytes, lines 9580-9667
9580 /* 9581 Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. 9582 Tracks when the pointer "drags" on a certain element, meaning down+move+up. 9583 9584 Also, tracks if there was touch-scrolling. 9585 Also, can prevent touch-scrolling from happening. 9586 Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. 9587 9588 emits: 9589 - pointerdown 9590 - pointermove 9591 - pointerup 9592 */ 9593 class PointerDragging { 9594 constructor(containerEl) { 9595 this.subjectEl = null; 9596 // options that can be directly assigned by caller 9597 this.selector = ''; // will cause subjectEl in all emitted events to be this element 9598 this.handleSelector = ''; 9599 this.shouldIgnoreMove = false; 9600 this.shouldWatchScroll = true; // for simulating pointermove on scroll 9601 // internal states 9602 this.isDragging = false; 9603 this.isTouchDragging = false; 9604 this.wasTouchScroll = false; 9605 // Mouse 9606 // ---------------------------------------------------------------------------------------------------- 9607 this.handleMouseDown = (ev) => { 9608 if (!this.shouldIgnoreMouse() && 9609 isPrimaryMouseButton(ev) && 9610 this.tryStart(ev)) { 9611 let pev = this.createEventFromMouse(ev, true); 9612 this.emitter.trigger('pointerdown', pev); 9613 this.initScrollWatch(pev); 9614 if (!this.shouldIgnoreMove) { 9615 document.addEventListener('mousemove', this.handleMouseMove); 9616 } 9617 document.addEventListener('mouseup', this.handleMouseUp); 9618 } 9619 }; 9620 this.handleMouseMove = (ev) => { 9621 let pev = this.createEventFromMouse(ev); 9622 this.recordCoords(pev); 9623 this.emitter.trigger('pointermove', pev); 9624 }; 9625 this.handleMouseUp = (ev) => { 9626 document.removeEventListener('mousemove', this.handleMouseMove); 9627 document.removeEventListener('mouseup', this.handleMouseUp); 9628 this.emitter.trigger('pointerup', this.createEventFromMouse(ev)); 9629 this.cleanup(); // call last so that pointerup has access to props 9630 }; 9631 // Touch 9632 // ---------------------------------------------------------------------------------------------------- 9633 this.handleTouchStart = (ev) => { 9634 if (this.tryStart(ev)) { 9635 this.isTouchDragging = true; 9636 let pev = this.createEventFromTouch(ev, true); 9637 this.emitter.trigger('pointerdown', pev); 9638 this.initScrollWatch(pev); 9639 // unlike mouse, need to attach to target, not document 9640 // https://stackoverflow.com/a/45760014 9641 let targetEl = ev.target; 9642 if (!this.shouldIgnoreMove) { 9643 targetEl.addEventListener('touchmove', this.handleTouchMove); 9644 } 9645 targetEl.addEventListener('touchend', this.handleTouchEnd); 9646 targetEl.addEventListener('touchcancel', this.handleTouchEnd); // treat it as a touch end 9647 // attach a handler to get called when ANY scroll action happens on the page. 9648 // this was impossible to do with normal on/off because 'scroll' doesn't bubble. 9649 // http://stackoverflow.com/a/32954565/96342 9650 window.addEventListener('scroll', this.handleTouchScroll, true); 9651 } 9652 }; 9653 this.handleTouchMove = (ev) => { 9654 let pev = this.createEventFromTouch(ev); 9655 this.recordCoords(pev); 9656 this.emitter.trigger('pointermove', pev); 9657 }; 9658 this.handleTouchEnd = (ev) => { 9659 if (this.isDragging) { // done to guard against touchend followed by touchcancel 9660 let targetEl = ev.target; 9661 targetEl.removeEventListener('touchmove', this.handleTouchMove); 9662 targetEl.removeEventListener('touchend', this.handleTouchEnd); 9663 targetEl.removeEventListener('touchcancel', this.handleTouchEnd); 9664 window.removeEventListener('scroll', this.handleTouchScroll, true); // useCaptured=true 9665 this.emitter.trigger('pointerup', this.createEventFromTouch(ev)); 9666 this.cleanup(); // call last so that pointerup has access to props 9667 this.isTouchDragging = false;
vendor: 16,810 bytes, lines 9668-10100
9668 startIgnoringMouse(); 9669 } 9670 }; 9671 this.handleTouchScroll = () => { 9672 this.wasTouchScroll = true; 9673 }; 9674 this.handleScroll = (ev) => { 9675 if (!this.shouldIgnoreMove) { 9676 let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX; 9677 let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY; 9678 this.emitter.trigger('pointermove', { 9679 origEvent: ev, 9680 isTouch: this.isTouchDragging, 9681 subjectEl: this.subjectEl, 9682 pageX, 9683 pageY, 9684 deltaX: pageX - this.origPageX, 9685 deltaY: pageY - this.origPageY, 9686 }); 9687 } 9688 }; 9689 this.containerEl = containerEl; 9690 this.emitter = new Emitter(); 9691 containerEl.addEventListener('mousedown', this.handleMouseDown); 9692 containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true }); 9693 listenerCreated(); 9694 } 9695 destroy() { 9696 this.containerEl.removeEventListener('mousedown', this.handleMouseDown); 9697 this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); 9698 listenerDestroyed(); 9699 } 9700 tryStart(ev) { 9701 let subjectEl = this.querySubjectEl(ev); 9702 let downEl = ev.target; 9703 if (subjectEl && 9704 (!this.handleSelector || elementClosest(downEl, this.handleSelector))) { 9705 this.subjectEl = subjectEl; 9706 this.isDragging = true; // do this first so cancelTouchScroll will work 9707 this.wasTouchScroll = false; 9708 return true; 9709 } 9710 return false; 9711 } 9712 cleanup() { 9713 isWindowTouchMoveCancelled = false; 9714 this.isDragging = false; 9715 this.subjectEl = null; 9716 // keep wasTouchScroll around for later access 9717 this.destroyScrollWatch(); 9718 } 9719 querySubjectEl(ev) { 9720 if (this.selector) { 9721 return elementClosest(ev.target, this.selector); 9722 } 9723 return this.containerEl; 9724 } 9725 shouldIgnoreMouse() { 9726 return ignoreMouseDepth || this.isTouchDragging; 9727 } 9728 // can be called by user of this class, to cancel touch-based scrolling for the current drag 9729 cancelTouchScroll() { 9730 if (this.isDragging) { 9731 isWindowTouchMoveCancelled = true; 9732 } 9733 } 9734 // Scrolling that simulates pointermoves 9735 // ---------------------------------------------------------------------------------------------------- 9736 initScrollWatch(ev) { 9737 if (this.shouldWatchScroll) { 9738 this.recordCoords(ev); 9739 window.addEventListener('scroll', this.handleScroll, true); // useCapture=true 9740 } 9741 } 9742 recordCoords(ev) { 9743 if (this.shouldWatchScroll) { 9744 this.prevPageX = ev.pageX; 9745 this.prevPageY = ev.pageY; 9746 this.prevScrollX = window.pageXOffset; 9747 this.prevScrollY = window.pageYOffset; 9748 } 9749 } 9750 destroyScrollWatch() { 9751 if (this.shouldWatchScroll) { 9752 window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true 9753 } 9754 } 9755 // Event Normalization 9756 // ---------------------------------------------------------------------------------------------------- 9757 createEventFromMouse(ev, isFirst) { 9758 let deltaX = 0; 9759 let deltaY = 0; 9760 // TODO: repeat code 9761 if (isFirst) { 9762 this.origPageX = ev.pageX; 9763 this.origPageY = ev.pageY; 9764 } 9765 else { 9766 deltaX = ev.pageX - this.origPageX; 9767 deltaY = ev.pageY - this.origPageY; 9768 } 9769 return { 9770 origEvent: ev, 9771 isTouch: false, 9772 subjectEl: this.subjectEl, 9773 pageX: ev.pageX, 9774 pageY: ev.pageY, 9775 deltaX, 9776 deltaY, 9777 }; 9778 } 9779 createEventFromTouch(ev, isFirst) { 9780 let touches = ev.touches; 9781 let pageX; 9782 let pageY; 9783 let deltaX = 0; 9784 let deltaY = 0; 9785 // if touch coords available, prefer, 9786 // because FF would give bad ev.pageX ev.pageY 9787 if (touches && touches.length) { 9788 pageX = touches[0].pageX; 9789 pageY = touches[0].pageY; 9790 } 9791 else { 9792 pageX = ev.pageX; 9793 pageY = ev.pageY; 9794 } 9795 // TODO: repeat code 9796 if (isFirst) { 9797 this.origPageX = pageX; 9798 this.origPageY = pageY; 9799 } 9800 else { 9801 deltaX = pageX - this.origPageX; 9802 deltaY = pageY - this.origPageY; 9803 } 9804 return { 9805 origEvent: ev, 9806 isTouch: true, 9807 subjectEl: this.subjectEl, 9808 pageX, 9809 pageY, 9810 deltaX, 9811 deltaY, 9812 }; 9813 } 9814 } 9815 // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) 9816 function isPrimaryMouseButton(ev) { 9817 return ev.button === 0 && !ev.ctrlKey; 9818 } 9819 // Ignoring fake mouse events generated by touch 9820 // ---------------------------------------------------------------------------------------------------- 9821 function startIgnoringMouse() { 9822 ignoreMouseDepth += 1; 9823 setTimeout(() => { 9824 ignoreMouseDepth -= 1; 9825 }, config.touchMouseIgnoreWait); 9826 } 9827 // We want to attach touchmove as early as possible for Safari 9828 // ---------------------------------------------------------------------------------------------------- 9829 function listenerCreated() { 9830 listenerCnt += 1; 9831 if (listenerCnt === 1) { 9832 window.addEventListener('touchmove', onWindowTouchMove, { passive: false }); 9833 } 9834 } 9835 function listenerDestroyed() { 9836 listenerCnt -= 1; 9837 if (!listenerCnt) { 9838 window.removeEventListener('touchmove', onWindowTouchMove, { passive: false }); 9839 } 9840 } 9841 function onWindowTouchMove(ev) { 9842 if (isWindowTouchMoveCancelled) { 9843 ev.preventDefault(); 9844 } 9845 } 9846 9847 /* 9848 An effect in which an element follows the movement of a pointer across the screen. 9849 The moving element is a clone of some other element. 9850 Must call start + handleMove + stop. 9851 */ 9852 class ElementMirror { 9853 constructor() { 9854 this.isVisible = false; // must be explicitly enabled 9855 this.sourceEl = null; 9856 this.mirrorEl = null; 9857 this.sourceElRect = null; // screen coords relative to viewport 9858 // options that can be set directly by caller 9859 this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues 9860 this.zIndex = 9999; 9861 this.revertDuration = 0; 9862 } 9863 start(sourceEl, pageX, pageY) { 9864 this.sourceEl = sourceEl; 9865 this.sourceElRect = this.sourceEl.getBoundingClientRect(); 9866 this.origScreenX = pageX - window.pageXOffset; 9867 this.origScreenY = pageY - window.pageYOffset; 9868 this.deltaX = 0; 9869 this.deltaY = 0; 9870 this.updateElPosition(); 9871 } 9872 handleMove(pageX, pageY) { 9873 this.deltaX = (pageX - window.pageXOffset) - this.origScreenX; 9874 this.deltaY = (pageY - window.pageYOffset) - this.origScreenY; 9875 this.updateElPosition(); 9876 } 9877 // can be called before start 9878 setIsVisible(bool) { 9879 if (bool) { 9880 if (!this.isVisible) { 9881 if (this.mirrorEl) { 9882 this.mirrorEl.style.display = ''; 9883 } 9884 this.isVisible = bool; // needs to happen before updateElPosition 9885 this.updateElPosition(); // because was not updating the position while invisible 9886 } 9887 } 9888 else if (this.isVisible) { 9889 if (this.mirrorEl) { 9890 this.mirrorEl.style.display = 'none'; 9891 } 9892 this.isVisible = bool; 9893 } 9894 } 9895 // always async 9896 stop(needsRevertAnimation, callback) { 9897 let done = () => { 9898 this.cleanup(); 9899 callback(); 9900 }; 9901 if (needsRevertAnimation && 9902 this.mirrorEl && 9903 this.isVisible && 9904 this.revertDuration && // if 0, transition won't work 9905 (this.deltaX || this.deltaY) // if same coords, transition won't work 9906 ) { 9907 this.doRevertAnimation(done, this.revertDuration); 9908 } 9909 else { 9910 setTimeout(done, 0); 9911 } 9912 } 9913 doRevertAnimation(callback, revertDuration) { 9914 let mirrorEl = this.mirrorEl; 9915 let finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened 9916 mirrorEl.style.transition = 9917 'top ' + revertDuration + 'ms,' + 9918 'left ' + revertDuration + 'ms'; 9919 applyStyle(mirrorEl, { 9920 left: finalSourceElRect.left, 9921 top: finalSourceElRect.top, 9922 }); 9923 whenTransitionDone(mirrorEl, () => { 9924 mirrorEl.style.transition = ''; 9925 callback(); 9926 }); 9927 } 9928 cleanup() { 9929 if (this.mirrorEl) { 9930 removeElement(this.mirrorEl); 9931 this.mirrorEl = null; 9932 } 9933 this.sourceEl = null; 9934 } 9935 updateElPosition() { 9936 if (this.sourceEl && this.isVisible) { 9937 applyStyle(this.getMirrorEl(), { 9938 left: this.sourceElRect.left + this.deltaX, 9939 top: this.sourceElRect.top + this.deltaY, 9940 }); 9941 } 9942 } 9943 getMirrorEl() { 9944 let sourceElRect = this.sourceElRect; 9945 let mirrorEl = this.mirrorEl; 9946 if (!mirrorEl) { 9947 mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true 9948 // we don't want long taps or any mouse interaction causing selection/menus. 9949 // would use preventSelection(), but that prevents selectstart, causing problems. 9950 mirrorEl.classList.add('fc-unselectable'); 9951 mirrorEl.classList.add('fc-event-dragging'); 9952 applyStyle(mirrorEl, { 9953 position: 'fixed', 9954 zIndex: this.zIndex, 9955 visibility: '', 9956 boxSizing: 'border-box', 9957 width: sourceElRect.right - sourceElRect.left, 9958 height: sourceElRect.bottom - sourceElRect.top, 9959 right: 'auto', 9960 bottom: 'auto', 9961 margin: 0, 9962 }); 9963 this.parentNode.appendChild(mirrorEl); 9964 } 9965 return mirrorEl; 9966 } 9967 } 9968 9969 /* 9970 Is a cache for a given element's scroll information (all the info that ScrollController stores) 9971 in addition the "client rectangle" of the element.. the area within the scrollbars. 9972 9973 The cache can be in one of two modes: 9974 - doesListening:false - ignores when the container is scrolled by someone else 9975 - doesListening:true - watch for scrolling and update the cache 9976 */ 9977 class ScrollGeomCache extends ScrollController { 9978 constructor(scrollController, doesListening) { 9979 super(); 9980 this.handleScroll = () => { 9981 this.scrollTop = this.scrollController.getScrollTop(); 9982 this.scrollLeft = this.scrollController.getScrollLeft(); 9983 this.handleScrollChange(); 9984 }; 9985 this.scrollController = scrollController; 9986 this.doesListening = doesListening; 9987 this.scrollTop = this.origScrollTop = scrollController.getScrollTop(); 9988 this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft(); 9989 this.scrollWidth = scrollController.getScrollWidth(); 9990 this.scrollHeight = scrollController.getScrollHeight(); 9991 this.clientWidth = scrollController.getClientWidth(); 9992 this.clientHeight = scrollController.getClientHeight(); 9993 this.clientRect = this.computeClientRect(); // do last in case it needs cached values 9994 if (this.doesListening) { 9995 this.getEventTarget().addEventListener('scroll', this.handleScroll); 9996 } 9997 } 9998 destroy() { 9999 if (this.doesListening) { 10000 this.getEventTarget().removeEventListener('scroll', this.handleScroll); 10001 } 10002 } 10003 getScrollTop() { 10004 return this.scrollTop; 10005 } 10006 getScrollLeft() { 10007 return this.scrollLeft; 10008 } 10009 setScrollTop(top) { 10010 this.scrollController.setScrollTop(top); 10011 if (!this.doesListening) { 10012 // we are not relying on the element to normalize out-of-bounds scroll values 10013 // so we need to sanitize ourselves 10014 this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0); 10015 this.handleScrollChange(); 10016 } 10017 } 10018 setScrollLeft(top) { 10019 this.scrollController.setScrollLeft(top); 10020 if (!this.doesListening) { 10021 // we are not relying on the element to normalize out-of-bounds scroll values 10022 // so we need to sanitize ourselves 10023 this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0); 10024 this.handleScrollChange(); 10025 } 10026 } 10027 getClientWidth() { 10028 return this.clientWidth; 10029 } 10030 getClientHeight() { 10031 return this.clientHeight; 10032 } 10033 getScrollWidth() { 10034 return this.scrollWidth; 10035 } 10036 getScrollHeight() { 10037 return this.scrollHeight; 10038 } 10039 handleScrollChange() { 10040 } 10041 } 10042 10043 class ElementScrollGeomCache extends ScrollGeomCache { 10044 constructor(el, doesListening) { 10045 super(new ElementScrollController(el), doesListening); 10046 } 10047 getEventTarget() { 10048 return this.scrollController.el; 10049 } 10050 computeClientRect() { 10051 return computeInnerRect(this.scrollController.el); 10052 } 10053 } 10054 10055 class WindowScrollGeomCache extends ScrollGeomCache { 10056 constructor(doesListening) { 10057 super(new WindowScrollController(), doesListening); 10058 } 10059 getEventTarget() { 10060 return window; 10061 } 10062 computeClientRect() { 10063 return { 10064 left: this.scrollLeft, 10065 right: this.scrollLeft + this.clientWidth, 10066 top: this.scrollTop, 10067 bottom: this.scrollTop + this.clientHeight, 10068 }; 10069 } 10070 // the window is the only scroll object that changes it's rectangle relative 10071 // to the document's topleft as it scrolls 10072 handleScrollChange() { 10073 this.clientRect = this.computeClientRect(); 10074 } 10075 } 10076 10077 // If available we are using native "performance" API instead of "Date" 10078 // Read more about it on MDN: 10079 // https://developer.mozilla.org/en-US/docs/Web/API/Performance 10080 const getTime = typeof performance === 'function' ? performance.now : Date.now; 10081 /* 10082 For a pointer interaction, automatically scrolls certain scroll containers when the pointer 10083 approaches the edge. 10084 10085 The caller must call start + handleMove + stop. 10086 */ 10087 class AutoScroller { 10088 constructor() { 10089 // options that can be set by caller 10090 this.isEnabled = true; 10091 this.scrollQuery = [window, '.fc-scroller']; 10092 this.edgeThreshold = 50; // pixels 10093 this.maxVelocity = 300; // pixels per second 10094 // internal state 10095 this.pointerScreenX = null; 10096 this.pointerScreenY = null; 10097 this.isAnimating = false; 10098 this.scrollCaches = null; 10099 // protect against the initial pointerdown being too close to an edge and starting the scroll 10100 this.everMovedUp = false;
vendor: 6,695 bytes, lines 10101-10252
10101 this.everMovedDown = false; 10102 this.everMovedLeft = false; 10103 this.everMovedRight = false; 10104 this.animate = () => { 10105 if (this.isAnimating) { // wasn't cancelled between animation calls 10106 let edge = this.computeBestEdge(this.pointerScreenX + window.pageXOffset, this.pointerScreenY + window.pageYOffset); 10107 if (edge) { 10108 let now = getTime(); 10109 this.handleSide(edge, (now - this.msSinceRequest) / 1000); 10110 this.requestAnimation(now); 10111 } 10112 else { 10113 this.isAnimating = false; // will stop animation 10114 } 10115 } 10116 }; 10117 } 10118 start(pageX, pageY, scrollStartEl) { 10119 if (this.isEnabled) { 10120 this.scrollCaches = this.buildCaches(scrollStartEl); 10121 this.pointerScreenX = null; 10122 this.pointerScreenY = null; 10123 this.everMovedUp = false; 10124 this.everMovedDown = false; 10125 this.everMovedLeft = false; 10126 this.everMovedRight = false; 10127 this.handleMove(pageX, pageY); 10128 } 10129 } 10130 handleMove(pageX, pageY) { 10131 if (this.isEnabled) { 10132 let pointerScreenX = pageX - window.pageXOffset; 10133 let pointerScreenY = pageY - window.pageYOffset; 10134 let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY; 10135 let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX; 10136 if (yDelta < 0) { 10137 this.everMovedUp = true; 10138 } 10139 else if (yDelta > 0) { 10140 this.everMovedDown = true; 10141 } 10142 if (xDelta < 0) { 10143 this.everMovedLeft = true; 10144 } 10145 else if (xDelta > 0) { 10146 this.everMovedRight = true; 10147 } 10148 this.pointerScreenX = pointerScreenX; 10149 this.pointerScreenY = pointerScreenY; 10150 if (!this.isAnimating) { 10151 this.isAnimating = true; 10152 this.requestAnimation(getTime()); 10153 } 10154 } 10155 } 10156 stop() { 10157 if (this.isEnabled) { 10158 this.isAnimating = false; // will stop animation 10159 for (let scrollCache of this.scrollCaches) { 10160 scrollCache.destroy(); 10161 } 10162 this.scrollCaches = null; 10163 } 10164 } 10165 requestAnimation(now) { 10166 this.msSinceRequest = now; 10167 requestAnimationFrame(this.animate); 10168 } 10169 handleSide(edge, seconds) { 10170 let { scrollCache } = edge; 10171 let { edgeThreshold } = this; 10172 let invDistance = edgeThreshold - edge.distance; 10173 let velocity = // the closer to the edge, the faster we scroll 10174 ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic 10175 this.maxVelocity * seconds; 10176 let sign = 1; 10177 switch (edge.name) { 10178 case 'left': 10179 sign = -1; 10180 // falls through 10181 case 'right': 10182 scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); 10183 break; 10184 case 'top': 10185 sign = -1; 10186 // falls through 10187 case 'bottom': 10188 scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); 10189 break; 10190 } 10191 } 10192 // left/top are relative to document topleft 10193 computeBestEdge(left, top) { 10194 let { edgeThreshold } = this; 10195 let bestSide = null; 10196 let scrollCaches = this.scrollCaches || []; 10197 for (let scrollCache of scrollCaches) { 10198 let rect = scrollCache.clientRect; 10199 let leftDist = left - rect.left; 10200 let rightDist = rect.right - left; 10201 let topDist = top - rect.top; 10202 let bottomDist = rect.bottom - top; 10203 // completely within the rect? 10204 if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { 10205 if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && 10206 (!bestSide || bestSide.distance > topDist)) { 10207 bestSide = { scrollCache, name: 'top', distance: topDist }; 10208 } 10209 if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && 10210 (!bestSide || bestSide.distance > bottomDist)) { 10211 bestSide = { scrollCache, name: 'bottom', distance: bottomDist }; 10212 } 10213 if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && 10214 (!bestSide || bestSide.distance > leftDist)) { 10215 bestSide = { scrollCache, name: 'left', distance: leftDist }; 10216 } 10217 if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && 10218 (!bestSide || bestSide.distance > rightDist)) { 10219 bestSide = { scrollCache, name: 'right', distance: rightDist }; 10220 } 10221 } 10222 } 10223 return bestSide; 10224 } 10225 buildCaches(scrollStartEl) { 10226 return this.queryScrollEls(scrollStartEl).map((el) => { 10227 if (el === window) { 10228 return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls 10229 } 10230 return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls 10231 }); 10232 } 10233 queryScrollEls(scrollStartEl) { 10234 let els = []; 10235 for (let query of this.scrollQuery) { 10236 if (typeof query === 'object') { 10237 els.push(query); 10238 } 10239 else { 10240 els.push(...Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query))); 10241 } 10242 } 10243 return els; 10244 } 10245 } 10246 10247 /* 10248 Monitors dragging on an element. Has a number of high-level features: 10249 - minimum distance required before dragging 10250 - minimum wait time ("delay") before dragging 10251 - a mirror element that follows the pointer 10252 */
vendor: 8,987 bytes, lines 10253-10448
10253 class FeaturefulElementDragging extends ElementDragging { 10254 constructor(containerEl, selector) { 10255 super(containerEl); 10256 this.containerEl = containerEl; 10257 // options that can be directly set by caller 10258 // the caller can also set the PointerDragging's options as well 10259 this.delay = null; 10260 this.minDistance = 0; 10261 this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag 10262 this.mirrorNeedsRevert = false; 10263 this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup 10264 this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation 10265 this.isDelayEnded = false; 10266 this.isDistanceSurpassed = false; 10267 this.delayTimeoutId = null; 10268 this.onPointerDown = (ev) => { 10269 if (!this.isDragging) { // so new drag doesn't happen while revert animation is going 10270 this.isInteracting = true; 10271 this.isDelayEnded = false; 10272 this.isDistanceSurpassed = false; 10273 preventSelection(document.body); 10274 preventContextMenu(document.body); 10275 // prevent links from being visited if there's an eventual drag. 10276 // also prevents selection in older browsers (maybe?). 10277 // not necessary for touch, besides, browser would complain about passiveness. 10278 if (!ev.isTouch) { 10279 ev.origEvent.preventDefault(); 10280 } 10281 this.emitter.trigger('pointerdown', ev); 10282 if (this.isInteracting && // not destroyed via pointerdown handler 10283 !this.pointer.shouldIgnoreMove) { 10284 // actions related to initiating dragstart+dragmove+dragend... 10285 this.mirror.setIsVisible(false); // reset. caller must set-visible 10286 this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down 10287 this.startDelay(ev); 10288 if (!this.minDistance) { 10289 this.handleDistanceSurpassed(ev); 10290 } 10291 } 10292 } 10293 }; 10294 this.onPointerMove = (ev) => { 10295 if (this.isInteracting) { 10296 this.emitter.trigger('pointermove', ev); 10297 if (!this.isDistanceSurpassed) { 10298 let minDistance = this.minDistance; 10299 let distanceSq; // current distance from the origin, squared 10300 let { deltaX, deltaY } = ev; 10301 distanceSq = deltaX * deltaX + deltaY * deltaY; 10302 if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem 10303 this.handleDistanceSurpassed(ev); 10304 } 10305 } 10306 if (this.isDragging) { 10307 // a real pointer move? (not one simulated by scrolling) 10308 if (ev.origEvent.type !== 'scroll') { 10309 this.mirror.handleMove(ev.pageX, ev.pageY); 10310 this.autoScroller.handleMove(ev.pageX, ev.pageY); 10311 } 10312 this.emitter.trigger('dragmove', ev); 10313 } 10314 } 10315 }; 10316 this.onPointerUp = (ev) => { 10317 if (this.isInteracting) { 10318 this.isInteracting = false; 10319 allowSelection(document.body); 10320 allowContextMenu(document.body); 10321 this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert 10322 if (this.isDragging) { 10323 this.autoScroller.stop(); 10324 this.tryStopDrag(ev); // which will stop the mirror 10325 } 10326 if (this.delayTimeoutId) { 10327 clearTimeout(this.delayTimeoutId); 10328 this.delayTimeoutId = null; 10329 } 10330 } 10331 }; 10332 let pointer = this.pointer = new PointerDragging(containerEl); 10333 pointer.emitter.on('pointerdown', this.onPointerDown); 10334 pointer.emitter.on('pointermove', this.onPointerMove); 10335 pointer.emitter.on('pointerup', this.onPointerUp); 10336 if (selector) { 10337 pointer.selector = selector; 10338 } 10339 this.mirror = new ElementMirror(); 10340 this.autoScroller = new AutoScroller(); 10341 } 10342 destroy() { 10343 this.pointer.destroy(); 10344 // HACK: simulate a pointer-up to end the current drag 10345 // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) 10346 this.onPointerUp({}); 10347 } 10348 startDelay(ev) { 10349 if (typeof this.delay === 'number') { 10350 this.delayTimeoutId = setTimeout(() => { 10351 this.delayTimeoutId = null; 10352 this.handleDelayEnd(ev); 10353 }, this.delay); // not assignable to number! 10354 } 10355 else { 10356 this.handleDelayEnd(ev); 10357 } 10358 } 10359 handleDelayEnd(ev) { 10360 this.isDelayEnded = true; 10361 this.tryStartDrag(ev); 10362 } 10363 handleDistanceSurpassed(ev) { 10364 this.isDistanceSurpassed = true; 10365 this.tryStartDrag(ev); 10366 } 10367 tryStartDrag(ev) { 10368 if (this.isDelayEnded && this.isDistanceSurpassed) { 10369 if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { 10370 this.isDragging = true; 10371 this.mirrorNeedsRevert = false; 10372 this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl); 10373 this.emitter.trigger('dragstart', ev); 10374 if (this.touchScrollAllowed === false) { 10375 this.pointer.cancelTouchScroll(); 10376 } 10377 } 10378 } 10379 } 10380 tryStopDrag(ev) { 10381 // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events 10382 // that come from the document to fire beforehand. much more convenient this way. 10383 this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev)); 10384 } 10385 stopDrag(ev) { 10386 this.isDragging = false; 10387 this.emitter.trigger('dragend', ev); 10388 } 10389 // fill in the implementations... 10390 setIgnoreMove(bool) { 10391 this.pointer.shouldIgnoreMove = bool; 10392 } 10393 setMirrorIsVisible(bool) { 10394 this.mirror.setIsVisible(bool); 10395 } 10396 setMirrorNeedsRevert(bool) { 10397 this.mirrorNeedsRevert = bool; 10398 } 10399 setAutoScrollEnabled(bool) { 10400 this.autoScroller.isEnabled = bool; 10401 } 10402 } 10403 10404 /* 10405 When this class is instantiated, it records the offset of an element (relative to the document topleft), 10406 and continues to monitor scrolling, updating the cached coordinates if it needs to. 10407 Does not access the DOM after instantiation, so highly performant. 10408 10409 Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element 10410 and an determine if a given point is inside the combined clipping rectangle. 10411 */ 10412 class OffsetTracker { 10413 constructor(el) { 10414 this.origRect = computeRect(el); 10415 // will work fine for divs that have overflow:hidden 10416 this.scrollCaches = getClippingParents(el).map((scrollEl) => new ElementScrollGeomCache(scrollEl, true)); 10417 } 10418 destroy() { 10419 for (let scrollCache of this.scrollCaches) { 10420 scrollCache.destroy(); 10421 } 10422 } 10423 computeLeft() { 10424 let left = this.origRect.left; 10425 for (let scrollCache of this.scrollCaches) { 10426 left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); 10427 } 10428 return left; 10429 } 10430 computeTop() { 10431 let top = this.origRect.top; 10432 for (let scrollCache of this.scrollCaches) { 10433 top += scrollCache.origScrollTop - scrollCache.getScrollTop(); 10434 } 10435 return top; 10436 } 10437 isWithinClipping(pageX, pageY) { 10438 let point = { left: pageX, top: pageY }; 10439 for (let scrollCache of this.scrollCaches) { 10440 if (!isIgnoredClipping(scrollCache.getEventTarget()) && 10441 !pointInsideRect(point, scrollCache.clientRect)) { 10442 return false; 10443 } 10444 } 10445 return true; 10446 } 10447 } 10448 // certain clipping containers should never constrain interactions, like <html> and <body>
10449 // https://github.com/fullcalendar/fullcalendar/issues/3615 10450 function isIgnoredClipping(node) { 10451 let tagName = node.tagName; 10452 return tagName === 'HTML' || tagName === 'BODY'; 10453 } 10454 10455 /* 10456 Tracks movement over multiple droppable areas (aka "hits") 10457 that exist in one or more DateComponents. 10458 Relies on an existing draggable. 10459 10460 emits: 10461 - pointerdown 10462 - dragstart 10463 - hitchange - fires initially, even if not over a hit 10464 - pointerup 10465 - (hitchange - again, to null, if ended over a hit) 10466 - dragend 10467 */ 10468 class HitDragging { 10469 constructor(dragging, droppableStore) { 10470 // options that can be set by caller 10471 this.useSubjectCenter = false; 10472 this.requireInitial = true; // if doesn't start out on a hit, won't emit any events 10473 this.initialHit = null; 10474 this.movingHit = null; 10475 this.finalHit = null; // won't ever be populated if shouldIgnoreMove 10476 this.handlePointerDown = (ev) => { 10477 let { dragging } = this; 10478 this.initialHit = null; 10479 this.movingHit = null; 10480 this.finalHit = null; 10481 this.prepareHits(); 10482 this.processFirstCoord(ev); 10483 if (this.initialHit || !this.requireInitial) { 10484 dragging.setIgnoreMove(false);
vendor: 52,336 bytes, lines 10485-11548
10485 // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( 10486 this.emitter.trigger('pointerdown', ev); 10487 } 10488 else { 10489 dragging.setIgnoreMove(true); 10490 } 10491 }; 10492 this.handleDragStart = (ev) => { 10493 this.emitter.trigger('dragstart', ev); 10494 this.handleMove(ev, true); // force = fire even if initially null 10495 }; 10496 this.handleDragMove = (ev) => { 10497 this.emitter.trigger('dragmove', ev); 10498 this.handleMove(ev); 10499 }; 10500 this.handlePointerUp = (ev) => { 10501 this.releaseHits(); 10502 this.emitter.trigger('pointerup', ev); 10503 }; 10504 this.handleDragEnd = (ev) => { 10505 if (this.movingHit) { 10506 this.emitter.trigger('hitupdate', null, true, ev); 10507 } 10508 this.finalHit = this.movingHit; 10509 this.movingHit = null; 10510 this.emitter.trigger('dragend', ev); 10511 }; 10512 this.droppableStore = droppableStore; 10513 dragging.emitter.on('pointerdown', this.handlePointerDown); 10514 dragging.emitter.on('dragstart', this.handleDragStart); 10515 dragging.emitter.on('dragmove', this.handleDragMove); 10516 dragging.emitter.on('pointerup', this.handlePointerUp); 10517 dragging.emitter.on('dragend', this.handleDragEnd); 10518 this.dragging = dragging; 10519 this.emitter = new Emitter(); 10520 } 10521 // sets initialHit 10522 // sets coordAdjust 10523 processFirstCoord(ev) { 10524 let origPoint = { left: ev.pageX, top: ev.pageY }; 10525 let adjustedPoint = origPoint; 10526 let subjectEl = ev.subjectEl; 10527 let subjectRect; 10528 if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot 10529 subjectRect = computeRect(subjectEl); 10530 adjustedPoint = constrainPoint(adjustedPoint, subjectRect); 10531 } 10532 let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); 10533 if (initialHit) { 10534 if (this.useSubjectCenter && subjectRect) { 10535 let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect); 10536 if (slicedSubjectRect) { 10537 adjustedPoint = getRectCenter(slicedSubjectRect); 10538 } 10539 } 10540 this.coordAdjust = diffPoints(adjustedPoint, origPoint); 10541 } 10542 else { 10543 this.coordAdjust = { left: 0, top: 0 }; 10544 } 10545 } 10546 handleMove(ev, forceHandle) { 10547 let hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); 10548 if (forceHandle || !isHitsEqual(this.movingHit, hit)) { 10549 this.movingHit = hit; 10550 this.emitter.trigger('hitupdate', hit, false, ev); 10551 } 10552 } 10553 prepareHits() { 10554 this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { 10555 interactionSettings.component.prepareHits(); 10556 return new OffsetTracker(interactionSettings.el); 10557 }); 10558 } 10559 releaseHits() { 10560 let { offsetTrackers } = this; 10561 for (let id in offsetTrackers) { 10562 offsetTrackers[id].destroy(); 10563 } 10564 this.offsetTrackers = {}; 10565 } 10566 queryHitForOffset(offsetLeft, offsetTop) { 10567 let { droppableStore, offsetTrackers } = this; 10568 let bestHit = null; 10569 for (let id in droppableStore) { 10570 let component = droppableStore[id].component; 10571 let offsetTracker = offsetTrackers[id]; 10572 if (offsetTracker && // wasn't destroyed mid-drag 10573 offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { 10574 let originLeft = offsetTracker.computeLeft(); 10575 let originTop = offsetTracker.computeTop(); 10576 let positionLeft = offsetLeft - originLeft; 10577 let positionTop = offsetTop - originTop; 10578 let { origRect } = offsetTracker; 10579 let width = origRect.right - origRect.left; 10580 let height = origRect.bottom - origRect.top; 10581 if ( 10582 // must be within the element's bounds 10583 positionLeft >= 0 && positionLeft < width && 10584 positionTop >= 0 && positionTop < height) { 10585 let hit = component.queryHit(positionLeft, positionTop, width, height); 10586 if (hit && ( 10587 // make sure the hit is within activeRange, meaning it's not a dead cell 10588 rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) && 10589 (!bestHit || hit.layer > bestHit.layer)) { 10590 hit.componentId = id; 10591 hit.context = component.context; 10592 // TODO: better way to re-orient rectangle 10593 hit.rect.left += originLeft; 10594 hit.rect.right += originLeft; 10595 hit.rect.top += originTop; 10596 hit.rect.bottom += originTop; 10597 bestHit = hit; 10598 } 10599 } 10600 } 10601 } 10602 return bestHit; 10603 } 10604 } 10605 function isHitsEqual(hit0, hit1) { 10606 if (!hit0 && !hit1) { 10607 return true; 10608 } 10609 if (Boolean(hit0) !== Boolean(hit1)) { 10610 return false; 10611 } 10612 return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); 10613 } 10614 10615 function buildDatePointApiWithContext(dateSpan, context) { 10616 let props = {}; 10617 for (let transform of context.pluginHooks.datePointTransforms) { 10618 Object.assign(props, transform(dateSpan, context)); 10619 } 10620 Object.assign(props, buildDatePointApi(dateSpan, context.dateEnv)); 10621 return props; 10622 } 10623 function buildDatePointApi(span, dateEnv) { 10624 return { 10625 date: dateEnv.toDate(span.range.start), 10626 dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), 10627 allDay: span.allDay, 10628 }; 10629 } 10630 10631 /* 10632 Monitors when the user clicks on a specific date/time of a component. 10633 A pointerdown+pointerup on the same "hit" constitutes a click. 10634 */ 10635 class DateClicking extends Interaction { 10636 constructor(settings) { 10637 super(settings); 10638 this.handlePointerDown = (pev) => { 10639 let { dragging } = this; 10640 let downEl = pev.origEvent.target; 10641 // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired 10642 dragging.setIgnoreMove(!this.component.isValidDateDownEl(downEl)); 10643 }; 10644 // won't even fire if moving was ignored 10645 this.handleDragEnd = (ev) => { 10646 let { component } = this; 10647 let { pointer } = this.dragging; 10648 if (!pointer.wasTouchScroll) { 10649 let { initialHit, finalHit } = this.hitDragging; 10650 if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { 10651 let { context } = component; 10652 let arg = Object.assign(Object.assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view }); 10653 context.emitter.trigger('dateClick', arg); 10654 } 10655 } 10656 }; 10657 // we DO want to watch pointer moves because otherwise finalHit won't get populated 10658 this.dragging = new FeaturefulElementDragging(settings.el); 10659 this.dragging.autoScroller.isEnabled = false; 10660 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 10661 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 10662 hitDragging.emitter.on('dragend', this.handleDragEnd); 10663 } 10664 destroy() { 10665 this.dragging.destroy(); 10666 } 10667 } 10668 10669 /* 10670 Tracks when the user selects a portion of time of a component, 10671 constituted by a drag over date cells, with a possible delay at the beginning of the drag. 10672 */ 10673 class DateSelecting extends Interaction { 10674 constructor(settings) { 10675 super(settings); 10676 this.dragSelection = null; 10677 this.handlePointerDown = (ev) => { 10678 let { component, dragging } = this; 10679 let { options } = component.context; 10680 let canSelect = options.selectable && 10681 component.isValidDateDownEl(ev.origEvent.target); 10682 // don't bother to watch expensive moves if component won't do selection 10683 dragging.setIgnoreMove(!canSelect); 10684 // if touch, require user to hold down 10685 dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null; 10686 }; 10687 this.handleDragStart = (ev) => { 10688 this.component.context.calendarApi.unselect(ev); // unselect previous selections 10689 }; 10690 this.handleHitUpdate = (hit, isFinal) => { 10691 let { context } = this.component; 10692 let dragSelection = null; 10693 let isInvalid = false; 10694 if (hit) { 10695 let initialHit = this.hitDragging.initialHit; 10696 let disallowed = hit.componentId === initialHit.componentId 10697 && this.isHitComboAllowed 10698 && !this.isHitComboAllowed(initialHit, hit); 10699 if (!disallowed) { 10700 dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers); 10701 } 10702 if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { 10703 isInvalid = true; 10704 dragSelection = null; 10705 } 10706 } 10707 if (dragSelection) { 10708 context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); 10709 } 10710 else if (!isFinal) { // only unselect if moved away while dragging 10711 context.dispatch({ type: 'UNSELECT_DATES' }); 10712 } 10713 if (!isInvalid) { 10714 enableCursor(); 10715 } 10716 else { 10717 disableCursor(); 10718 } 10719 if (!isFinal) { 10720 this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging 10721 } 10722 }; 10723 this.handlePointerUp = (pev) => { 10724 if (this.dragSelection) { 10725 // selection is already rendered, so just need to report selection 10726 triggerDateSelect(this.dragSelection, pev, this.component.context); 10727 this.dragSelection = null; 10728 } 10729 }; 10730 let { component } = settings; 10731 let { options } = component.context; 10732 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 10733 dragging.touchScrollAllowed = false; 10734 dragging.minDistance = options.selectMinDistance || 0; 10735 dragging.autoScroller.isEnabled = options.dragScroll; 10736 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 10737 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 10738 hitDragging.emitter.on('dragstart', this.handleDragStart); 10739 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 10740 hitDragging.emitter.on('pointerup', this.handlePointerUp); 10741 } 10742 destroy() { 10743 this.dragging.destroy(); 10744 } 10745 } 10746 function getComponentTouchDelay$1(component) { 10747 let { options } = component.context; 10748 let delay = options.selectLongPressDelay; 10749 if (delay == null) { 10750 delay = options.longPressDelay; 10751 } 10752 return delay; 10753 } 10754 function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { 10755 let dateSpan0 = hit0.dateSpan; 10756 let dateSpan1 = hit1.dateSpan; 10757 let ms = [ 10758 dateSpan0.range.start, 10759 dateSpan0.range.end, 10760 dateSpan1.range.start, 10761 dateSpan1.range.end, 10762 ]; 10763 ms.sort(compareNumbers); 10764 let props = {}; 10765 for (let transformer of dateSelectionTransformers) { 10766 let res = transformer(hit0, hit1); 10767 if (res === false) { 10768 return null; 10769 } 10770 if (res) { 10771 Object.assign(props, res); 10772 } 10773 } 10774 props.range = { start: ms[0], end: ms[3] }; 10775 props.allDay = dateSpan0.allDay; 10776 return props; 10777 } 10778 10779 class EventDragging extends Interaction { 10780 constructor(settings) { 10781 super(settings); 10782 // internal state 10783 this.subjectEl = null; 10784 this.subjectSeg = null; // the seg being selected/dragged 10785 this.isDragging = false; 10786 this.eventRange = null; 10787 this.relevantEvents = null; // the events being dragged 10788 this.receivingContext = null; 10789 this.validMutation = null; 10790 this.mutatedRelevantEvents = null; 10791 this.handlePointerDown = (ev) => { 10792 let origTarget = ev.origEvent.target; 10793 let { component, dragging } = this; 10794 let { mirror } = dragging; 10795 let { options } = component.context; 10796 let initialContext = component.context; 10797 this.subjectEl = ev.subjectEl; 10798 let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl); 10799 let eventRange = this.eventRange = subjectSeg.eventRange; 10800 let eventInstanceId = eventRange.instance.instanceId; 10801 this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId); 10802 dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance; 10803 dragging.delay = 10804 // only do a touch delay if touch and this event hasn't been selected yet 10805 (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? 10806 getComponentTouchDelay(component) : 10807 null; 10808 if (options.fixedMirrorParent) { 10809 mirror.parentNode = options.fixedMirrorParent; 10810 } 10811 else { 10812 mirror.parentNode = elementClosest(origTarget, '.fc'); 10813 } 10814 mirror.revertDuration = options.dragRevertDuration; 10815 let isValid = component.isValidSegDownEl(origTarget) && 10816 !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer 10817 dragging.setIgnoreMove(!isValid); 10818 // disable dragging for elements that are resizable (ie, selectable) 10819 // but are not draggable 10820 this.isDragging = isValid && 10821 ev.subjectEl.classList.contains('fc-event-draggable'); 10822 }; 10823 this.handleDragStart = (ev) => { 10824 let initialContext = this.component.context; 10825 let eventRange = this.eventRange; 10826 let eventInstanceId = eventRange.instance.instanceId; 10827 if (ev.isTouch) { 10828 // need to select a different event? 10829 if (eventInstanceId !== this.component.props.eventSelection) { 10830 initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }); 10831 } 10832 } 10833 else { 10834 // if now using mouse, but was previous touch interaction, clear selected event 10835 initialContext.dispatch({ type: 'UNSELECT_EVENT' }); 10836 } 10837 if (this.isDragging) { 10838 initialContext.calendarApi.unselect(ev); // unselect *date* selection 10839 initialContext.emitter.trigger('eventDragStart', { 10840 el: this.subjectEl, 10841 event: new EventImpl(initialContext, eventRange.def, eventRange.instance), 10842 jsEvent: ev.origEvent, 10843 view: initialContext.viewApi, 10844 }); 10845 } 10846 }; 10847 this.handleHitUpdate = (hit, isFinal) => { 10848 if (!this.isDragging) { 10849 return; 10850 } 10851 let relevantEvents = this.relevantEvents; 10852 let initialHit = this.hitDragging.initialHit; 10853 let initialContext = this.component.context; 10854 // states based on new hit 10855 let receivingContext = null; 10856 let mutation = null; 10857 let mutatedRelevantEvents = null; 10858 let isInvalid = false; 10859 let interaction = { 10860 affectedEvents: relevantEvents, 10861 mutatedEvents: createEmptyEventStore(), 10862 isEvent: true, 10863 }; 10864 if (hit) { 10865 receivingContext = hit.context; 10866 let receivingOptions = receivingContext.options; 10867 if (initialContext === receivingContext || 10868 (receivingOptions.editable && receivingOptions.droppable)) { 10869 mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers); 10870 if (mutation) { 10871 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext); 10872 interaction.mutatedEvents = mutatedRelevantEvents; 10873 if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { 10874 isInvalid = true; 10875 mutation = null; 10876 mutatedRelevantEvents = null; 10877 interaction.mutatedEvents = createEmptyEventStore(); 10878 } 10879 } 10880 } 10881 else { 10882 receivingContext = null; 10883 } 10884 } 10885 this.displayDrag(receivingContext, interaction); 10886 if (!isInvalid) { 10887 enableCursor(); 10888 } 10889 else { 10890 disableCursor(); 10891 } 10892 if (!isFinal) { 10893 if (initialContext === receivingContext && // TODO: write test for this 10894 isHitsEqual(initialHit, hit)) { 10895 mutation = null; 10896 } 10897 this.dragging.setMirrorNeedsRevert(!mutation); 10898 // render the mirror if no already-rendered mirror 10899 // TODO: wish we could somehow wait for dispatch to guarantee render 10900 this.dragging.setMirrorIsVisible(!hit || !getElRoot(this.subjectEl).querySelector('.fc-event-mirror')); 10901 // assign states based on new hit 10902 this.receivingContext = receivingContext; 10903 this.validMutation = mutation; 10904 this.mutatedRelevantEvents = mutatedRelevantEvents; 10905 } 10906 }; 10907 this.handlePointerUp = () => { 10908 if (!this.isDragging) { 10909 this.cleanup(); // because handleDragEnd won't fire 10910 } 10911 }; 10912 this.handleDragEnd = (ev) => { 10913 if (this.isDragging) { 10914 let initialContext = this.component.context; 10915 let initialView = initialContext.viewApi; 10916 let { receivingContext, validMutation } = this; 10917 let eventDef = this.eventRange.def; 10918 let eventInstance = this.eventRange.instance; 10919 let eventApi = new EventImpl(initialContext, eventDef, eventInstance); 10920 let relevantEvents = this.relevantEvents; 10921 let mutatedRelevantEvents = this.mutatedRelevantEvents; 10922 let { finalHit } = this.hitDragging; 10923 this.clearDrag(); // must happen after revert animation 10924 initialContext.emitter.trigger('eventDragStop', { 10925 el: this.subjectEl, 10926 event: eventApi, 10927 jsEvent: ev.origEvent, 10928 view: initialView, 10929 }); 10930 if (validMutation) { 10931 // dropped within same calendar 10932 if (receivingContext === initialContext) { 10933 let updatedEventApi = new EventImpl(initialContext, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); 10934 initialContext.dispatch({ 10935 type: 'MERGE_EVENTS', 10936 eventStore: mutatedRelevantEvents, 10937 }); 10938 let eventChangeArg = { 10939 oldEvent: eventApi, 10940 event: updatedEventApi, 10941 relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), 10942 revert() { 10943 initialContext.dispatch({ 10944 type: 'MERGE_EVENTS', 10945 eventStore: relevantEvents, // the pre-change data 10946 }); 10947 }, 10948 }; 10949 let transformed = {}; 10950 for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { 10951 Object.assign(transformed, transformer(validMutation, initialContext)); 10952 } 10953 initialContext.emitter.trigger('eventDrop', Object.assign(Object.assign(Object.assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView })); 10954 initialContext.emitter.trigger('eventChange', eventChangeArg); 10955 // dropped in different calendar 10956 } 10957 else if (receivingContext) { 10958 let eventRemoveArg = { 10959 event: eventApi, 10960 relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), 10961 revert() { 10962 initialContext.dispatch({ 10963 type: 'MERGE_EVENTS', 10964 eventStore: relevantEvents, 10965 }); 10966 }, 10967 }; 10968 initialContext.emitter.trigger('eventLeave', Object.assign(Object.assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView })); 10969 initialContext.dispatch({ 10970 type: 'REMOVE_EVENTS', 10971 eventStore: relevantEvents, 10972 }); 10973 initialContext.emitter.trigger('eventRemove', eventRemoveArg); 10974 let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId]; 10975 let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId]; 10976 let addedEventApi = new EventImpl(receivingContext, addedEventDef, addedEventInstance); 10977 receivingContext.dispatch({ 10978 type: 'MERGE_EVENTS', 10979 eventStore: mutatedRelevantEvents, 10980 }); 10981 let eventAddArg = { 10982 event: addedEventApi, 10983 relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), 10984 revert() { 10985 receivingContext.dispatch({ 10986 type: 'REMOVE_EVENTS', 10987 eventStore: mutatedRelevantEvents, 10988 }); 10989 }, 10990 }; 10991 receivingContext.emitter.trigger('eventAdd', eventAddArg); 10992 if (ev.isTouch) { 10993 receivingContext.dispatch({ 10994 type: 'SELECT_EVENT', 10995 eventInstanceId: eventInstance.instanceId, 10996 }); 10997 } 10998 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi })); 10999 receivingContext.emitter.trigger('eventReceive', Object.assign(Object.assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi })); 11000 } 11001 } 11002 else { 11003 initialContext.emitter.trigger('_noEventDrop'); 11004 } 11005 } 11006 this.cleanup(); 11007 }; 11008 let { component } = this; 11009 let { options } = component.context; 11010 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 11011 dragging.pointer.selector = EventDragging.SELECTOR; 11012 dragging.touchScrollAllowed = false; 11013 dragging.autoScroller.isEnabled = options.dragScroll; 11014 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore); 11015 hitDragging.useSubjectCenter = settings.useEventCenter; 11016 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 11017 hitDragging.emitter.on('dragstart', this.handleDragStart); 11018 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11019 hitDragging.emitter.on('pointerup', this.handlePointerUp); 11020 hitDragging.emitter.on('dragend', this.handleDragEnd); 11021 } 11022 destroy() { 11023 this.dragging.destroy(); 11024 } 11025 // render a drag state on the next receivingCalendar 11026 displayDrag(nextContext, state) { 11027 let initialContext = this.component.context; 11028 let prevContext = this.receivingContext; 11029 // does the previous calendar need to be cleared? 11030 if (prevContext && prevContext !== nextContext) { 11031 // does the initial calendar need to be cleared? 11032 // if so, don't clear all the way. we still need to to hide the affectedEvents 11033 if (prevContext === initialContext) { 11034 prevContext.dispatch({ 11035 type: 'SET_EVENT_DRAG', 11036 state: { 11037 affectedEvents: state.affectedEvents, 11038 mutatedEvents: createEmptyEventStore(), 11039 isEvent: true, 11040 }, 11041 }); 11042 // completely clear the old calendar if it wasn't the initial 11043 } 11044 else { 11045 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11046 } 11047 } 11048 if (nextContext) { 11049 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); 11050 } 11051 } 11052 clearDrag() { 11053 let initialCalendar = this.component.context; 11054 let { receivingContext } = this; 11055 if (receivingContext) { 11056 receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11057 } 11058 // the initial calendar might have an dummy drag state from displayDrag 11059 if (initialCalendar !== receivingContext) { 11060 initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11061 } 11062 } 11063 cleanup() { 11064 this.subjectSeg = null; 11065 this.isDragging = false; 11066 this.eventRange = null; 11067 this.relevantEvents = null; 11068 this.receivingContext = null; 11069 this.validMutation = null; 11070 this.mutatedRelevantEvents = null; 11071 } 11072 } 11073 // TODO: test this in IE11 11074 // QUESTION: why do we need it on the resizable??? 11075 EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable'; 11076 function computeEventMutation(hit0, hit1, massagers) { 11077 let dateSpan0 = hit0.dateSpan; 11078 let dateSpan1 = hit1.dateSpan; 11079 let date0 = dateSpan0.range.start; 11080 let date1 = dateSpan1.range.start; 11081 let standardProps = {}; 11082 if (dateSpan0.allDay !== dateSpan1.allDay) { 11083 standardProps.allDay = dateSpan1.allDay; 11084 standardProps.hasEnd = hit1.context.options.allDayMaintainDuration; 11085 if (dateSpan1.allDay) { 11086 // means date1 is already start-of-day, 11087 // but date0 needs to be converted 11088 date0 = startOfDay(date0); 11089 } 11090 } 11091 let delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ? 11092 hit0.largeUnit : 11093 null); 11094 if (delta.milliseconds) { // has hours/minutes/seconds 11095 standardProps.allDay = false; 11096 } 11097 let mutation = { 11098 datesDelta: delta, 11099 standardProps, 11100 }; 11101 for (let massager of massagers) { 11102 massager(mutation, hit0, hit1); 11103 } 11104 return mutation; 11105 } 11106 function getComponentTouchDelay(component) { 11107 let { options } = component.context; 11108 let delay = options.eventLongPressDelay; 11109 if (delay == null) { 11110 delay = options.longPressDelay; 11111 } 11112 return delay; 11113 } 11114 11115 class EventResizing extends Interaction { 11116 constructor(settings) { 11117 super(settings); 11118 // internal state 11119 this.draggingSegEl = null; 11120 this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg? 11121 this.eventRange = null; 11122 this.relevantEvents = null; 11123 this.validMutation = null; 11124 this.mutatedRelevantEvents = null; 11125 this.handlePointerDown = (ev) => { 11126 let { component } = this; 11127 let segEl = this.querySegEl(ev); 11128 let seg = getElSeg(segEl); 11129 let eventRange = this.eventRange = seg.eventRange; 11130 this.dragging.minDistance = component.context.options.eventDragMinDistance; 11131 // if touch, need to be working with a selected event 11132 this.dragging.setIgnoreMove(!this.component.isValidSegDownEl(ev.origEvent.target) || 11133 (ev.isTouch && this.component.props.eventSelection !== eventRange.instance.instanceId)); 11134 }; 11135 this.handleDragStart = (ev) => { 11136 let { context } = this.component; 11137 let eventRange = this.eventRange; 11138 this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, this.eventRange.instance.instanceId); 11139 let segEl = this.querySegEl(ev); 11140 this.draggingSegEl = segEl; 11141 this.draggingSeg = getElSeg(segEl); 11142 context.calendarApi.unselect(); 11143 context.emitter.trigger('eventResizeStart', { 11144 el: segEl, 11145 event: new EventImpl(context, eventRange.def, eventRange.instance), 11146 jsEvent: ev.origEvent, 11147 view: context.viewApi, 11148 }); 11149 }; 11150 this.handleHitUpdate = (hit, isFinal, ev) => { 11151 let { context } = this.component; 11152 let relevantEvents = this.relevantEvents; 11153 let initialHit = this.hitDragging.initialHit; 11154 let eventInstance = this.eventRange.instance; 11155 let mutation = null; 11156 let mutatedRelevantEvents = null; 11157 let isInvalid = false; 11158 let interaction = { 11159 affectedEvents: relevantEvents, 11160 mutatedEvents: createEmptyEventStore(), 11161 isEvent: true, 11162 }; 11163 if (hit) { 11164 let disallowed = hit.componentId === initialHit.componentId 11165 && this.isHitComboAllowed 11166 && !this.isHitComboAllowed(initialHit, hit); 11167 if (!disallowed) { 11168 mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range); 11169 } 11170 } 11171 if (mutation) { 11172 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context); 11173 interaction.mutatedEvents = mutatedRelevantEvents; 11174 if (!isInteractionValid(interaction, hit.dateProfile, context)) { 11175 isInvalid = true; 11176 mutation = null; 11177 mutatedRelevantEvents = null; 11178 interaction.mutatedEvents = null; 11179 } 11180 } 11181 if (mutatedRelevantEvents) { 11182 context.dispatch({ 11183 type: 'SET_EVENT_RESIZE', 11184 state: interaction, 11185 }); 11186 } 11187 else { 11188 context.dispatch({ type: 'UNSET_EVENT_RESIZE' }); 11189 } 11190 if (!isInvalid) { 11191 enableCursor(); 11192 } 11193 else { 11194 disableCursor(); 11195 } 11196 if (!isFinal) { 11197 if (mutation && isHitsEqual(initialHit, hit)) { 11198 mutation = null; 11199 } 11200 this.validMutation = mutation; 11201 this.mutatedRelevantEvents = mutatedRelevantEvents; 11202 } 11203 }; 11204 this.handleDragEnd = (ev) => { 11205 let { context } = this.component; 11206 let eventDef = this.eventRange.def; 11207 let eventInstance = this.eventRange.instance; 11208 let eventApi = new EventImpl(context, eventDef, eventInstance); 11209 let relevantEvents = this.relevantEvents; 11210 let mutatedRelevantEvents = this.mutatedRelevantEvents; 11211 context.emitter.trigger('eventResizeStop', { 11212 el: this.draggingSegEl, 11213 event: eventApi, 11214 jsEvent: ev.origEvent, 11215 view: context.viewApi, 11216 }); 11217 if (this.validMutation) { 11218 let updatedEventApi = new EventImpl(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); 11219 context.dispatch({ 11220 type: 'MERGE_EVENTS', 11221 eventStore: mutatedRelevantEvents, 11222 }); 11223 let eventChangeArg = { 11224 oldEvent: eventApi, 11225 event: updatedEventApi, 11226 relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), 11227 revert() { 11228 context.dispatch({ 11229 type: 'MERGE_EVENTS', 11230 eventStore: relevantEvents, // the pre-change events 11231 }); 11232 }, 11233 }; 11234 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 })); 11235 context.emitter.trigger('eventChange', eventChangeArg); 11236 } 11237 else { 11238 context.emitter.trigger('_noEventResize'); 11239 } 11240 // reset all internal state 11241 this.draggingSeg = null; 11242 this.relevantEvents = null; 11243 this.validMutation = null; 11244 // okay to keep eventInstance around. useful to set it in handlePointerDown 11245 }; 11246 let { component } = settings; 11247 let dragging = this.dragging = new FeaturefulElementDragging(settings.el); 11248 dragging.pointer.selector = '.fc-event-resizer'; 11249 dragging.touchScrollAllowed = false; 11250 dragging.autoScroller.isEnabled = component.context.options.dragScroll; 11251 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); 11252 hitDragging.emitter.on('pointerdown', this.handlePointerDown); 11253 hitDragging.emitter.on('dragstart', this.handleDragStart); 11254 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11255 hitDragging.emitter.on('dragend', this.handleDragEnd); 11256 } 11257 destroy() { 11258 this.dragging.destroy(); 11259 } 11260 querySegEl(ev) { 11261 return elementClosest(ev.subjectEl, '.fc-event'); 11262 } 11263 } 11264 function computeMutation(hit0, hit1, isFromStart, instanceRange) { 11265 let dateEnv = hit0.context.dateEnv; 11266 let date0 = hit0.dateSpan.range.start; 11267 let date1 = hit1.dateSpan.range.start; 11268 let delta = diffDates(date0, date1, dateEnv, hit0.largeUnit); 11269 if (isFromStart) { 11270 if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { 11271 return { startDelta: delta }; 11272 } 11273 } 11274 else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { 11275 return { endDelta: delta }; 11276 } 11277 return null; 11278 } 11279 11280 class UnselectAuto { 11281 constructor(context) { 11282 this.context = context; 11283 this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system 11284 this.matchesCancel = false; 11285 this.matchesEvent = false; 11286 this.onSelect = (selectInfo) => { 11287 if (selectInfo.jsEvent) { 11288 this.isRecentPointerDateSelect = true; 11289 } 11290 }; 11291 this.onDocumentPointerDown = (pev) => { 11292 let unselectCancel = this.context.options.unselectCancel; 11293 let downEl = getEventTargetViaRoot(pev.origEvent); 11294 this.matchesCancel = !!elementClosest(downEl, unselectCancel); 11295 this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event? 11296 }; 11297 this.onDocumentPointerUp = (pev) => { 11298 let { context } = this; 11299 let { documentPointer } = this; 11300 let calendarState = context.getCurrentData(); 11301 // touch-scrolling should never unfocus any type of selection 11302 if (!documentPointer.wasTouchScroll) { 11303 if (calendarState.dateSelection && // an existing date selection? 11304 !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? 11305 ) { 11306 let unselectAuto = context.options.unselectAuto; 11307 if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { 11308 context.calendarApi.unselect(pev); 11309 } 11310 } 11311 if (calendarState.eventSelection && // an existing event selected? 11312 !this.matchesEvent // interaction DIDN'T start on an event 11313 ) { 11314 context.dispatch({ type: 'UNSELECT_EVENT' }); 11315 } 11316 } 11317 this.isRecentPointerDateSelect = false; 11318 }; 11319 let documentPointer = this.documentPointer = new PointerDragging(document); 11320 documentPointer.shouldIgnoreMove = true; 11321 documentPointer.shouldWatchScroll = false; 11322 documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown); 11323 documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); 11324 /* 11325 TODO: better way to know about whether there was a selection with the pointer 11326 */ 11327 context.emitter.on('select', this.onSelect); 11328 } 11329 destroy() { 11330 this.context.emitter.off('select', this.onSelect); 11331 this.documentPointer.destroy(); 11332 } 11333 } 11334 11335 const OPTION_REFINERS$2 = { 11336 fixedMirrorParent: identity, 11337 }; 11338 const LISTENER_REFINERS = { 11339 dateClick: identity, 11340 eventDragStart: identity, 11341 eventDragStop: identity, 11342 eventDrop: identity, 11343 eventResizeStart: identity, 11344 eventResizeStop: identity, 11345 eventResize: identity, 11346 drop: identity, 11347 eventReceive: identity, 11348 eventLeave: identity, 11349 }; 11350 11351 /* 11352 Given an already instantiated draggable object for one-or-more elements, 11353 Interprets any dragging as an attempt to drag an events that lives outside 11354 of a calendar onto a calendar. 11355 */ 11356 class ExternalElementDragging { 11357 constructor(dragging, suppliedDragMeta) { 11358 this.receivingContext = null; 11359 this.droppableEvent = null; // will exist for all drags, even if create:false 11360 this.suppliedDragMeta = null; 11361 this.dragMeta = null; 11362 this.handleDragStart = (ev) => { 11363 this.dragMeta = this.buildDragMeta(ev.subjectEl); 11364 }; 11365 this.handleHitUpdate = (hit, isFinal, ev) => { 11366 let { dragging } = this.hitDragging; 11367 let receivingContext = null; 11368 let droppableEvent = null; 11369 let isInvalid = false; 11370 let interaction = { 11371 affectedEvents: createEmptyEventStore(), 11372 mutatedEvents: createEmptyEventStore(), 11373 isEvent: this.dragMeta.create, 11374 }; 11375 if (hit) { 11376 receivingContext = hit.context; 11377 if (this.canDropElOnCalendar(ev.subjectEl, receivingContext)) { 11378 droppableEvent = computeEventForDateSpan(hit.dateSpan, this.dragMeta, receivingContext); 11379 interaction.mutatedEvents = eventTupleToStore(droppableEvent); 11380 isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext); 11381 if (isInvalid) { 11382 interaction.mutatedEvents = createEmptyEventStore(); 11383 droppableEvent = null; 11384 } 11385 } 11386 } 11387 this.displayDrag(receivingContext, interaction); 11388 // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) 11389 // TODO: wish we could somehow wait for dispatch to guarantee render 11390 dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror')); 11391 if (!isInvalid) { 11392 enableCursor(); 11393 } 11394 else { 11395 disableCursor(); 11396 } 11397 if (!isFinal) { 11398 dragging.setMirrorNeedsRevert(!droppableEvent); 11399 this.receivingContext = receivingContext; 11400 this.droppableEvent = droppableEvent; 11401 } 11402 }; 11403 this.handleDragEnd = (pev) => { 11404 let { receivingContext, droppableEvent } = this; 11405 this.clearDrag(); 11406 if (receivingContext && droppableEvent) { 11407 let finalHit = this.hitDragging.finalHit; 11408 let finalView = finalHit.context.viewApi; 11409 let dragMeta = this.dragMeta; 11410 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView })); 11411 if (dragMeta.create) { 11412 let addingEvents = eventTupleToStore(droppableEvent); 11413 receivingContext.dispatch({ 11414 type: 'MERGE_EVENTS', 11415 eventStore: addingEvents, 11416 }); 11417 if (pev.isTouch) { 11418 receivingContext.dispatch({ 11419 type: 'SELECT_EVENT', 11420 eventInstanceId: droppableEvent.instance.instanceId, 11421 }); 11422 } 11423 // signal that an external event landed 11424 receivingContext.emitter.trigger('eventReceive', { 11425 event: new EventImpl(receivingContext, droppableEvent.def, droppableEvent.instance), 11426 relatedEvents: [], 11427 revert() { 11428 receivingContext.dispatch({ 11429 type: 'REMOVE_EVENTS', 11430 eventStore: addingEvents, 11431 }); 11432 }, 11433 draggedEl: pev.subjectEl, 11434 view: finalView, 11435 }); 11436 } 11437 } 11438 this.receivingContext = null; 11439 this.droppableEvent = null; 11440 }; 11441 let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore); 11442 hitDragging.requireInitial = false; // will start outside of a component 11443 hitDragging.emitter.on('dragstart', this.handleDragStart); 11444 hitDragging.emitter.on('hitupdate', this.handleHitUpdate); 11445 hitDragging.emitter.on('dragend', this.handleDragEnd); 11446 this.suppliedDragMeta = suppliedDragMeta; 11447 } 11448 buildDragMeta(subjectEl) { 11449 if (typeof this.suppliedDragMeta === 'object') { 11450 return parseDragMeta(this.suppliedDragMeta); 11451 } 11452 if (typeof this.suppliedDragMeta === 'function') { 11453 return parseDragMeta(this.suppliedDragMeta(subjectEl)); 11454 } 11455 return getDragMetaFromEl(subjectEl); 11456 } 11457 displayDrag(nextContext, state) { 11458 let prevContext = this.receivingContext; 11459 if (prevContext && prevContext !== nextContext) { 11460 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11461 } 11462 if (nextContext) { 11463 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); 11464 } 11465 } 11466 clearDrag() { 11467 if (this.receivingContext) { 11468 this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); 11469 } 11470 } 11471 canDropElOnCalendar(el, receivingContext) { 11472 let dropAccept = receivingContext.options.dropAccept; 11473 if (typeof dropAccept === 'function') { 11474 return dropAccept.call(receivingContext.calendarApi, el); 11475 } 11476 if (typeof dropAccept === 'string' && dropAccept) { 11477 return Boolean(elementMatches(el, dropAccept)); 11478 } 11479 return true; 11480 } 11481 } 11482 // Utils for computing event store from the DragMeta 11483 // ---------------------------------------------------------------------------------------------------- 11484 function computeEventForDateSpan(dateSpan, dragMeta, context) { 11485 let defProps = Object.assign({}, dragMeta.leftoverProps); 11486 for (let transform of context.pluginHooks.externalDefTransforms) { 11487 Object.assign(defProps, transform(dateSpan, dragMeta)); 11488 } 11489 let { refined, extra } = refineEventDef(defProps, context); 11490 let def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd 11491 context); 11492 let start = dateSpan.range.start; 11493 // only rely on time info if drop zone is all-day, 11494 // otherwise, we already know the time 11495 if (dateSpan.allDay && dragMeta.startTime) { 11496 start = context.dateEnv.add(start, dragMeta.startTime); 11497 } 11498 let end = dragMeta.duration ? 11499 context.dateEnv.add(start, dragMeta.duration) : 11500 getDefaultEventEnd(dateSpan.allDay, start, context); 11501 let instance = createEventInstance(def.defId, { start, end }); 11502 return { def, instance }; 11503 } 11504 // Utils for extracting data from element 11505 // ---------------------------------------------------------------------------------------------------- 11506 function getDragMetaFromEl(el) { 11507 let str = getEmbeddedElData(el, 'event'); 11508 let obj = str ? 11509 JSON.parse(str) : 11510 { create: false }; // if no embedded data, assume no event creation 11511 return parseDragMeta(obj); 11512 } 11513 config.dataAttrPrefix = ''; 11514 function getEmbeddedElData(el, name) { 11515 let prefix = config.dataAttrPrefix; 11516 let prefixedName = (prefix ? prefix + '-' : '') + name; 11517 return el.getAttribute('data-' + prefixedName) || ''; 11518 } 11519 11520 /* 11521 Makes an element (that is *external* to any calendar) draggable. 11522 Can pass in data that determines how an event will be created when dropped onto a calendar. 11523 Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. 11524 */ 11525 class ExternalDraggable { 11526 constructor(el, settings = {}) { 11527 this.handlePointerDown = (ev) => { 11528 let { dragging } = this; 11529 let { minDistance, longPressDelay } = this.settings; 11530 dragging.minDistance = 11531 minDistance != null ? 11532 minDistance : 11533 (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance); 11534 dragging.delay = 11535 ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv 11536 (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : 11537 0; 11538 }; 11539 this.handleDragStart = (ev) => { 11540 if (ev.isTouch && 11541 this.dragging.delay && 11542 ev.subjectEl.classList.contains('fc-event')) { 11543 this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected'); 11544 } 11545 }; 11546 this.settings = settings; 11547 let dragging = this.dragging = new FeaturefulElementDragging(el); 11548 dragging.touchScrollAllowed = false;
vendor: 6,340 bytes, lines 11549-11701
11549 if (settings.itemSelector != null) { 11550 dragging.pointer.selector = settings.itemSelector; 11551 } 11552 if (settings.appendTo != null) { 11553 dragging.mirror.parentNode = settings.appendTo; // TODO: write tests 11554 } 11555 dragging.emitter.on('pointerdown', this.handlePointerDown); 11556 dragging.emitter.on('dragstart', this.handleDragStart); 11557 new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new 11558 } 11559 destroy() { 11560 this.dragging.destroy(); 11561 } 11562 } 11563 11564 /* 11565 Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. 11566 The third-party system is responsible for drawing the visuals effects of the drag. 11567 This class simply monitors for pointer movements and fires events. 11568 It also has the ability to hide the moving element (the "mirror") during the drag. 11569 */ 11570 class InferredElementDragging extends ElementDragging { 11571 constructor(containerEl) { 11572 super(containerEl); 11573 this.shouldIgnoreMove = false; 11574 this.mirrorSelector = ''; 11575 this.currentMirrorEl = null; 11576 this.handlePointerDown = (ev) => { 11577 this.emitter.trigger('pointerdown', ev); 11578 if (!this.shouldIgnoreMove) { 11579 // fire dragstart right away. does not support delay or min-distance 11580 this.emitter.trigger('dragstart', ev); 11581 } 11582 }; 11583 this.handlePointerMove = (ev) => { 11584 if (!this.shouldIgnoreMove) { 11585 this.emitter.trigger('dragmove', ev); 11586 } 11587 }; 11588 this.handlePointerUp = (ev) => { 11589 this.emitter.trigger('pointerup', ev); 11590 if (!this.shouldIgnoreMove) { 11591 // fire dragend right away. does not support a revert animation 11592 this.emitter.trigger('dragend', ev); 11593 } 11594 }; 11595 let pointer = this.pointer = new PointerDragging(containerEl); 11596 pointer.emitter.on('pointerdown', this.handlePointerDown); 11597 pointer.emitter.on('pointermove', this.handlePointerMove); 11598 pointer.emitter.on('pointerup', this.handlePointerUp); 11599 } 11600 destroy() { 11601 this.pointer.destroy(); 11602 } 11603 setIgnoreMove(bool) { 11604 this.shouldIgnoreMove = bool; 11605 } 11606 setMirrorIsVisible(bool) { 11607 if (bool) { 11608 // restore a previously hidden element. 11609 // use the reference in case the selector class has already been removed. 11610 if (this.currentMirrorEl) { 11611 this.currentMirrorEl.style.visibility = ''; 11612 this.currentMirrorEl = null; 11613 } 11614 } 11615 else { 11616 let mirrorEl = this.mirrorSelector 11617 // TODO: somehow query FullCalendars WITHIN shadow-roots 11618 ? document.querySelector(this.mirrorSelector) 11619 : null; 11620 if (mirrorEl) { 11621 this.currentMirrorEl = mirrorEl; 11622 mirrorEl.style.visibility = 'hidden'; 11623 } 11624 } 11625 } 11626 } 11627 11628 /* 11629 Bridges third-party drag-n-drop systems with FullCalendar. 11630 Must be instantiated and destroyed by caller. 11631 */ 11632 class ThirdPartyDraggable { 11633 constructor(containerOrSettings, settings) { 11634 let containerEl = document; 11635 if ( 11636 // wish we could just test instanceof EventTarget, but doesn't work in IE11 11637 containerOrSettings === document || 11638 containerOrSettings instanceof Element) { 11639 containerEl = containerOrSettings; 11640 settings = settings || {}; 11641 } 11642 else { 11643 settings = (containerOrSettings || {}); 11644 } 11645 let dragging = this.dragging = new InferredElementDragging(containerEl); 11646 if (typeof settings.itemSelector === 'string') { 11647 dragging.pointer.selector = settings.itemSelector; 11648 } 11649 else if (containerEl === document) { 11650 dragging.pointer.selector = '[data-event]'; 11651 } 11652 if (typeof settings.mirrorSelector === 'string') { 11653 dragging.mirrorSelector = settings.mirrorSelector; 11654 } 11655 new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new 11656 } 11657 destroy() { 11658 this.dragging.destroy(); 11659 } 11660 } 11661 11662 var index$3 = createPlugin({ 11663 name: '@fullcalendar/interaction', 11664 componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], 11665 calendarInteractions: [UnselectAuto], 11666 elementDraggingImpl: FeaturefulElementDragging, 11667 optionRefiners: OPTION_REFINERS$2, 11668 listenerRefiners: LISTENER_REFINERS, 11669 }); 11670 11671 /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. 11672 ----------------------------------------------------------------------------------------------------------------------*/ 11673 // It is a manager for a Table subcomponent, which does most of the heavy lifting. 11674 // It is responsible for managing width/height. 11675 class TableView extends DateComponent { 11676 constructor() { 11677 super(...arguments); 11678 this.headerElRef = y(); 11679 } 11680 renderSimpleLayout(headerRowContent, bodyContent) { 11681 let { props, context } = this; 11682 let sections = []; 11683 let stickyHeaderDates = getStickyHeaderDates(context.options); 11684 if (headerRowContent) { 11685 sections.push({ 11686 type: 'header', 11687 key: 'header', 11688 isSticky: stickyHeaderDates, 11689 chunk: { 11690 elRef: this.headerElRef, 11691 tableClassName: 'fc-col-header', 11692 rowContent: headerRowContent, 11693 }, 11694 }); 11695 } 11696 sections.push({ 11697 type: 'body', 11698 key: 'body', 11699 liquid: true, 11700 chunk: { content: bodyContent }, 11701 });
vendor: 4,067 bytes, lines 11702-11813
11702 return (h(ViewContainer$1, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec }, 11703 h(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); 11704 } 11705 renderHScrollLayout(headerRowContent, bodyContent, colCnt, dayMinWidth) { 11706 let ScrollGrid = this.context.pluginHooks.scrollGridImpl; 11707 if (!ScrollGrid) { 11708 throw new Error('No ScrollGrid implementation'); 11709 } 11710 let { props, context } = this; 11711 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); 11712 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); 11713 let sections = []; 11714 if (headerRowContent) { 11715 sections.push({ 11716 type: 'header', 11717 key: 'header', 11718 isSticky: stickyHeaderDates, 11719 chunks: [{ 11720 key: 'main', 11721 elRef: this.headerElRef, 11722 tableClassName: 'fc-col-header', 11723 rowContent: headerRowContent, 11724 }], 11725 }); 11726 } 11727 sections.push({ 11728 type: 'body', 11729 key: 'body', 11730 liquid: true, 11731 chunks: [{ 11732 key: 'main', 11733 content: bodyContent, 11734 }], 11735 }); 11736 if (stickyFooterScrollbar) { 11737 sections.push({ 11738 type: 'footer', 11739 key: 'footer', 11740 isSticky: true, 11741 chunks: [{ 11742 key: 'main', 11743 content: renderScrollShim, 11744 }], 11745 }); 11746 } 11747 return (h(ViewContainer$1, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec }, 11748 h(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); 11749 } 11750 } 11751 11752 function splitSegsByRow(segs, rowCnt) { 11753 let byRow = []; 11754 for (let i = 0; i < rowCnt; i += 1) { 11755 byRow[i] = []; 11756 } 11757 for (let seg of segs) { 11758 byRow[seg.row].push(seg); 11759 } 11760 return byRow; 11761 } 11762 function splitSegsByFirstCol(segs, colCnt) { 11763 let byCol = []; 11764 for (let i = 0; i < colCnt; i += 1) { 11765 byCol[i] = []; 11766 } 11767 for (let seg of segs) { 11768 byCol[seg.firstCol].push(seg); 11769 } 11770 return byCol; 11771 } 11772 function splitInteractionByRow(ui, rowCnt) { 11773 let byRow = []; 11774 if (!ui) { 11775 for (let i = 0; i < rowCnt; i += 1) { 11776 byRow[i] = null; 11777 } 11778 } 11779 else { 11780 for (let i = 0; i < rowCnt; i += 1) { 11781 byRow[i] = { 11782 affectedInstances: ui.affectedInstances, 11783 isEvent: ui.isEvent, 11784 segs: [], 11785 }; 11786 } 11787 for (let seg of ui.segs) { 11788 byRow[seg.row].segs.push(seg); 11789 } 11790 } 11791 return byRow; 11792 } 11793 11794 const DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({ 11795 hour: 'numeric', 11796 minute: '2-digit', 11797 omitZeroMinute: true, 11798 meridiem: 'narrow', 11799 }); 11800 function hasListItemDisplay(seg) { 11801 let { display } = seg.eventRange.ui; 11802 return display === 'list-item' || (display === 'auto' && 11803 !seg.eventRange.def.allDay && 11804 seg.firstCol === seg.lastCol && // can't be multi-day 11805 seg.isStart && // " 11806 seg.isEnd // " 11807 ); 11808 } 11809 11810 class TableBlockEvent extends BaseComponent { 11811 render() { 11812 let { props } = this; 11813 return (h(StandardEvent, Object.assign({}
vendor: 8,375 bytes, lines 11813-11953
11813, 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 }))); 11814 } 11815 } 11816 11817 class TableListItemEvent extends BaseComponent { 11818 render() { 11819 let { props, context } = this; 11820 let { options } = context; 11821 let { seg } = props; 11822 let timeFormat = options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT; 11823 let timeText = buildSegTimeText(seg, timeFormat, context, true, props.defaultDisplayEventEnd); 11824 return (h(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 }))); 11825 } 11826 } 11827 function renderInnerContent$2(renderProps) { 11828 return (h(p, null, 11829 h("div", { className: "fc-daygrid-event-dot", style: { borderColor: renderProps.borderColor || renderProps.backgroundColor } }), 11830 renderProps.timeText && (h("div", { className: "fc-event-time" }, renderProps.timeText)), 11831 h("div", { className: "fc-event-title" }, renderProps.event.title || h(p, null, "\u00A0")))); 11832 } 11833 11834 class TableCellMoreLink extends BaseComponent { 11835 constructor() { 11836 super(...arguments); 11837 this.compileSegs = memoize(compileSegs); 11838 } 11839 render() { 11840 let { props } = this; 11841 let { allSegs, invisibleSegs } = this.compileSegs(props.singlePlacements); 11842 return (h(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: () => { 11843 let isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) || 11844 (props.eventResize ? props.eventResize.affectedInstances : null) || 11845 {}; 11846 return (h(p, null, allSegs.map((seg) => { 11847 let instanceId = seg.eventRange.instance.instanceId; 11848 return (h("div", { className: "fc-daygrid-event-harness", key: instanceId, style: { 11849 visibility: isForcedInvisible[instanceId] ? 'hidden' : '', 11850 } }, hasListItemDisplay(seg) ? (h(TableListItemEvent, Object.assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (h(TableBlockEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))))); 11851 }))); 11852 } })); 11853 } 11854 } 11855 function compileSegs(singlePlacements) { 11856 let allSegs = []; 11857 let invisibleSegs = []; 11858 for (let placement of singlePlacements) { 11859 allSegs.push(placement.seg); 11860 if (!placement.isVisible) { 11861 invisibleSegs.push(placement.seg); 11862 } 11863 } 11864 return { allSegs, invisibleSegs }; 11865 } 11866 11867 const DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' }); 11868 class TableCell extends DateComponent { 11869 constructor() { 11870 super(...arguments); 11871 this.rootElRef = y(); 11872 this.state = { 11873 dayNumberId: getUniqueDomId(), 11874 }; 11875 this.handleRootEl = (el) => { 11876 setRef(this.rootElRef, el); 11877 setRef(this.props.elRef, el); 11878 }; 11879 } 11880 render() { 11881 let { context, props, state, rootElRef } = this; 11882 let { options } = context; 11883 let { date, dateProfile } = props; 11884 return (h(DayCellContainer, { elTag: "td", elRef: this.handleRootEl, elClasses: [ 11885 'fc-daygrid-day', 11886 ...(props.extraClassNames || []), 11887 ], 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, extraRenderProps: props.extraRenderProps }, (InnerContent, renderProps) => (h("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef }, 11888 props.showWeekNumber && (h(WeekNumberContainer, { elTag: "a", elClasses: ['fc-daygrid-week-number'], elAttrs: buildNavLinkAttrs(context, date, 'week'), date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 })), 11889 Boolean(!renderProps.isDisabled && 11890 (props.showDayNumber || hasCustomDayCellContent(options) || props.forceDayTop)) && (h("div", { className: "fc-daygrid-day-top" }, 11891 h(InnerContent, { elTag: "a", elClasses: ['fc-daygrid-day-number'], elAttrs: Object.assign(Object.assign({}, buildNavLinkAttrs(context, date)), { id: state.dayNumberId }) }))), 11892 h("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef }, 11893 props.fgContent, 11894 h("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } }, 11895 h(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 }))), 11896 h("div", { className: "fc-daygrid-day-bg" }, props.bgContent))))); 11897 } 11898 } 11899 function renderTopInner(props) { 11900 return props.dayNumberText || h(p, null, "\u00A0"); 11901 } 11902 11903 function computeFgSegPlacement(segs, // assumed already sorted 11904 dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) { 11905 let hierarchy = new DayGridSegHierarchy(); 11906 hierarchy.allowReslicing = true; 11907 hierarchy.strictOrder = strictOrder; 11908 if (dayMaxEvents === true || dayMaxEventRows === true) { 11909 hierarchy.maxCoord = maxContentHeight; 11910 hierarchy.hiddenConsumes = true; 11911 } 11912 else if (typeof dayMaxEvents === 'number') { 11913 hierarchy.maxStackCnt = dayMaxEvents; 11914 } 11915 else if (typeof dayMaxEventRows === 'number') { 11916 hierarchy.maxStackCnt = dayMaxEventRows; 11917 hierarchy.hiddenConsumes = true; 11918 } 11919 // create segInputs only for segs with known heights 11920 let segInputs = []; 11921 let unknownHeightSegs = []; 11922 for (let i = 0; i < segs.length; i += 1) { 11923 let seg = segs[i]; 11924 let { instanceId } = seg.eventRange.instance; 11925 let eventHeight = eventInstanceHeights[instanceId]; 11926 if (eventHeight != null) { 11927 segInputs.push({ 11928 index: i, 11929 thickness: eventHeight, 11930 span: { 11931 start: seg.firstCol, 11932 end: seg.lastCol + 1, 11933 }, 11934 }); 11935 } 11936 else { 11937 unknownHeightSegs.push(seg); 11938 } 11939 } 11940 let hiddenEntries = hierarchy.addSegs(segInputs); 11941 let segRects = hierarchy.toRects(); 11942 let { singleColPlacements, multiColPlacements, leftoverMargins } = placeRects(segRects, segs, cells); 11943 let moreCnts = []; 11944 let moreMarginTops = []; 11945 // add segs with unknown heights 11946 for (let seg of unknownHeightSegs) { 11947 multiColPlacements[seg.firstCol].push({ 11948 seg, 11949 isVisible: false, 11950 isAbsolute: true, 11951 absoluteTop: 0, 11952 marginTop: 0, 11953 });
vendor: 13,637 bytes, lines 11954-12215
11954 for (let col = seg.firstCol; col <= seg.lastCol; col += 1) { 11955 singleColPlacements[col].push({ 11956 seg: resliceSeg(seg, col, col + 1, cells), 11957 isVisible: false, 11958 isAbsolute: false, 11959 absoluteTop: 0, 11960 marginTop: 0, 11961 }); 11962 } 11963 } 11964 // add the hidden entries 11965 for (let col = 0; col < cells.length; col += 1) { 11966 moreCnts.push(0); 11967 } 11968 for (let hiddenEntry of hiddenEntries) { 11969 let seg = segs[hiddenEntry.index]; 11970 let hiddenSpan = hiddenEntry.span; 11971 multiColPlacements[hiddenSpan.start].push({ 11972 seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells), 11973 isVisible: false, 11974 isAbsolute: true, 11975 absoluteTop: 0, 11976 marginTop: 0, 11977 }); 11978 for (let col = hiddenSpan.start; col < hiddenSpan.end; col += 1) { 11979 moreCnts[col] += 1; 11980 singleColPlacements[col].push({ 11981 seg: resliceSeg(seg, col, col + 1, cells), 11982 isVisible: false, 11983 isAbsolute: false, 11984 absoluteTop: 0, 11985 marginTop: 0, 11986 }); 11987 } 11988 } 11989 // deal with leftover margins 11990 for (let col = 0; col < cells.length; col += 1) { 11991 moreMarginTops.push(leftoverMargins[col]); 11992 } 11993 return { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops }; 11994 } 11995 // rects ordered by top coord, then left 11996 function placeRects(allRects, segs, cells) { 11997 let rectsByEachCol = groupRectsByEachCol(allRects, cells.length); 11998 let singleColPlacements = []; 11999 let multiColPlacements = []; 12000 let leftoverMargins = []; 12001 for (let col = 0; col < cells.length; col += 1) { 12002 let rects = rectsByEachCol[col]; 12003 // compute all static segs in singlePlacements 12004 let singlePlacements = []; 12005 let currentHeight = 0; 12006 let currentMarginTop = 0; 12007 for (let rect of rects) { 12008 let seg = segs[rect.index]; 12009 singlePlacements.push({ 12010 seg: resliceSeg(seg, col, col + 1, cells), 12011 isVisible: true, 12012 isAbsolute: false, 12013 absoluteTop: rect.levelCoord, 12014 marginTop: rect.levelCoord - currentHeight, 12015 }); 12016 currentHeight = rect.levelCoord + rect.thickness; 12017 } 12018 // compute mixed static/absolute segs in multiPlacements 12019 let multiPlacements = []; 12020 currentHeight = 0; 12021 currentMarginTop = 0; 12022 for (let rect of rects) { 12023 let seg = segs[rect.index]; 12024 let isAbsolute = rect.span.end - rect.span.start > 1; // multi-column? 12025 let isFirstCol = rect.span.start === col; 12026 currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg 12027 currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg 12028 if (isAbsolute) { 12029 currentMarginTop += rect.thickness; 12030 if (isFirstCol) { 12031 multiPlacements.push({ 12032 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), 12033 isVisible: true, 12034 isAbsolute: true, 12035 absoluteTop: rect.levelCoord, 12036 marginTop: 0, 12037 }); 12038 } 12039 } 12040 else if (isFirstCol) { 12041 multiPlacements.push({ 12042 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), 12043 isVisible: true, 12044 isAbsolute: false, 12045 absoluteTop: rect.levelCoord, 12046 marginTop: currentMarginTop, // claim the margin 12047 }); 12048 currentMarginTop = 0; 12049 } 12050 } 12051 singleColPlacements.push(singlePlacements); 12052 multiColPlacements.push(multiPlacements); 12053 leftoverMargins.push(currentMarginTop); 12054 } 12055 return { singleColPlacements, multiColPlacements, leftoverMargins }; 12056 } 12057 function groupRectsByEachCol(rects, colCnt) { 12058 let rectsByEachCol = []; 12059 for (let col = 0; col < colCnt; col += 1) { 12060 rectsByEachCol.push([]); 12061 } 12062 for (let rect of rects) { 12063 for (let col = rect.span.start; col < rect.span.end; col += 1) { 12064 rectsByEachCol[col].push(rect); 12065 } 12066 } 12067 return rectsByEachCol; 12068 } 12069 function resliceSeg(seg, spanStart, spanEnd, cells) { 12070 if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) { 12071 return seg; 12072 } 12073 let eventRange = seg.eventRange; 12074 let origRange = eventRange.range; 12075 let slicedRange = intersectRanges(origRange, { 12076 start: cells[spanStart].date, 12077 end: addDays(cells[spanEnd - 1].date, 1), 12078 }); 12079 return Object.assign(Object.assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: { 12080 def: eventRange.def, 12081 ui: Object.assign(Object.assign({}, eventRange.ui), { durationEditable: false }), 12082 instance: eventRange.instance, 12083 range: slicedRange, 12084 }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() }); 12085 } 12086 class DayGridSegHierarchy extends SegHierarchy { 12087 constructor() { 12088 super(...arguments); 12089 // config 12090 this.hiddenConsumes = false; 12091 // allows us to keep hidden entries in the hierarchy so they take up space 12092 this.forceHidden = {}; 12093 } 12094 addSegs(segInputs) { 12095 const hiddenSegs = super.addSegs(segInputs); 12096 const { entriesByLevel } = this; 12097 const excludeHidden = (entry) => !this.forceHidden[buildEntryKey(entry)]; 12098 // remove the forced-hidden segs 12099 for (let level = 0; level < entriesByLevel.length; level += 1) { 12100 entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden); 12101 } 12102 return hiddenSegs; 12103 } 12104 handleInvalidInsertion(insertion, entry, hiddenEntries) { 12105 const { entriesByLevel, forceHidden } = this; 12106 const { touchingEntry, touchingLevel, touchingLateral } = insertion; 12107 if (this.hiddenConsumes && touchingEntry) { 12108 const touchingEntryId = buildEntryKey(touchingEntry); 12109 // if not already hidden 12110 if (!forceHidden[touchingEntryId]) { 12111 if (this.allowReslicing) { 12112 const placeholderEntry = Object.assign(Object.assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) }); 12113 const placeholderEntryId = buildEntryKey(placeholderEntry); 12114 forceHidden[placeholderEntryId] = true; 12115 entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder 12116 this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it 12117 } 12118 else { 12119 forceHidden[touchingEntryId] = true; 12120 hiddenEntries.push(touchingEntry); 12121 } 12122 } 12123 } 12124 return super.handleInvalidInsertion(insertion, entry, hiddenEntries); 12125 } 12126 } 12127 12128 class TableRow extends DateComponent { 12129 constructor() { 12130 super(...arguments); 12131 this.cellElRefs = new RefMap(); // the <td> 12132 this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame 12133 this.fgElRefs = new RefMap(); // the fc-daygrid-day-events 12134 this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol" 12135 this.rootElRef = y(); 12136 this.state = { 12137 framePositions: null, 12138 maxContentHeight: null, 12139 eventInstanceHeights: {}, 12140 }; 12141 this.handleResize = (isForced) => { 12142 if (isForced) { 12143 this.updateSizing(true); // isExternal=true 12144 } 12145 }; 12146 } 12147 render() { 12148 let { props, state, context } = this; 12149 let { options } = context; 12150 let colCnt = props.cells.length; 12151 let businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt); 12152 let bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt); 12153 let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt); 12154 let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt); 12155 let { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops } = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells); 12156 let isForcedInvisible = // TODO: messy way to compute this 12157 (props.eventDrag && props.eventDrag.affectedInstances) || 12158 (props.eventResize && props.eventResize.affectedInstances) || 12159 {}; 12160 return (h("tr", { ref: this.rootElRef, role: "row" }, 12161 props.renderIntro && props.renderIntro(), 12162 props.cells.map((cell, col) => { 12163 let normalFgNodes = this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible); 12164 let mirrorFgNodes = this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false); 12165 return (h(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 12166 h(p, null, 12167 h(p, null, normalFgNodes), 12168 h(p, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys 12169 h(p, null, 12170 this.renderFillSegs(highlightSegsByCol[col], 'highlight'), 12171 this.renderFillSegs(businessHoursByCol[col], 'non-business'), 12172 this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) })); 12173 }))); 12174 } 12175 componentDidMount() { 12176 this.updateSizing(true); 12177 this.context.addResizeHandler(this.handleResize); 12178 } 12179 componentDidUpdate(prevProps, prevState) { 12180 let currentProps = this.props; 12181 this.updateSizing(!isPropsEqual(prevProps, currentProps)); 12182 } 12183 componentWillUnmount() { 12184 this.context.removeResizeHandler(this.handleResize); 12185 } 12186 getHighlightSegs() { 12187 let { props } = this; 12188 if (props.eventDrag && props.eventDrag.segs.length) { // messy check 12189 return props.eventDrag.segs; 12190 } 12191 if (props.eventResize && props.eventResize.segs.length) { // messy check 12192 return props.eventResize.segs; 12193 } 12194 return props.dateSelectionSegs; 12195 } 12196 getMirrorSegs() { 12197 let { props } = this; 12198 if (props.eventResize && props.eventResize.segs.length) { // messy check 12199 return props.eventResize.segs; 12200 } 12201 return []; 12202 } 12203 renderFgSegs(col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) { 12204 let { context } = this; 12205 let { eventSelection } = this.props; 12206 let { framePositions } = this.state; 12207 let defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1 12208 let isMirror = isDragging || isResizing || isDateSelecting; 12209 let nodes = []; 12210 if (framePositions) { 12211 for (let placement of segPlacements) { 12212 let { seg } = placement; 12213 let { instanceId } = seg.eventRange.instance; 12214 let key = instanceId + ':' + col; 12215 let isVisible = placement.isVisible && !isForcedInvisible[instanceId];
vendor: 15,724 bytes, lines 12216-12487
12216 let isAbsolute = placement.isAbsolute; 12217 let left = ''; 12218 let right = ''; 12219 if (isAbsolute) { 12220 if (context.isRtl) { 12221 right = 0; 12222 left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol]; 12223 } 12224 else { 12225 left = 0; 12226 right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol]; 12227 } 12228 } 12229 /* 12230 known bug: events that are force to be list-item but span multiple days still take up space in later columns 12231 todo: in print view, for multi-day events, don't display title within non-start/end segs 12232 */ 12233 nodes.push(h("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: { 12234 visibility: isVisible ? '' : 'hidden', 12235 marginTop: isAbsolute ? '' : placement.marginTop, 12236 top: isAbsolute ? placement.absoluteTop : '', 12237 left, 12238 right, 12239 } }, hasListItemDisplay(seg) ? (h(TableListItemEvent, Object.assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (h(TableBlockEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))))); 12240 } 12241 } 12242 return nodes; 12243 } 12244 renderFillSegs(segs, fillType) { 12245 let { isRtl } = this.context; 12246 let { todayRange } = this.props; 12247 let { framePositions } = this.state; 12248 let nodes = []; 12249 if (framePositions) { 12250 for (let seg of segs) { 12251 let leftRightCss = isRtl ? { 12252 right: 0, 12253 left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol], 12254 } : { 12255 left: 0, 12256 right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol], 12257 }; 12258 nodes.push(h("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ? 12259 h(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, todayRange))) : 12260 renderFill(fillType))); 12261 } 12262 } 12263 return h(p, {}, ...nodes); 12264 } 12265 updateSizing(isExternalSizingChange) { 12266 let { props, frameElRefs } = this; 12267 if (!props.forPrint && 12268 props.clientWidth !== null // positioning ready? 12269 ) { 12270 if (isExternalSizingChange) { 12271 let frameEls = props.cells.map((cell) => frameElRefs.currentMap[cell.key]); 12272 if (frameEls.length) { 12273 let originEl = this.rootElRef.current; 12274 this.setState({ 12275 framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal 12276 false), 12277 }); 12278 } 12279 } 12280 const oldInstanceHeights = this.state.eventInstanceHeights; 12281 const newInstanceHeights = this.queryEventInstanceHeights(); 12282 const limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true; 12283 this.safeSetState({ 12284 // HACK to prevent oscillations of events being shown/hidden from max-event-rows 12285 // Essentially, once you compute an element's height, never null-out. 12286 // TODO: always display all events, as visibility:hidden? 12287 eventInstanceHeights: Object.assign(Object.assign({}, oldInstanceHeights), newInstanceHeights), 12288 maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null, 12289 }); 12290 } 12291 } 12292 queryEventInstanceHeights() { 12293 let segElMap = this.segHarnessRefs.currentMap; 12294 let eventInstanceHeights = {}; 12295 // get the max height amongst instance segs 12296 for (let key in segElMap) { 12297 let height = Math.round(segElMap[key].getBoundingClientRect().height); 12298 let instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key 12299 eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height); 12300 } 12301 return eventInstanceHeights; 12302 } 12303 computeMaxContentHeight() { 12304 let firstKey = this.props.cells[0].key; 12305 let cellEl = this.cellElRefs.currentMap[firstKey]; 12306 let fcContainerEl = this.fgElRefs.currentMap[firstKey]; 12307 return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top; 12308 } 12309 getCellEls() { 12310 let elMap = this.cellElRefs.currentMap; 12311 return this.props.cells.map((cell) => elMap[cell.key]); 12312 } 12313 } 12314 TableRow.addStateEquality({ 12315 eventInstanceHeights: isPropsEqual, 12316 }); 12317 function buildMirrorPlacements(mirrorSegs, colPlacements) { 12318 if (!mirrorSegs.length) { 12319 return []; 12320 } 12321 let topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render? 12322 return mirrorSegs.map((seg) => ({ 12323 seg, 12324 isVisible: true, 12325 isAbsolute: true, 12326 absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId], 12327 marginTop: 0, 12328 })); 12329 } 12330 function buildAbsoluteTopHash(colPlacements) { 12331 let topsByInstanceId = {}; 12332 for (let placements of colPlacements) { 12333 for (let placement of placements) { 12334 topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop; 12335 } 12336 } 12337 return topsByInstanceId; 12338 } 12339 12340 class Table extends DateComponent { 12341 constructor() { 12342 super(...arguments); 12343 this.splitBusinessHourSegs = memoize(splitSegsByRow); 12344 this.splitBgEventSegs = memoize(splitSegsByRow); 12345 this.splitFgEventSegs = memoize(splitSegsByRow); 12346 this.splitDateSelectionSegs = memoize(splitSegsByRow); 12347 this.splitEventDrag = memoize(splitInteractionByRow); 12348 this.splitEventResize = memoize(splitInteractionByRow); 12349 this.rowRefs = new RefMap(); 12350 this.handleRootEl = (rootEl) => { 12351 this.rootEl = rootEl; 12352 if (rootEl) { 12353 this.context.registerInteractiveComponent(this, { 12354 el: rootEl, 12355 isHitComboAllowed: this.props.isHitComboAllowed, 12356 }); 12357 } 12358 else { 12359 this.context.unregisterInteractiveComponent(this); 12360 } 12361 }; 12362 } 12363 render() { 12364 let { props } = this; 12365 let { dateProfile, dayMaxEventRows, dayMaxEvents, expandRows } = props; 12366 let rowCnt = props.cells.length; 12367 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt); 12368 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt); 12369 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt); 12370 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt); 12371 let eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt); 12372 let eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt); 12373 let limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true; 12374 // if rows can't expand to fill fixed height, can't do balanced-height event limit 12375 // TODO: best place to normalize these options? 12376 if (limitViaBalanced && !expandRows) { 12377 limitViaBalanced = false; 12378 dayMaxEventRows = null; 12379 dayMaxEvents = null; 12380 } 12381 let classNames = [ 12382 'fc-daygrid-body', 12383 limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced', 12384 expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others? 12385 ]; 12386 return (h("div", { className: classNames.join(' '), ref: this.handleRootEl, style: { 12387 // these props are important to give this wrapper correct dimensions for interactions 12388 // TODO: if we set it here, can we avoid giving to inner tables? 12389 width: props.clientWidth, 12390 minWidth: props.tableMinWidth, 12391 } }, 12392 h(NowTimer, { unit: "day" }, (nowDate, todayRange) => (h(p, null, 12393 h("table", { role: "presentation", className: "fc-scrollgrid-sync-table", style: { 12394 width: props.clientWidth, 12395 minWidth: props.tableMinWidth, 12396 height: expandRows ? props.clientHeight : '', 12397 } }, 12398 props.colGroupNode, 12399 h("tbody", { role: "presentation" }, props.cells.map((cells, row) => (h(TableRow, { ref: this.rowRefs.createRef(row), key: cells.length 12400 ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */ 12401 : row // in case there are no cells (like when resource view is loading) 12402 , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: 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: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })))))))))); 12403 } 12404 // Hit System 12405 // ---------------------------------------------------------------------------------------------------- 12406 prepareHits() { 12407 this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map((rowObj) => rowObj.getCellEls()[0]), // first cell el in each row. TODO: not optimal 12408 false, true); 12409 this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row 12410 true, // horizontal 12411 false); 12412 } 12413 queryHit(positionLeft, positionTop) { 12414 let { colPositions, rowPositions } = this; 12415 let col = colPositions.leftToIndex(positionLeft); 12416 let row = rowPositions.topToIndex(positionTop); 12417 if (row != null && col != null) { 12418 let cell = this.props.cells[row][col]; 12419 return { 12420 dateProfile: this.props.dateProfile, 12421 dateSpan: Object.assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan), 12422 dayEl: this.getCellEl(row, col), 12423 rect: { 12424 left: colPositions.lefts[col], 12425 right: colPositions.rights[col], 12426 top: rowPositions.tops[row], 12427 bottom: rowPositions.bottoms[row], 12428 }, 12429 layer: 0, 12430 }; 12431 } 12432 return null; 12433 } 12434 getCellEl(row, col) { 12435 return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal 12436 } 12437 getCellRange(row, col) { 12438 let start = this.props.cells[row][col].date; 12439 let end = addDays(start, 1); 12440 return { start, end }; 12441 } 12442 } 12443 function isSegAllDay(seg) { 12444 return seg.eventRange.def.allDay; 12445 } 12446 12447 class DayTableSlicer extends Slicer { 12448 constructor() { 12449 super(...arguments); 12450 this.forceDayIfListItem = true; 12451 } 12452 sliceRange(dateRange, dayTableModel) { 12453 return dayTableModel.sliceRange(dateRange); 12454 } 12455 } 12456 12457 class DayTable extends DateComponent { 12458 constructor() { 12459 super(...arguments); 12460 this.slicer = new DayTableSlicer(); 12461 this.tableRef = y(); 12462 } 12463 render() { 12464 let { props, context } = this; 12465 return (h(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 }))); 12466 } 12467 } 12468 12469 class DayTableView extends TableView { 12470 constructor() { 12471 super(...arguments); 12472 this.buildDayTableModel = memoize(buildDayTableModel); 12473 this.headerRef = y(); 12474 this.tableRef = y(); 12475 } 12476 render() { 12477 let { options, dateProfileGenerator } = this.context; 12478 let { props } = this; 12479 let dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator); 12480 let headerContent = options.dayHeaders && (h(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 })); 12481 let bodyContent = (contentArg) => (h(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 })); 12482 return options.dayMinWidth 12483 ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth) 12484 : this.renderSimpleLayout(headerContent, bodyContent); 12485 } 12486 } 12487 function buildDayTableModel(dateProfile, dateProfileGenerator) {
12488 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); 12489 return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit)); 12490 } 12491 12492 class TableDateProfileGenerator extends DateProfileGenerator { 12493 // Computes the date range that will be rendered. 12494 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { 12495 let { dateEnv } = this.props; 12496 let renderRange = super.buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay); 12497 let start = renderRange.start; 12498 let end = renderRange.end; 12499 let endOfWeek; 12500 // year and month views should be aligned with weeks. this is already done for week 12501 if (/^(year|month)$/.test(currentRangeUnit)) { 12502 start = dateEnv.startOfWeek(start); 12503 // make end-of-week if not already 12504 endOfWeek = dateEnv.startOfWeek(end); 12505 if (endOfWeek.valueOf() !== end.valueOf()) { 12506 end = addWeeks(endOfWeek, 1); 12507 } 12508 } 12509 // ensure 6 weeks 12510 if (this.props.monthMode && 12511 this.props.fixedWeekCount) { 12512 let rowCnt = Math.ceil(// could be partial weeks due to hiddenDays 12513 diffWeeks(start, end)); 12514 end = addWeeks(end, 6 - rowCnt); 12515 } 12516 return { start, end }; 12517 } 12518 } 12519 12520 var css_248z$2 = ":root{--fc-daygrid-event-dot-width:8px}.fc-daygrid-day-events:after,.fc-daygrid-day-events:before,.fc-daygri
12520d-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-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;padding:2px 3px 0}.fc .fc-daygrid-day-bottom:before{clear:both;content:\"\";display:table}.fc .fc-daygrid-more-link{cursor:pointer;position:relative;z-index:4}.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-week-number{border-radius:0 0 3px 0;left:0}.fc-direction-rtl .fc-daygrid-week-number{border-radius:0 0 0 3px;right:0}
vendor: 59,969 bytes, lines 12520-13679
12520.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}"; 12521 injectStyles(css_248z$2); 12522 12523 var index$2 = createPlugin({ 12524 name: '@fullcalendar/daygrid', 12525 initialView: 'dayGridMonth', 12526 views: { 12527 dayGrid: { 12528 component: DayTableView, 12529 dateProfileGeneratorClass: TableDateProfileGenerator, 12530 }, 12531 dayGridDay: { 12532 type: 'dayGrid', 12533 duration: { days: 1 }, 12534 }, 12535 dayGridWeek: { 12536 type: 'dayGrid', 12537 duration: { weeks: 1 }, 12538 }, 12539 dayGridMonth: { 12540 type: 'dayGrid', 12541 duration: { months: 1 }, 12542 monthMode: true, 12543 fixedWeekCount: true, 12544 }, 12545 }, 12546 }); 12547 12548 class AllDaySplitter extends Splitter { 12549 getKeyInfo() { 12550 return { 12551 allDay: {}, 12552 timed: {}, 12553 }; 12554 } 12555 getKeysForDateSpan(dateSpan) { 12556 if (dateSpan.allDay) { 12557 return ['allDay']; 12558 } 12559 return ['timed']; 12560 } 12561 getKeysForEventDef(eventDef) { 12562 if (!eventDef.allDay) { 12563 return ['timed']; 12564 } 12565 if (hasBgRendering(eventDef)) { 12566 return ['timed', 'allDay']; 12567 } 12568 return ['allDay']; 12569 } 12570 } 12571 12572 const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({ 12573 hour: 'numeric', 12574 minute: '2-digit', 12575 omitZeroMinute: true, 12576 meridiem: 'short', 12577 }); 12578 function TimeColsAxisCell(props) { 12579 let classNames = [ 12580 'fc-timegrid-slot', 12581 'fc-timegrid-slot-label', 12582 props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor', 12583 ]; 12584 return (h(ViewContextType.Consumer, null, (context) => { 12585 if (!props.isLabeled) { 12586 return (h("td", { className: classNames.join(' '), "data-time": props.isoTimeStr })); 12587 } 12588 let { dateEnv, options, viewApi } = context; 12589 let labelFormat = // TODO: fully pre-parse 12590 options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT : 12591 Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) : 12592 createFormatter(options.slotLabelFormat); 12593 let renderProps = { 12594 level: 0, 12595 time: props.time, 12596 date: dateEnv.toDate(props.date), 12597 view: viewApi, 12598 text: dateEnv.format(props.date, labelFormat), 12599 }; 12600 return (h(ContentContainer, { elTag: "td", elClasses: classNames, elAttrs: { 12601 'data-time': props.isoTimeStr, 12602 }, renderProps: renderProps, generatorName: "slotLabelContent", generator: options.slotLabelContent || renderInnerContent$1, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (h("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" }, 12603 h(InnerContent, { elTag: "div", elClasses: [ 12604 'fc-timegrid-slot-label-cushion', 12605 'fc-scrollgrid-shrink-cushion', 12606 ] }))))); 12607 })); 12608 } 12609 function renderInnerContent$1(props) { 12610 return props.text; 12611 } 12612 12613 class TimeBodyAxis extends BaseComponent { 12614 render() { 12615 return this.props.slatMetas.map((slatMeta) => (h("tr", { key: slatMeta.key }, 12616 h(TimeColsAxisCell, Object.assign({}, slatMeta))))); 12617 } 12618 } 12619 12620 const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }); 12621 const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5; 12622 class TimeColsView extends DateComponent { 12623 constructor() { 12624 super(...arguments); 12625 this.allDaySplitter = new AllDaySplitter(); // for use by subclasses 12626 this.headerElRef = y(); 12627 this.rootElRef = y(); 12628 this.scrollerElRef = y(); 12629 this.state = { 12630 slatCoords: null, 12631 }; 12632 this.handleScrollTopRequest = (scrollTop) => { 12633 let scrollerEl = this.scrollerElRef.current; 12634 if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer 12635 scrollerEl.scrollTop = scrollTop; 12636 } 12637 }; 12638 /* Header Render Methods 12639 ------------------------------------------------------------------------------------------------------------------*/ 12640 this.renderHeadAxis = (rowKey, frameHeight = '') => { 12641 let { options } = this.context; 12642 let { dateProfile } = this.props; 12643 let range = dateProfile.renderRange; 12644 let dayCnt = diffDays(range.start, range.end); 12645 // only do in day views (to avoid doing in week views that dont need it) 12646 let navLinkAttrs = (dayCnt === 1) 12647 ? buildNavLinkAttrs(this.context, range.start, 'week') 12648 : {}; 12649 if (options.weekNumbers && rowKey === 'day') { 12650 return (h(WeekNumberContainer, { elTag: "th", elClasses: [ 12651 'fc-timegrid-axis', 12652 'fc-scrollgrid-shrink', 12653 ], elAttrs: { 12654 'aria-hidden': true, 12655 }, date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, (InnerContent) => (h("div", { className: [ 12656 'fc-timegrid-axis-frame', 12657 'fc-scrollgrid-shrink-frame', 12658 'fc-timegrid-axis-frame-liquid', 12659 ].join(' '), style: { height: frameHeight } }, 12660 h(InnerContent, { elTag: "a", elClasses: [ 12661 'fc-timegrid-axis-cushion', 12662 'fc-scrollgrid-shrink-cushion', 12663 'fc-scrollgrid-sync-inner', 12664 ], elAttrs: navLinkAttrs }))))); 12665 } 12666 return (h("th", { "aria-hidden": true, className: "fc-timegrid-axis" }, 12667 h("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } }))); 12668 }; 12669 /* Table Component Render Methods 12670 ------------------------------------------------------------------------------------------------------------------*/ 12671 // only a one-way height sync. we don't send the axis inner-content height to the DayGrid, 12672 // but DayGrid still needs to have classNames on inner elements in order to measure. 12673 this.renderTableRowAxis = (rowHeight) => { 12674 let { options, viewApi } = this.context; 12675 let renderProps = { 12676 text: options.allDayText, 12677 view: viewApi, 12678 }; 12679 return ( 12680 // TODO: make reusable hook. used in list view too 12681 h(ContentContainer, { elTag: "td", elClasses: [ 12682 'fc-timegrid-axis', 12683 'fc-scrollgrid-shrink', 12684 ], elAttrs: { 12685 'aria-hidden': true, 12686 }, renderProps: renderProps, generatorName: "allDayContent", generator: options.allDayContent || renderAllDayInner$1, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, (InnerContent) => (h("div", { className: [ 12687 'fc-timegrid-axis-frame', 12688 'fc-scrollgrid-shrink-frame', 12689 rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : '', 12690 ].join(' '), style: { height: rowHeight } }, 12691 h(InnerContent, { elTag: "span", elClasses: [ 12692 'fc-timegrid-axis-cushion', 12693 'fc-scrollgrid-shrink-cushion', 12694 'fc-scrollgrid-sync-inner', 12695 ] }))))); 12696 }; 12697 this.handleSlatCoords = (slatCoords) => { 12698 this.setState({ slatCoords }); 12699 }; 12700 } 12701 // rendering 12702 // ---------------------------------------------------------------------------------------------------- 12703 renderSimpleLayout(headerRowContent, allDayContent, timeContent) { 12704 let { context, props } = this; 12705 let sections = []; 12706 let stickyHeaderDates = getStickyHeaderDates(context.options); 12707 if (headerRowContent) { 12708 sections.push({ 12709 type: 'header', 12710 key: 'header', 12711 isSticky: stickyHeaderDates, 12712 chunk: { 12713 elRef: this.headerElRef, 12714 tableClassName: 'fc-col-header', 12715 rowContent: headerRowContent, 12716 }, 12717 }); 12718 } 12719 if (allDayContent) { 12720 sections.push({ 12721 type: 'body', 12722 key: 'all-day', 12723 chunk: { content: allDayContent }, 12724 }); 12725 sections.push({ 12726 type: 'body', 12727 key: 'all-day-divider', 12728 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>? 12729 h("tr", { role: "presentation", className: "fc-scrollgrid-section" }, 12730 h("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), 12731 }); 12732 } 12733 sections.push({ 12734 type: 'body', 12735 key: 'body', 12736 liquid: true, 12737 expandRows: Boolean(context.options.expandRows), 12738 chunk: { 12739 scrollerElRef: this.scrollerElRef, 12740 content: timeContent, 12741 }, 12742 }); 12743 return (h(ViewContainer$1, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec }, 12744 h(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); 12745 } 12746 renderHScrollLayout(headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { 12747 let ScrollGrid = this.context.pluginHooks.scrollGridImpl; 12748 if (!ScrollGrid) { 12749 throw new Error('No ScrollGrid implementation'); 12750 } 12751 let { context, props } = this; 12752 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); 12753 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); 12754 let sections = []; 12755 if (headerRowContent) { 12756 sections.push({ 12757 type: 'header', 12758 key: 'header', 12759 isSticky: stickyHeaderDates, 12760 syncRowHeights: true, 12761 chunks: [ 12762 { 12763 key: 'axis', 12764 rowContent: (arg) => (h("tr", { role: "presentation" }, this.renderHeadAxis('day', arg.rowSyncHeights[0]))), 12765 }, 12766 { 12767 key: 'cols', 12768 elRef: this.headerElRef, 12769 tableClassName: 'fc-col-header', 12770 rowContent: headerRowContent, 12771 }, 12772 ], 12773 }); 12774 } 12775 if (allDayContent) { 12776 sections.push({ 12777 type: 'body', 12778 key: 'all-day', 12779 syncRowHeights: true, 12780 chunks: [ 12781 { 12782 key: 'axis', 12783 rowContent: (contentArg) => (h("tr", { role: "presentation" }, this.renderTableRowAxis(contentArg.rowSyncHeights[0]))), 12784 }, 12785 { 12786 key: 'cols', 12787 content: allDayContent, 12788 }, 12789 ], 12790 }); 12791 sections.push({ 12792 key: 'all-day-divider', 12793 type: 'body', 12794 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>? 12795 h("tr", { role: "presentation", className: "fc-scrollgrid-section" }, 12796 h("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), 12797 }); 12798 } 12799 let isNowIndicator = context.options.nowIndicator; 12800 sections.push({ 12801 type: 'body', 12802 key: 'body', 12803 liquid: true, 12804 expandRows: Boolean(context.options.expandRows), 12805 chunks: [ 12806 { 12807 key: 'axis', 12808 content: (arg) => ( 12809 // TODO: make this now-indicator arrow more DRY with TimeColsContent 12810 h("div", { className: "fc-timegrid-axis-chunk" }, 12811 h("table", { "aria-hidden": true, style: { height: arg.expandRows ? arg.clientHeight : '' } }, 12812 arg.tableColGroupNode, 12813 h("tbody", null, 12814 h(TimeBodyAxis, { slatMetas: slatMetas }))), 12815 h("div", { className: "fc-timegrid-now-indicator-container" }, 12816 h(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, (nowDate) => { 12817 let nowIndicatorTop = isNowIndicator && 12818 slatCoords && 12819 slatCoords.safeComputeTop(nowDate); // might return void 12820 if (typeof nowIndicatorTop === 'number') { 12821 return (h(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: nowDate })); 12822 } 12823 return null; 12824 })))), 12825 }, 12826 { 12827 key: 'cols', 12828 scrollerElRef: this.scrollerElRef, 12829 content: timeContent, 12830 }, 12831 ], 12832 }); 12833 if (stickyFooterScrollbar) { 12834 sections.push({ 12835 key: 'footer', 12836 type: 'footer', 12837 isSticky: true, 12838 chunks: [ 12839 { 12840 key: 'axis', 12841 content: renderScrollShim, 12842 }, 12843 { 12844 key: 'cols', 12845 content: renderScrollShim, 12846 }, 12847 ], 12848 }); 12849 } 12850 return (h(ViewContainer$1, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec }, 12851 h(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [ 12852 { width: 'shrink', cols: [{ width: 'shrink' }] }, 12853 { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, 12854 ], sections: sections }))); 12855 } 12856 /* Dimensions 12857 ------------------------------------------------------------------------------------------------------------------*/ 12858 getAllDayMaxEventProps() { 12859 let { dayMaxEvents, dayMaxEventRows } = this.context.options; 12860 if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? 12861 dayMaxEvents = undefined; 12862 dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number 12863 } 12864 return { dayMaxEvents, dayMaxEventRows }; 12865 } 12866 } 12867 function renderAllDayInner$1(renderProps) { 12868 return renderProps.text; 12869 } 12870 12871 class TimeColsSlatsCoords { 12872 constructor(positions, dateProfile, slotDuration) { 12873 this.positions = positions; 12874 this.dateProfile = dateProfile; 12875 this.slotDuration = slotDuration; 12876 } 12877 safeComputeTop(date) { 12878 let { dateProfile } = this; 12879 if (rangeContainsMarker(dateProfile.currentRange, date)) { 12880 let startOfDayDate = startOfDay(date); 12881 let timeMs = date.valueOf() - startOfDayDate.valueOf(); 12882 if (timeMs >= asRoughMs(dateProfile.slotMinTime) && 12883 timeMs < asRoughMs(dateProfile.slotMaxTime)) { 12884 return this.computeTimeTop(createDuration(timeMs)); 12885 } 12886 } 12887 return null; 12888 } 12889 // Computes the top coordinate, relative to the bounds of the grid, of the given date. 12890 // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. 12891 computeDateTop(when, startOfDayDate) { 12892 if (!startOfDayDate) { 12893 startOfDayDate = startOfDay(when); 12894 } 12895 return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); 12896 } 12897 // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). 12898 // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. 12899 // Eventually allow computation with arbirary slat dates. 12900 computeTimeTop(duration) { 12901 let { positions, dateProfile } = this; 12902 let len = positions.els.length; 12903 // floating-point value of # of slots covered 12904 let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); 12905 let slatIndex; 12906 let slatRemainder; 12907 // compute a floating-point number for how many slats should be progressed through. 12908 // from 0 to number of slats (inclusive) 12909 // constrained because slotMinTime/slotMaxTime might be customized. 12910 slatCoverage = Math.max(0, slatCoverage); 12911 slatCoverage = Math.min(len, slatCoverage); 12912 // an integer index of the furthest whole slat 12913 // from 0 to number slats (*exclusive*, so len-1) 12914 slatIndex = Math.floor(slatCoverage); 12915 slatIndex = Math.min(slatIndex, len - 1); 12916 // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. 12917 // could be 1.0 if slatCoverage is covering *all* the slots 12918 slatRemainder = slatCoverage - slatIndex; 12919 return positions.tops[slatIndex] + 12920 positions.getHeight(slatIndex) * slatRemainder; 12921 } 12922 } 12923 12924 class TimeColsSlatsBody extends BaseComponent { 12925 render() { 12926 let { props, context } = this; 12927 let { options } = context; 12928 let { slatElRefs } = props; 12929 return (h("tbody", null, props.slatMetas.map((slatMeta, i) => { 12930 let renderProps = { 12931 time: slatMeta.time, 12932 date: context.dateEnv.toDate(slatMeta.date), 12933 view: context.viewApi, 12934 }; 12935 return (h("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, 12936 props.axis && (h(TimeColsAxisCell, Object.assign({}, slatMeta))), 12937 h(ContentContainer, { elTag: "td", elClasses: [ 12938 'fc-timegrid-slot', 12939 'fc-timegrid-slot-lane', 12940 !slatMeta.isLabeled && 'fc-timegrid-slot-minor', 12941 ], elAttrs: { 12942 'data-time': slatMeta.isoTimeStr, 12943 }, renderProps: renderProps, generatorName: "slotLaneContent", generator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }))); 12944 }))); 12945 } 12946 } 12947 12948 /* 12949 for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. 12950 */ 12951 class TimeColsSlats extends BaseComponent { 12952 constructor() { 12953 super(...arguments); 12954 this.rootElRef = y(); 12955 this.slatElRefs = new RefMap(); 12956 } 12957 render() { 12958 let { props, context } = this; 12959 return (h("div", { ref: this.rootElRef, className: "fc-timegrid-slots" }, 12960 h("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: { 12961 minWidth: props.tableMinWidth, 12962 width: props.clientWidth, 12963 height: props.minHeight, 12964 } }, 12965 props.tableColGroupNode /* relies on there only being a single <col> for the axis */, 12966 h(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); 12967 } 12968 componentDidMount() { 12969 this.updateSizing(); 12970 } 12971 componentDidUpdate() { 12972 this.updateSizing(); 12973 } 12974 componentWillUnmount() { 12975 if (this.props.onCoords) { 12976 this.props.onCoords(null); 12977 } 12978 } 12979 updateSizing() { 12980 let { context, props } = this; 12981 if (props.onCoords && 12982 props.clientWidth !== null // means sizing has stabilized 12983 ) { 12984 let rootEl = this.rootElRef.current; 12985 if (rootEl.offsetHeight) { // not hidden by css 12986 props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); 12987 } 12988 } 12989 } 12990 } 12991 function collectSlatEls(elMap, slatMetas) { 12992 return slatMetas.map((slatMeta) => elMap[slatMeta.key]); 12993 } 12994 12995 function splitSegsByCol(segs, colCnt) { 12996 let segsByCol = []; 12997 let i; 12998 for (i = 0; i < colCnt; i += 1) { 12999 segsByCol.push([]); 13000 } 13001 if (segs) { 13002 for (i = 0; i < segs.length; i += 1) { 13003 segsByCol[segs[i].col].push(segs[i]); 13004 } 13005 } 13006 return segsByCol; 13007 } 13008 function splitInteractionByCol(ui, colCnt) { 13009 let byRow = []; 13010 if (!ui) { 13011 for (let i = 0; i < colCnt; i += 1) { 13012 byRow[i] = null; 13013 } 13014 } 13015 else { 13016 for (let i = 0; i < colCnt; i += 1) { 13017 byRow[i] = { 13018 affectedInstances: ui.affectedInstances, 13019 isEvent: ui.isEvent, 13020 segs: [], 13021 }; 13022 } 13023 for (let seg of ui.segs) { 13024 byRow[seg.col].segs.push(seg); 13025 } 13026 } 13027 return byRow; 13028 } 13029 13030 class TimeColMoreLink extends BaseComponent { 13031 render() { 13032 let { props } = this; 13033 return (h(MoreLinkContainer, { elClasses: ['fc-timegrid-more-link'], elStyle: { 13034 top: props.top, 13035 bottom: props.bottom, 13036 }, 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 }, (InnerContent) => (h(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-more-link-inner', 'fc-sticky'] })))); 13037 } 13038 } 13039 function renderMoreLinkInner(props) { 13040 return props.shortText; 13041 } 13042 13043 // segInputs assumed sorted 13044 function buildPositioning(segInputs, strictOrder, maxStackCnt) { 13045 let hierarchy = new SegHierarchy(); 13046 if (strictOrder != null) { 13047 hierarchy.strictOrder = strictOrder; 13048 } 13049 if (maxStackCnt != null) { 13050 hierarchy.maxStackCnt = maxStackCnt; 13051 } 13052 let hiddenEntries = hierarchy.addSegs(segInputs); 13053 let hiddenGroups = groupIntersectingEntries(hiddenEntries); 13054 let web = buildWeb(hierarchy); 13055 web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 13056 let segRects = webToRects(web); 13057 return { segRects, hiddenGroups }; 13058 } 13059 function buildWeb(hierarchy) { 13060 const { entriesByLevel } = hierarchy; 13061 const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => { 13062 let siblingRange = findNextLevelSegs(hierarchy, level, lateral); 13063 let nextLevelRes = buildNodes(siblingRange, buildNode); 13064 let entry = entriesByLevel[level][lateral]; 13065 return [ 13066 Object.assign(Object.assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), 13067 entry.thickness + nextLevelRes[1], // the pressure builds 13068 ]; 13069 }); 13070 return buildNodes(entriesByLevel.length 13071 ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } 13072 : null, buildNode)[0]; 13073 } 13074 function buildNodes(siblingRange, buildNode) { 13075 if (!siblingRange) { 13076 return [[], 0]; 13077 } 13078 let { level, lateralStart, lateralEnd } = siblingRange; 13079 let lateral = lateralStart; 13080 let pairs = []; 13081 while (lateral < lateralEnd) { 13082 pairs.push(buildNode(level, lateral)); 13083 lateral += 1; 13084 } 13085 pairs.sort(cmpDescPressures); 13086 return [ 13087 pairs.map(extractNode), 13088 pairs[0][1], // first item's pressure 13089 ]; 13090 } 13091 function cmpDescPressures(a, b) { 13092 return b[1] - a[1]; 13093 } 13094 function extractNode(a) { 13095 return a[0]; 13096 } 13097 function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { 13098 let { levelCoords, entriesByLevel } = hierarchy; 13099 let subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; 13100 let afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; 13101 let levelCnt = levelCoords.length; 13102 let level = subjectLevel; 13103 // skip past levels that are too high up 13104 for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) 13105 ; // do nothing 13106 for (; level < levelCnt; level += 1) { 13107 let entries = entriesByLevel[level]; 13108 let entry; 13109 let searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); 13110 let lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one 13111 let lateralEnd = lateralStart; 13112 while ( // loop through entries that horizontally intersect 13113 (entry = entries[lateralEnd]) && // but not past the whole seg list 13114 entry.span.start < subjectEntry.span.end) { 13115 lateralEnd += 1; 13116 } 13117 if (lateralStart < lateralEnd) { 13118 return { level, lateralStart, lateralEnd }; 13119 } 13120 } 13121 return null; 13122 } 13123 function stretchWeb(topLevelNodes, totalThickness) { 13124 const stretchNode = cacheable((node, startCoord, prevThickness) => buildEntryKey(node), (node, startCoord, prevThickness) => { 13125 let { nextLevelNodes, thickness } = node; 13126 let allThickness = thickness + prevThickness; 13127 let thicknessFraction = thickness / allThickness; 13128 let endCoord; 13129 let newChildren = []; 13130 if (!nextLevelNodes.length) { 13131 endCoord = totalThickness; 13132 } 13133 else { 13134 for (let childNode of nextLevelNodes) { 13135 if (endCoord === undefined) { 13136 let res = stretchNode(childNode, startCoord, allThickness); 13137 endCoord = res[0]; 13138 newChildren.push(res[1]); 13139 } 13140 else { 13141 let res = stretchNode(childNode, endCoord, 0); 13142 newChildren.push(res[1]); 13143 } 13144 } 13145 } 13146 let newThickness = (endCoord - startCoord) * thicknessFraction; 13147 return [endCoord - newThickness, Object.assign(Object.assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; 13148 }); 13149 return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]); 13150 } 13151 // not sorted in any particular order 13152 function webToRects(topLevelNodes) { 13153 let rects = []; 13154 const processNode = cacheable((node, levelCoord, stackDepth) => buildEntryKey(node), (node, levelCoord, stackDepth) => { 13155 let rect = Object.assign(Object.assign({}, node), { levelCoord, 13156 stackDepth, stackForward: 0 }); 13157 rects.push(rect); 13158 return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); 13159 }); 13160 function processNodes(nodes, levelCoord, stackDepth) { 13161 let stackForward = 0; 13162 for (let node of nodes) { 13163 stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); 13164 } 13165 return stackForward; 13166 } 13167 processNodes(topLevelNodes, 0, 0); 13168 return rects; // TODO: sort rects by levelCoord to be consistent with toRects? 13169 } 13170 // TODO: move to general util 13171 function cacheable(keyFunc, workFunc) { 13172 const cache = {}; 13173 return (...args) => { 13174 let key = keyFunc(...args); 13175 return (key in cache) 13176 ? cache[key] 13177 : (cache[key] = workFunc(...args)); 13178 }; 13179 } 13180 13181 function computeSegVCoords(segs, colDate, slatCoords = null, eventMinHeight = 0) { 13182 let vcoords = []; 13183 if (slatCoords) { 13184 for (let i = 0; i < segs.length; i += 1) { 13185 let seg = segs[i]; 13186 let spanStart = slatCoords.computeDateTop(seg.start, colDate); 13187 let spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( 13188 slatCoords.computeDateTop(seg.end, colDate)); 13189 vcoords.push({ 13190 start: Math.round(spanStart), 13191 end: Math.round(spanEnd), // 13192 }); 13193 } 13194 } 13195 return vcoords; 13196 } 13197 function computeFgSegPlacements(segs, segVCoords, // might not have for every seg 13198 eventOrderStrict, eventMaxStack) { 13199 let segInputs = []; 13200 let dumbSegs = []; // segs without coords 13201 for (let i = 0; i < segs.length; i += 1) { 13202 let vcoords = segVCoords[i]; 13203 if (vcoords) { 13204 segInputs.push({ 13205 index: i, 13206 thickness: 1, 13207 span: vcoords, 13208 }); 13209 } 13210 else { 13211 dumbSegs.push(segs[i]); 13212 } 13213 } 13214 let { segRects, hiddenGroups } = buildPositioning(segInputs, eventOrderStrict, eventMaxStack); 13215 let segPlacements = []; 13216 for (let segRect of segRects) { 13217 segPlacements.push({ 13218 seg: segs[segRect.index], 13219 rect: segRect, 13220 }); 13221 } 13222 for (let dumbSeg of dumbSegs) { 13223 segPlacements.push({ seg: dumbSeg, rect: null }); 13224 } 13225 return { segPlacements, hiddenGroups }; 13226 } 13227 13228 const DEFAULT_TIME_FORMAT$1 = createFormatter({ 13229 hour: 'numeric', 13230 minute: '2-digit', 13231 meridiem: false, 13232 }); 13233 class TimeColEvent extends BaseComponent { 13234 render() { 13235 return (h(StandardEvent, Object.assign({}, this.props, { elClasses: [ 13236 'fc-timegrid-event', 13237 'fc-v-event', 13238 this.props.isShort && 'fc-timegrid-event-short', 13239 ], defaultTimeFormat: DEFAULT_TIME_FORMAT$1 }))); 13240 } 13241 } 13242 13243 class TimeCol extends BaseComponent { 13244 constructor() { 13245 super(...arguments); 13246 this.sortEventSegs = memoize(sortEventSegs); 13247 } 13248 // TODO: memoize event-placement? 13249 render() { 13250 let { props, context } = this; 13251 let { options } = context; 13252 let isSelectMirror = options.selectMirror; 13253 let mirrorSegs = // yuck 13254 (props.eventDrag && props.eventDrag.segs) || 13255 (props.eventResize && props.eventResize.segs) || 13256 (isSelectMirror && props.dateSelectionSegs) || 13257 []; 13258 let interactionAffectedInstances = // TODO: messy way to compute this 13259 (props.eventDrag && props.eventDrag.affectedInstances) || 13260 (props.eventResize && props.eventResize.affectedInstances) || 13261 {}; 13262 let sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder); 13263 return (h(DayCellContainer, { elTag: "td", elRef: props.elRef, elClasses: [ 13264 'fc-timegrid-col', 13265 ...(props.extraClassNames || []), 13266 ], elAttrs: Object.assign({ role: 'gridcell' }, props.extraDataAttrs), date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraRenderProps: props.extraRenderProps }, (InnerContent) => (h("div", { className: "fc-timegrid-col-frame" }, 13267 h("div", { className: "fc-timegrid-col-bg" }, 13268 this.renderFillSegs(props.businessHourSegs, 'non-business'), 13269 this.renderFillSegs(props.bgEventSegs, 'bg-event'), 13270 this.renderFillSegs(props.dateSelectionSegs, 'highlight')), 13271 h("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), 13272 h("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))), 13273 h("div", { className: "fc-timegrid-now-indicator-container" }, this.renderNowIndicator(props.nowIndicatorSegs)), 13274 hasCustomDayCellContent(options) && (h(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-col-misc'] })))))); 13275 } 13276 renderFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) { 13277 let { props } = this; 13278 if (props.forPrint) { 13279 return renderPlainFgSegs(sortedFgSegs, props); 13280 } 13281 return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting); 13282 } 13283 renderPositionedFgSegs(segs, // if not mirror, needs to be sorted 13284 segIsInvisible, isDragging, isResizing, isDateSelecting) { 13285 let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = this.context.options; 13286 let { date, slatCoords, eventSelection, todayRange, nowDate } = this.props; 13287 let isMirror = isDragging || isResizing || isDateSelecting; 13288 let segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); 13289 let { segPlacements, hiddenGroups } = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack); 13290 return (h(p, null, 13291 this.renderHiddenGroups(hiddenGroups, segs), 13292 segPlacements.map((segPlacement) => { 13293 let { seg, rect } = segPlacement; 13294 let instanceId = seg.eventRange.instance.instanceId; 13295 let isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); 13296 let vStyle = computeSegVStyle(rect && rect.span); 13297 let hStyle = (!isMirror && rect) ? this.computeSegHStyle(rect) : { left: 0, right: 0 }; 13298 let isInset = Boolean(rect) && rect.stackForward > 0; 13299 let isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem 13300 return (h("div", { className: 'fc-timegrid-event-harness' + 13301 (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: Object.assign(Object.assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, 13302 h(TimeColEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); 13303 }))); 13304 } 13305 // will already have eventMinHeight applied because segInputs already had it 13306 renderHiddenGroups(hiddenGroups, segs) { 13307 let { extraDateSpan, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize } = this.props; 13308 return (h(p, null, hiddenGroups.map((hiddenGroup) => { 13309 let positionCss = computeSegVStyle(hiddenGroup.span); 13310 let hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); 13311 return (h(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 })); 13312 }))); 13313 } 13314 renderFillSegs(segs, fillType) { 13315 let { props, context } = this; 13316 let segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated 13317 let children = segVCoords.map((vcoords, i) => { 13318 let seg = segs[i]; 13319 return (h("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? 13320 h(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : 13321 renderFill(fillType))); 13322 }); 13323 return h(p, null, children); 13324 } 13325 renderNowIndicator(segs) { 13326 let { slatCoords, date } = this.props; 13327 if (!slatCoords) { 13328 return null; 13329 } 13330 return segs.map((seg, i) => (h(NowIndicatorContainer 13331 // key doesn't matter. will only ever be one 13332 , { 13333 // key doesn't matter. will only ever be one 13334 key: i, elClasses: ['fc-timegrid-now-indicator-line'], elStyle: { 13335 top: slatCoords.computeDateTop(seg.start, date), 13336 }, isAxis: false, date: date }))); 13337 } 13338 computeSegHStyle(segHCoords) { 13339 let { isRtl, options } = this.context; 13340 let shouldOverlap = options.slotEventOverlap; 13341 let nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point 13342 let farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point 13343 let left; // amount of space from left edge, a fraction of the total width 13344 let right; // amount of space from right edge, a fraction of the total width 13345 if (shouldOverlap) { 13346 // double the width, but don't go beyond the maximum forward coordinate (1.0) 13347 farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); 13348 } 13349 if (isRtl) { 13350 left = 1 - farCoord; 13351 right = nearCoord; 13352 } 13353 else { 13354 left = nearCoord; 13355 right = 1 - farCoord; 13356 } 13357 let props = { 13358 zIndex: segHCoords.stackDepth + 1, 13359 left: left * 100 + '%', 13360 right: right * 100 + '%', 13361 }; 13362 if (shouldOverlap && !segHCoords.stackForward) { 13363 // add padding to the edge so that forward stacked events don't cover the resizer's icon 13364 props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width 13365 } 13366 return props; 13367 } 13368 } 13369 function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }) { 13370 let hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || 13371 (eventResize ? eventResize.affectedInstances : null) || 13372 {}; 13373 return (h(p, null, sortedFgSegs.map((seg) => { 13374 let instanceId = seg.eventRange.instance.instanceId; 13375 return (h("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, 13376 h(TimeColEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); 13377 }))); 13378 } 13379 function computeSegVStyle(segVCoords) { 13380 if (!segVCoords) { 13381 return { top: '', bottom: '' }; 13382 } 13383 return { 13384 top: segVCoords.start, 13385 bottom: -segVCoords.end, 13386 }; 13387 } 13388 function compileSegsFromEntries(segEntries, allSegs) { 13389 return segEntries.map((segEntry) => allSegs[segEntry.index]); 13390 } 13391 13392 class TimeColsContent extends BaseComponent { 13393 constructor() { 13394 super(...arguments); 13395 this.splitFgEventSegs = memoize(splitSegsByCol); 13396 this.splitBgEventSegs = memoize(splitSegsByCol); 13397 this.splitBusinessHourSegs = memoize(splitSegsByCol); 13398 this.splitNowIndicatorSegs = memoize(splitSegsByCol); 13399 this.splitDateSelectionSegs = memoize(splitSegsByCol); 13400 this.splitEventDrag = memoize(splitInteractionByCol); 13401 this.splitEventResize = memoize(splitInteractionByCol); 13402 this.rootElRef = y(); 13403 this.cellElRefs = new RefMap(); 13404 } 13405 render() { 13406 let { props, context } = this; 13407 let nowIndicatorTop = context.options.nowIndicator && 13408 props.slatCoords && 13409 props.slatCoords.safeComputeTop(props.nowDate); // might return void 13410 let colCnt = props.cells.length; 13411 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); 13412 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); 13413 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); 13414 let nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); 13415 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); 13416 let eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); 13417 let eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); 13418 return (h("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, 13419 h("table", { role: "presentation", style: { 13420 minWidth: props.tableMinWidth, 13421 width: props.clientWidth, 13422 } }, 13423 props.tableColGroupNode, 13424 h("tbody", { role: "presentation" }, 13425 h("tr", { role: "row" }, 13426 props.axis && (h("td", { "aria-hidden": true, className: "fc-timegrid-col fc-timegrid-axis" }, 13427 h("div", { className: "fc-timegrid-col-frame" }, 13428 h("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (h(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: props.nowDate })))))), 13429 props.cells.map((cell, i) => (h(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 })))))))); 13430 } 13431 componentDidMount() { 13432 this.updateCoords(); 13433 } 13434 componentDidUpdate() { 13435 this.updateCoords(); 13436 } 13437 updateCoords() { 13438 let { props } = this; 13439 if (props.onColCoords && 13440 props.clientWidth !== null // means sizing has stabilized 13441 ) { 13442 props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal 13443 false)); 13444 } 13445 } 13446 } 13447 function collectCellEls(elMap, cells) { 13448 return cells.map((cell) => elMap[cell.key]); 13449 } 13450 13451 /* A component that renders one or more columns of vertical time slots 13452 ----------------------------------------------------------------------------------------------------------------------*/ 13453 class TimeCols extends DateComponent { 13454 constructor() { 13455 super(...arguments); 13456 this.processSlotOptions = memoize(processSlotOptions); 13457 this.state = { 13458 slatCoords: null, 13459 }; 13460 this.handleRootEl = (el) => { 13461 if (el) { 13462 this.context.registerInteractiveComponent(this, { 13463 el, 13464 isHitComboAllowed: this.props.isHitComboAllowed, 13465 }); 13466 } 13467 else { 13468 this.context.unregisterInteractiveComponent(this); 13469 } 13470 }; 13471 this.handleScrollRequest = (request) => { 13472 let { onScrollTopRequest } = this.props; 13473 let { slatCoords } = this.state; 13474 if (onScrollTopRequest && slatCoords) { 13475 if (request.time) { 13476 let top = slatCoords.computeTimeTop(request.time); 13477 top = Math.ceil(top); // zoom can give weird floating-point values. rather scroll a little bit further 13478 if (top) { 13479 top += 1; // to overcome top border that slots beyond the first have. looks better 13480 } 13481 onScrollTopRequest(top); 13482 } 13483 return true; 13484 } 13485 return false; 13486 }; 13487 this.handleColCoords = (colCoords) => { 13488 this.colCoords = colCoords; 13489 }; 13490 this.handleSlatCoords = (slatCoords) => { 13491 this.setState({ slatCoords }); 13492 if (this.props.onSlatCoords) { 13493 this.props.onSlatCoords(slatCoords); 13494 } 13495 }; 13496 } 13497 render() { 13498 let { props, state } = this; 13499 return (h("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: { 13500 // these props are important to give this wrapper correct dimensions for interactions 13501 // TODO: if we set it here, can we avoid giving to inner tables? 13502 width: props.clientWidth, 13503 minWidth: props.tableMinWidth, 13504 } }, 13505 h(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 }), 13506 h(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 }))); 13507 } 13508 componentDidMount() { 13509 this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); 13510 } 13511 componentDidUpdate(prevProps) { 13512 this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); 13513 } 13514 componentWillUnmount() { 13515 this.scrollResponder.detach(); 13516 } 13517 queryHit(positionLeft, positionTop) { 13518 let { dateEnv, options } = this.context; 13519 let { colCoords } = this; 13520 let { dateProfile } = this.props; 13521 let { slatCoords } = this.state; 13522 let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration); 13523 let colIndex = colCoords.leftToIndex(positionLeft); 13524 let slatIndex = slatCoords.positions.topToIndex(positionTop); 13525 if (colIndex != null && slatIndex != null) { 13526 let cell = this.props.cells[colIndex]; 13527 let slatTop = slatCoords.positions.tops[slatIndex]; 13528 let slatHeight = slatCoords.positions.getHeight(slatIndex); 13529 let partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 13530 let localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat 13531 let snapIndex = slatIndex * snapsPerSlot + localSnapIndex; 13532 let dayDate = this.props.cells[colIndex].date; 13533 let time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); 13534 let start = dateEnv.add(dayDate, time); 13535 let end = dateEnv.add(start, snapDuration); 13536 return { 13537 dateProfile, 13538 dateSpan: Object.assign({ range: { start, end }, allDay: false }, cell.extraDateSpan), 13539 dayEl: colCoords.els[colIndex], 13540 rect: { 13541 left: colCoords.lefts[colIndex], 13542 right: colCoords.rights[colIndex], 13543 top: slatTop, 13544 bottom: slatTop + slatHeight, 13545 }, 13546 layer: 0, 13547 }; 13548 } 13549 return null; 13550 } 13551 } 13552 function processSlotOptions(slotDuration, snapDurationOverride) { 13553 let snapDuration = snapDurationOverride || slotDuration; 13554 let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); 13555 if (snapsPerSlot === null) { 13556 snapDuration = slotDuration; 13557 snapsPerSlot = 1; 13558 // TODO: say warning? 13559 } 13560 return { snapDuration, snapsPerSlot }; 13561 } 13562 13563 class DayTimeColsSlicer extends Slicer { 13564 sliceRange(range, dayRanges) { 13565 let segs = []; 13566 for (let col = 0; col < dayRanges.length; col += 1) { 13567 let segRange = intersectRanges(range, dayRanges[col]); 13568 if (segRange) { 13569 segs.push({ 13570 start: segRange.start, 13571 end: segRange.end, 13572 isStart: segRange.start.valueOf() === range.start.valueOf(), 13573 isEnd: segRange.end.valueOf() === range.end.valueOf(), 13574 col, 13575 }); 13576 } 13577 } 13578 return segs; 13579 } 13580 } 13581 13582 class DayTimeCols extends DateComponent { 13583 constructor() { 13584 super(...arguments); 13585 this.buildDayRanges = memoize(buildDayRanges); 13586 this.slicer = new DayTimeColsSlicer(); 13587 this.timeColsRef = y(); 13588 } 13589 render() { 13590 let { props, context } = this; 13591 let { dateProfile, dayTableModel } = props; 13592 let isNowIndicator = context.options.nowIndicator; 13593 let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); 13594 // give it the first row of cells 13595 // TODO: would move this further down hierarchy, but sliceNowDate needs it 13596 return (h(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, (nowDate, todayRange) => (h(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: isNowIndicator && this.slicer.sliceNowDate(nowDate, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords }))))); 13597 } 13598 } 13599 function buildDayRanges(dayTableModel, dateProfile, dateEnv) { 13600 let ranges = []; 13601 for (let date of dayTableModel.headerDates) { 13602 ranges.push({ 13603 start: dateEnv.add(date, dateProfile.slotMinTime), 13604 end: dateEnv.add(date, dateProfile.slotMaxTime), 13605 }); 13606 } 13607 return ranges; 13608 } 13609 13610 // potential nice values for the slot-duration and interval-duration 13611 // from largest to smallest 13612 const STOCK_SUB_DURATIONS = [ 13613 { hours: 1 }, 13614 { minutes: 30 }, 13615 { minutes: 15 }, 13616 { seconds: 30 }, 13617 { seconds: 15 }, 13618 ]; 13619 function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { 13620 let dayStart = new Date(0); 13621 let slatTime = slotMinTime; 13622 let slatIterator = createDuration(0); 13623 let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); 13624 let metas = []; 13625 while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { 13626 let date = dateEnv.add(dayStart, slatTime); 13627 let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; 13628 metas.push({ 13629 date, 13630 time: slatTime, 13631 key: date.toISOString(), 13632 isoTimeStr: formatIsoTimeString(date), 13633 isLabeled, 13634 }); 13635 slatTime = addDurations(slatTime, slotDuration); 13636 slatIterator = addDurations(slatIterator, slotDuration); 13637 } 13638 return metas; 13639 } 13640 // Computes an automatic value for slotLabelInterval 13641 function computeLabelInterval(slotDuration) { 13642 let i; 13643 let labelInterval; 13644 let slotsPerLabel; 13645 // find the smallest stock label interval that results in more than one slots-per-label 13646 for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { 13647 labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); 13648 slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); 13649 if (slotsPerLabel !== null && slotsPerLabel > 1) { 13650 return labelInterval; 13651 } 13652 } 13653 return slotDuration; // fall back 13654 } 13655 13656 class DayTimeColsView extends TimeColsView { 13657 constructor() { 13658 super(...arguments); 13659 this.buildTimeColsModel = memoize(buildTimeColsModel); 13660 this.buildSlatMetas = memoize(buildSlatMetas); 13661 } 13662 render() { 13663 let { options, dateEnv, dateProfileGenerator } = this.context; 13664 let { props } = this; 13665 let { dateProfile } = props; 13666 let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); 13667 let splitProps = this.allDaySplitter.splitProps(props); 13668 let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); 13669 let { dayMinWidth } = options; 13670 let hasAttachedAxis = !dayMinWidth; 13671 let hasDetachedAxis = dayMinWidth; 13672 let headerContent = options.dayHeaders && (h(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); 13673 let allDayContent = (options.allDaySlot !== false) && ((contentArg) => (h(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())))); 13674 let timeGridContent = (contentArg) => (h(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 }))); 13675 return hasDetachedAxis 13676 ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) 13677 : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); 13678 } 13679 }
13680 function buildTimeColsModel(dateProfile, dateProfileGenerator) { 13681 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); 13682 return new DayTableModel(daySeries, false); 13683 } 13684 13685 const OPTION_REFINERS$1 = { 13686 allDaySlot: Boolean, 13687 }; 13688 13689 var css_248z$1 = ".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}"; 13690 injectStyles(css_248z$1); 13691 13692 var index$1 = createPlugin({ 13693 name: '@fullcalendar/timegrid', 13694 initialView: 'timeGridWeek', 13695 optionRefiners: OPTION_REFINERS$1, 13696 views: { 13697 timeGrid: { 13698 component: DayTimeColsView, 13699 usesMinMaxTime: true, 13700 allDaySlot: true, 13701 slotDuration: '00:30:00', 13702 slotEventOverlap: true, // a bad name. confused with overlap/constraint system 13703 }, 13704 timeGridDay: { 13705 type: 'timeGrid', 13706 duration: { days: 1 }, 13707 }, 13708 timeGridWeek: { 13709 type: 'timeGrid', 13710 duration: { weeks: 1 }, 13711 }, 13712 }, 13713 }); 13714 13715 class ListViewHeaderRow extends BaseComponent { 13716 constructor() { 13717 super(...arguments); 13718 this.state = { 13719 textId: getUniqueDomId(), 13720 }; 13721 } 13722 render() { 13723 let { theme, dateEnv, options, viewApi } = this.context; 13724 let { cellId, dayDate, todayRange } = this.props; 13725 let { textId } = this.state;
vendor: 17,170 bytes, lines 13726-14041
13726 let dayMeta = getDateMeta(dayDate, todayRange); 13727 // will ever be falsy? 13728 let text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; 13729 // will ever be falsy? also, BAD NAME "alt" 13730 let sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; 13731 let renderProps = Object.assign({ date: dateEnv.toDate(dayDate), view: viewApi, textId, 13732 text, 13733 sideText, navLinkAttrs: buildNavLinkAttrs(this.context, dayDate), sideNavLinkAttrs: buildNavLinkAttrs(this.context, dayDate, 'day', false) }, dayMeta); 13734 // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) 13735 return (h(ContentContainer, { elTag: "tr", elClasses: [ 13736 'fc-list-day', 13737 ...getDayClassNames(dayMeta, theme), 13738 ], elAttrs: { 13739 'data-date': formatDayString(dayDate), 13740 }, renderProps: renderProps, generatorName: "dayHeaderContent", generator: options.dayHeaderContent || renderInnerContent, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => ( // TODO: force-hide top border based on :first-child 13741 h("th", { scope: "colgroup", colSpan: 3, id: cellId, "aria-labelledby": textId }, 13742 h(InnerContent, { elTag: "div", elClasses: [ 13743 'fc-list-day-cushion', 13744 theme.getClass('tableCellShaded'), 13745 ] }))))); 13746 } 13747 } 13748 function renderInnerContent(props) { 13749 return (h(p, null, 13750 props.text && (h("a", Object.assign({ id: props.textId, className: "fc-list-day-text" }, props.navLinkAttrs), props.text)), 13751 props.sideText && ( /* not keyboard tabbable */h("a", Object.assign({ "aria-hidden": true, className: "fc-list-day-side-text" }, props.sideNavLinkAttrs), props.sideText)))); 13752 } 13753 13754 const DEFAULT_TIME_FORMAT = createFormatter({ 13755 hour: 'numeric', 13756 minute: '2-digit', 13757 meridiem: 'short', 13758 }); 13759 class ListViewEventRow extends BaseComponent { 13760 render() { 13761 let { props, context } = this; 13762 let { options } = context; 13763 let { seg, timeHeaderId, eventHeaderId, dateHeaderId } = props; 13764 let timeFormat = options.eventTimeFormat || DEFAULT_TIME_FORMAT; 13765 return (h(EventContainer, Object.assign({}, props, { elTag: "tr", elClasses: [ 13766 'fc-list-event', 13767 seg.eventRange.def.url && 'fc-event-forced-url', 13768 ], defaultGenerator: () => renderEventInnerContent(seg, context) /* weird */, seg: seg, timeText: "", disableDragging: true, disableResizing: true }), (InnerContent, eventContentArg) => (h(p, null, 13769 buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId), 13770 h("td", { "aria-hidden": true, className: "fc-list-event-graphic" }, 13771 h("span", { className: "fc-list-event-dot", style: { 13772 borderColor: eventContentArg.borderColor || eventContentArg.backgroundColor, 13773 } })), 13774 h(InnerContent, { elTag: "td", elClasses: ['fc-list-event-title'], elAttrs: { headers: `${eventHeaderId} ${dateHeaderId}` } }))))); 13775 } 13776 } 13777 function renderEventInnerContent(seg, context) { 13778 let interactiveAttrs = getSegAnchorAttrs(seg, context); 13779 return (h("a", Object.assign({}, interactiveAttrs), seg.eventRange.def.title)); 13780 } 13781 function buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId) { 13782 let { options } = context; 13783 if (options.displayEventTime !== false) { 13784 let eventDef = seg.eventRange.def; 13785 let eventInstance = seg.eventRange.instance; 13786 let doAllDay = false; 13787 let timeText; 13788 if (eventDef.allDay) { 13789 doAllDay = true; 13790 } 13791 else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? 13792 if (seg.isStart) { 13793 timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); 13794 } 13795 else if (seg.isEnd) { 13796 timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); 13797 } 13798 else { 13799 doAllDay = true; 13800 } 13801 } 13802 else { 13803 timeText = buildSegTimeText(seg, timeFormat, context); 13804 } 13805 if (doAllDay) { 13806 let renderProps = { 13807 text: context.options.allDayText, 13808 view: context.viewApi, 13809 }; 13810 return (h(ContentContainer, { elTag: "td", elClasses: ['fc-list-event-time'], elAttrs: { 13811 headers: `${timeHeaderId} ${dateHeaderId}`, 13812 }, renderProps: renderProps, generatorName: "allDayContent", generator: options.allDayContent || renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount })); 13813 } 13814 return (h("td", { className: "fc-list-event-time" }, timeText)); 13815 } 13816 return null; 13817 } 13818 function renderAllDayInner(renderProps) { 13819 return renderProps.text; 13820 } 13821 13822 /* 13823 Responsible for the scroller, and forwarding event-related actions into the "grid". 13824 */ 13825 class ListView extends DateComponent { 13826 constructor() { 13827 super(...arguments); 13828 this.computeDateVars = memoize(computeDateVars); 13829 this.eventStoreToSegs = memoize(this._eventStoreToSegs); 13830 this.state = { 13831 timeHeaderId: getUniqueDomId(), 13832 eventHeaderId: getUniqueDomId(), 13833 dateHeaderIdRoot: getUniqueDomId(), 13834 }; 13835 this.setRootEl = (rootEl) => { 13836 if (rootEl) { 13837 this.context.registerInteractiveComponent(this, { 13838 el: rootEl, 13839 }); 13840 } 13841 else { 13842 this.context.unregisterInteractiveComponent(this); 13843 } 13844 }; 13845 } 13846 render() { 13847 let { props, context } = this; 13848 let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile); 13849 let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); 13850 return (h(ViewContainer$1, { elRef: this.setRootEl, elClasses: [ 13851 'fc-list', 13852 context.theme.getClass('table'), 13853 context.options.stickyHeaderDates !== false ? 13854 'fc-list-sticky' : 13855 '', 13856 ], viewSpec: context.viewSpec }, 13857 h(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? 13858 this.renderSegList(eventSegs, dayDates) : 13859 this.renderEmptyMessage()))); 13860 } 13861 renderEmptyMessage() { 13862 let { options, viewApi } = this.context; 13863 let renderProps = { 13864 text: options.noEventsText, 13865 view: viewApi, 13866 }; 13867 return (h(ContentContainer, { elTag: "div", elClasses: ['fc-list-empty'], renderProps: renderProps, generatorName: "noEventsContent", generator: options.noEventsContent || renderNoEventsInner, classNameGenerator: options.noEventsClassNames, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, (InnerContent) => (h(InnerContent, { elTag: "div", elClasses: ['fc-list-empty-cushion'] })))); 13868 } 13869 renderSegList(allSegs, dayDates) { 13870 let { theme, options } = this.context; 13871 let { timeHeaderId, eventHeaderId, dateHeaderIdRoot } = this.state; 13872 let segsByDay = groupSegsByDay(allSegs); // sparse array 13873 return (h(NowTimer, { unit: "day" }, (nowDate, todayRange) => { 13874 let innerNodes = []; 13875 for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { 13876 let daySegs = segsByDay[dayIndex]; 13877 if (daySegs) { // sparse array, so might be undefined 13878 let dayStr = formatDayString(dayDates[dayIndex]); 13879 let dateHeaderId = dateHeaderIdRoot + '-' + dayStr; 13880 // append a day header 13881 innerNodes.push(h(ListViewHeaderRow, { key: dayStr, cellId: dateHeaderId, dayDate: dayDates[dayIndex], todayRange: todayRange })); 13882 daySegs = sortEventSegs(daySegs, options.eventOrder); 13883 for (let seg of daySegs) { 13884 innerNodes.push(h(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)))); 13885 } 13886 } 13887 } 13888 return (h("table", { className: 'fc-list-table ' + theme.getClass('table') }, 13889 h("thead", null, 13890 h("tr", null, 13891 h("th", { scope: "col", id: timeHeaderId }, options.timeHint), 13892 h("th", { scope: "col", "aria-hidden": true }), 13893 h("th", { scope: "col", id: eventHeaderId }, options.eventHint))), 13894 h("tbody", null, innerNodes))); 13895 })); 13896 } 13897 _eventStoreToSegs(eventStore, eventUiBases, dayRanges) { 13898 return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); 13899 } 13900 eventRangesToSegs(eventRanges, dayRanges) { 13901 let segs = []; 13902 for (let eventRange of eventRanges) { 13903 segs.push(...this.eventRangeToSegs(eventRange, dayRanges)); 13904 } 13905 return segs; 13906 } 13907 eventRangeToSegs(eventRange, dayRanges) { 13908 let { dateEnv } = this.context; 13909 let { nextDayThreshold } = this.context.options; 13910 let range = eventRange.range; 13911 let allDay = eventRange.def.allDay; 13912 let dayIndex; 13913 let segRange; 13914 let seg; 13915 let segs = []; 13916 for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { 13917 segRange = intersectRanges(range, dayRanges[dayIndex]); 13918 if (segRange) { 13919 seg = { 13920 component: this, 13921 eventRange, 13922 start: segRange.start, 13923 end: segRange.end, 13924 isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), 13925 isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), 13926 dayIndex, 13927 }; 13928 segs.push(seg); 13929 // detect when range won't go fully into the next day, 13930 // and mutate the latest seg to the be the end. 13931 if (!seg.isEnd && !allDay && 13932 dayIndex + 1 < dayRanges.length && 13933 range.end < 13934 dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { 13935 seg.end = range.end; 13936 seg.isEnd = true; 13937 break; 13938 } 13939 } 13940 } 13941 return segs; 13942 } 13943 } 13944 function renderNoEventsInner(renderProps) { 13945 return renderProps.text; 13946 } 13947 function computeDateVars(dateProfile) { 13948 let dayStart = startOfDay(dateProfile.renderRange.start); 13949 let viewEnd = dateProfile.renderRange.end; 13950 let dayDates = []; 13951 let dayRanges = []; 13952 while (dayStart < viewEnd) { 13953 dayDates.push(dayStart); 13954 dayRanges.push({ 13955 start: dayStart, 13956 end: addDays(dayStart, 1), 13957 }); 13958 dayStart = addDays(dayStart, 1); 13959 } 13960 return { dayDates, dayRanges }; 13961 } 13962 // Returns a sparse array of arrays, segs grouped by their dayIndex 13963 function groupSegsByDay(segs) { 13964 let segsByDay = []; // sparse array 13965 let i; 13966 let seg; 13967 for (i = 0; i < segs.length; i += 1) { 13968 seg = segs[i]; 13969 (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) 13970 .push(seg); 13971 } 13972 return segsByDay; 13973 } 13974 13975 const OPTION_REFINERS = { 13976 listDayFormat: createFalsableFormatter, 13977 listDaySideFormat: createFalsableFormatter, 13978 noEventsClassNames: identity, 13979 noEventsContent: identity, 13980 noEventsDidMount: identity, 13981 noEventsWillUnmount: identity, 13982 // noEventsText is defined in base options 13983 }; 13984 function createFalsableFormatter(input) { 13985 return input === false ? null : createFormatter(input); 13986 } 13987 13988 var css_248z = ":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}"; 13989 injectStyles(css_248z); 13990 13991 var index = createPlugin({ 13992 name: '@fullcalendar/list', 13993 optionRefiners: OPTION_REFINERS, 13994 views: { 13995 list: { 13996 component: ListView, 13997 buttonTextKey: 'list', 13998 listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" 13999 }, 14000 listDay: { 14001 type: 'list', 14002 duration: { days: 1 }, 14003 listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar 14004 }, 14005 listWeek: { 14006 type: 'list', 14007 duration: { weeks: 1 }, 14008 listDayFormat: { weekday: 'long' }, 14009 listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, 14010 }, 14011 listMonth: { 14012 type: 'list', 14013 duration: { month: 1 }, 14014 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have 14015 }, 14016 listYear: { 14017 type: 'list', 14018 duration: { year: 1 }, 14019 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have 14020 }, 14021 }, 14022 }); 14023 14024 globalPlugins.push(index$3, index$2, index$1, index); 14025 14026 exports.Calendar = Calendar; 14027 exports.Draggable = ExternalDraggable; 14028 exports.Internal = internal; 14029 exports.JsonRequestError = JsonRequestError; 14030 exports.ThirdPartyDraggable = ThirdPartyDraggable; 14031 exports.createPlugin = createPlugin; 14032 exports.formatDate = formatDate; 14033 exports.formatRange = formatRange; 14034 exports.sliceEvents = sliceEvents; 14035 exports.version = version; 14036 14037 Object.defineProperty(exports, '__esModule', { value: true }); 14038 14039 return exports; 14040 14041})({});
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.