Files
Kwesi Banson Jnr b71f5c7553 Initial commit
2026-07-17 09:58:21 +00:00

42698 lines
1.6 MiB

/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 464
(module) {
!function(t,e){ true?module.exports=e():0}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",c="month",f="quarter",h="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,c),s=n-i<0,u=e.clone().add(r+(s?-1:1),c);return+(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return{M:c,y:h,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:f}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p="$isDayjsObject",S=function(t){return t instanceof _||!(!t||!t[p])},w=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},O=function(t,e){if(S(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},b=v;b.l=w,b.i=S,b.w=function(t,e){return O(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=w(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[p]=!0}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(b.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return b},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=O(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return O(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<O(t)},m.$g=function(t,e,n){return b.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!b.u(e)||e,f=b.p(t),l=function(t,e){var i=b.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return b.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(f){case h:return r?l(1,0):l(31,11);case c:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=b.p(t),f="set"+(this.$u?"UTC":""),l=(n={},n[a]=f+"Date",n[d]=f+"Date",n[c]=f+"Month",n[h]=f+"FullYear",n[u]=f+"Hours",n[s]=f+"Minutes",n[i]=f+"Seconds",n[r]=f+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===c||o===h){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[b.p(t)]()},m.add=function(r,f){var d,l=this;r=Number(r);var $=b.p(f),y=function(t){var e=O(l);return b.w(e.date(e.date()+Math.round(t*r)),l)};if($===c)return this.set(c,this.$M+r);if($===h)return this.set(h,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return b.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=b.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,c=n.months,f=n.meridiem,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},d=function(t){return b.s(s%12||12,t,"0")},$=f||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r};return r.replace(y,(function(t,r){return r||function(t){switch(t){case"YY":return String(e.$y).slice(-2);case"YYYY":return b.s(e.$y,4,"0");case"M":return a+1;case"MM":return b.s(a+1,2,"0");case"MMM":return h(n.monthsShort,a,c,3);case"MMMM":return h(c,a);case"D":return e.$D;case"DD":return b.s(e.$D,2,"0");case"d":return String(e.$W);case"dd":return h(n.weekdaysMin,e.$W,o,2);case"ddd":return h(n.weekdaysShort,e.$W,o,3);case"dddd":return o[e.$W];case"H":return String(s);case"HH":return b.s(s,2,"0");case"h":return d(1);case"hh":return d(2);case"a":return $(s,u,!0);case"A":return $(s,u,!1);case"m":return String(u);case"mm":return b.s(u,2,"0");case"s":return String(e.$s);case"ss":return b.s(e.$s,2,"0");case"SSS":return b.s(e.$ms,3,"0");case"Z":return i}return null}(t)||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=this,M=b.p(d),m=O(r),v=(m.utcOffset()-this.utcOffset())*e,g=this-m,D=function(){return b.m(y,m)};switch(M){case h:$=D()/12;break;case c:$=D();break;case f:$=D()/3;break;case o:$=(g-v)/6048e5;break;case a:$=(g-v)/864e5;break;case u:$=g/n;break;case s:$=g/e;break;case i:$=g/t;break;default:$=g}return l?$:b.a($)},m.daysInMonth=function(){return this.endOf(c).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=w(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return b.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),k=_.prototype;return O.prototype=k,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",c],["$y",h],["$D",d]].forEach((function(t){k[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),O.extend=function(t,e){return t.$i||(t(e,_,O),t.$i=!0),O},O.locale=w,O.isDayjs=S,O.unix=function(t){return O(1e3*t)},O.en=D[g],O.Ls=D,O.p={},O}));
/***/ },
/***/ 258
(module) {
!function(e,t){ true?module.exports=t():0}(this,(function(){"use strict";return function(e,t){var r=t.prototype,n=r.format;r.format=function(e){var t=this,r=this.$locale();if(!this.isValid())return n.bind(this)(e);var s=this.$utils(),a=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,(function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return r.ordinal(t.$D);case"gggg":return t.weekYear();case"GGGG":return t.isoWeekYear();case"wo":return r.ordinal(t.week(),"W");case"w":case"ww":return s.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return s.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return s.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();case"z":return"["+t.offsetName()+"]";case"zzz":return"["+t.offsetName("long")+"]";default:return e}}));return n.bind(this)(a)}}}));
/***/ },
/***/ 630
(module) {
!function(e,t){ true?module.exports=t():0}(this,(function(){"use strict";var e={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},t=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,n=/\d/,r=/\d\d/,i=/\d\d?/,o=/\d*[^-_:/,()\s\d]+/,s={},a=function(e){return(e=+e)+(e>68?1900:2e3)};var f=function(e){return function(t){this[e]=+t}},h=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],u=function(e){var t=s[e];return t&&(t.indexOf?t:t.s.concat(t.f))},d=function(e,t){var n,r=s.meridiem;if(r){for(var i=1;i<=24;i+=1)if(e.indexOf(r(i,0,t))>-1){n=i>12;break}}else n=e===(t?"pm":"PM");return n},c={A:[o,function(e){this.afternoon=d(e,!1)}],a:[o,function(e){this.afternoon=d(e,!0)}],Q:[n,function(e){this.month=3*(e-1)+1}],S:[n,function(e){this.milliseconds=100*+e}],SS:[r,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[i,f("seconds")],ss:[i,f("seconds")],m:[i,f("minutes")],mm:[i,f("minutes")],H:[i,f("hours")],h:[i,f("hours")],HH:[i,f("hours")],hh:[i,f("hours")],D:[i,f("day")],DD:[r,f("day")],Do:[o,function(e){var t=s.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],w:[i,f("week")],ww:[r,f("week")],M:[i,f("month")],MM:[r,f("month")],MMM:[o,function(e){var t=u("months"),n=(u("monthsShort")||t.map((function(e){return e.slice(0,3)}))).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[o,function(e){var t=u("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,f("year")],YY:[r,function(e){this.year=a(e)}],YYYY:[/\d{4}/,f("year")],Z:h,ZZ:h};function l(n){var r,i;r=n,i=s&&s.formats;for(var o=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,n,r){var o=r&&r.toUpperCase();return n||i[r]||e[r]||i[o].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}))).match(t),a=o.length,f=0;f<a;f+=1){var h=o[f],u=c[h],d=u&&u[0],l=u&&u[1];o[f]=l?{regex:d,parser:l}:h.replace(/^\[|\]$/g,"")}return function(e){for(var t={},n=0,r=0;n<a;n+=1){var i=o[n];if("string"==typeof i)r+=i.length;else{var s=i.regex,f=i.parser,h=e.slice(r),u=s.exec(h)[0];f.call(t,u),e=e.replace(u,"")}}return function(e){var t=e.afternoon;if(void 0!==t){var n=e.hours;t?n<12&&(e.hours+=12):12===n&&(e.hours=0),delete e.afternoon}}(t),t}}return function(e,t,n){n.p.customParseFormat=!0,e&&e.parseTwoDigitYear&&(a=e.parseTwoDigitYear);var r=t.prototype,i=r.parse;r.parse=function(e){var t=e.date,r=e.utc,o=e.args;this.$u=r;var a=o[1];if("string"==typeof a){var f=!0===o[2],h=!0===o[3],u=f||h,d=o[2];h&&(d=o[2]),s=this.$locale(),!f&&d&&(s=n.Ls[d]),this.$d=function(e,t,n,r){try{if(["x","X"].indexOf(t)>-1)return new Date(("X"===t?1e3:1)*e);var i=l(t)(e),o=i.year,s=i.month,a=i.day,f=i.hours,h=i.minutes,u=i.seconds,d=i.milliseconds,c=i.zone,m=i.week,M=new Date,Y=a||(o||s?1:M.getDate()),p=o||M.getFullYear(),v=0;o&&!s||(v=s>0?s-1:M.getMonth());var D,w=f||0,g=h||0,y=u||0,L=d||0;return c?new Date(Date.UTC(p,v,Y,w,g,y,L+60*c.offset*1e3)):n?new Date(Date.UTC(p,v,Y,w,g,y,L)):(D=new Date(p,v,Y,w,g,y,L),m&&(D=r(D).week(m).toDate()),D)}catch(e){return new Date("")}}(t,a,r,n),this.init(),d&&!0!==d&&(this.$L=this.locale(d).$L),u&&t!=this.format(a)&&(this.$d=new Date("")),s={}}else if(a instanceof Array)for(var c=a.length,m=1;m<=c;m+=1){o[1]=a[m-1];var M=n.apply(this,o);if(M.isValid()){this.$d=M.$d,this.$L=M.$L,this.init();break}m===c&&(this.$d=new Date(""))}else i.call(this,e)}}}));
/***/ },
/***/ 562
(module) {
!function(r,e){ true?module.exports=e():0}(this,(function(){"use strict";return function(r,e,t){r=r||{};var n=e.prototype,o={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function i(r,e,t,o){return n.fromToBase(r,e,t,o)}t.en.relativeTime=o,n.fromToBase=function(e,n,i,d,u){for(var f,a,s,l=i.$locale().relativeTime||o,h=r.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],m=h.length,c=0;c<m;c+=1){var y=h[c];y.d&&(f=d?t(e).diff(i,y.d,!0):i.diff(e,y.d,!0));var p=(r.rounding||Math.round)(Math.abs(f));if(s=f>0,p<=y.r||!y.r){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),a="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s);break}}if(n)return a;var M=s?l.future:l.past;return"function"==typeof M?M(a):M.replace("%s",a)},n.to=function(r,e){return i(r,e,this,!0)},n.from=function(r,e){return i(r,e,this)};var d=function(r){return r.$u?t.utc():t()};n.toNow=function(r){return this.to(d(this),r)},n.fromNow=function(r){return this.from(d(this),r)}}}));
/***/ },
/***/ 168
(module) {
!function(t,e){ true?module.exports=e():0}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,h=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",v=+e;return(o.utc(h).valueOf()-(v-=v%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n,i=this.utcOffset(),a=this.toDate(),u=a.toLocaleString("en-US",{timeZone:t}),f=Math.round((a-new Date(u))/1e3/60),s=15*-Math.round(a.getTimezoneOffset()/15)-f;if(!Number(s))n=this.utcOffset(0,e);else if(n=o(u,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(s,!0),e){var m=n.utcOffset();n=n.add(i-m,"minute")}return n.$x.$timezone=t,n},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
/***/ },
/***/ 657
(module) {
!function(t,i){ true?module.exports=i():0}(this,(function(){"use strict";var t="minute",i=/[+-]\d\d(?::?\d\d)?/g,e=/([+-]|\d\d)/g;return function(s,f,n){var u=f.prototype;n.utc=function(t){var i={date:t,utc:!0,args:arguments};return new f(i)},u.utc=function(i){var e=n(this.toDate(),{locale:this.$L,utc:!0});return i?e.add(this.utcOffset(),t):e},u.local=function(){return n(this.toDate(),{locale:this.$L,utc:!1})};var r=u.parse;u.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),r.call(this,t)};var o=u.init;u.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else o.call(this)};var a=u.utcOffset;u.utcOffset=function(s,f){var n=this.$utils().u;if(n(s))return this.$u?0:n(this.$offset)?a.call(this):this.$offset;if("string"==typeof s&&(s=function(t){void 0===t&&(t="");var s=t.match(i);if(!s)return null;var f=(""+s[0]).match(e)||["-",0,0],n=f[0],u=60*+f[1]+ +f[2];return 0===u?0:"+"===n?u:-u}(s),null===s))return this;var u=Math.abs(s)<=16?60*s:s;if(0===u)return this.utc(f);var r=this.clone();if(f)return r.$offset=u,r.$u=!1,r;var o=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();return(r=this.local().add(u+o,t)).$offset=u,r.$x.$localOffset=o,r};var h=u.format;u.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return h.call(this,i)},u.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*t},u.isUTC=function(){return!!this.$u},u.toISOString=function(){return this.toDate().toISOString()},u.toString=function(){return this.toDate().toUTCString()};var l=u.toDate;u.toDate=function(t){return"s"===t&&this.$offset?n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():l.call(this)};var c=u.diff;u.diff=function(t,i,e){if(t&&this.$u===t.$u)return c.call(this,t,i,e);var s=this.local(),f=n(t).local();return c.call(s,f,i,e)}}}));
/***/ },
/***/ 869
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
* matchesSelector v2.0.2
* matchesSelector( element, '.selector' )
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
( function( window, factory ) {
/*global define: false, module: false */
'use strict';
// universal module definition
if ( true ) {
// AMD
!(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
__WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( window, function factory() {
'use strict';
var matchesMethod = ( function() {
var ElemProto = window.Element.prototype;
// check for the standard method name first
if ( ElemProto.matches ) {
return 'matches';
}
// check un-prefixed
if ( ElemProto.matchesSelector ) {
return 'matchesSelector';
}
// check vendor prefixes
var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
for ( var i=0; i < prefixes.length; i++ ) {
var prefix = prefixes[i];
var method = prefix + 'MatchesSelector';
if ( ElemProto[ method ] ) {
return method;
}
}
})();
return function matchesSelector( elem, selector ) {
return elem[ matchesMethod ]( selector );
};
}));
/***/ },
/***/ 240
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
* EvEmitter v1.1.0
* Lil' event emitter
* MIT License
*/
/* jshint unused: true, undef: true, strict: true */
( function( global, factory ) {
// universal module definition
/* jshint strict: false */ /* globals define, module, window */
if ( true ) {
// AMD - RequireJS
!(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
__WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( typeof window != 'undefined' ? window : this, function() {
"use strict";
function EvEmitter() {}
var proto = EvEmitter.prototype;
proto.on = function( eventName, listener ) {
if ( !eventName || !listener ) {
return;
}
// set events hash
var events = this._events = this._events || {};
// set listeners array
var listeners = events[ eventName ] = events[ eventName ] || [];
// only add once
if ( listeners.indexOf( listener ) == -1 ) {
listeners.push( listener );
}
return this;
};
proto.once = function( eventName, listener ) {
if ( !eventName || !listener ) {
return;
}
// add event
this.on( eventName, listener );
// set once flag
// set onceEvents hash
var onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
// set flag
onceListeners[ listener ] = true;
return this;
};
proto.off = function( eventName, listener ) {
var listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) {
return;
}
var index = listeners.indexOf( listener );
if ( index != -1 ) {
listeners.splice( index, 1 );
}
return this;
};
proto.emitEvent = function( eventName, args ) {
var listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) {
return;
}
// copy over to avoid interference if .off() in listener
listeners = listeners.slice(0);
args = args || [];
// once stuff
var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
for ( var i=0; i < listeners.length; i++ ) {
var listener = listeners[i]
var isOnce = onceListeners && onceListeners[ listener ];
if ( isOnce ) {
// remove listener
// remove before trigger to prevent recursion
this.off( eventName, listener );
// unset once flag
delete onceListeners[ listener ];
}
// trigger listener
listener.apply( this, args );
}
return this;
};
proto.allOff = function() {
delete this._events;
delete this._onceEvents;
};
return EvEmitter;
}));
/***/ },
/***/ 346
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
* Fizzy UI utils v2.0.7
* MIT license
*/
/*jshint browser: true, undef: true, unused: true, strict: true */
( function( window, factory ) {
// universal module definition
/*jshint strict: false */ /*globals define, module, require */
if ( true ) {
// AMD
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
__webpack_require__(869)
], __WEBPACK_AMD_DEFINE_RESULT__ = (function( matchesSelector ) {
return factory( window, matchesSelector );
}).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( window, function factory( window, matchesSelector ) {
'use strict';
var utils = {};
// ----- extend ----- //
// extends objects
utils.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
// ----- modulo ----- //
utils.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
// ----- makeArray ----- //
var arraySlice = Array.prototype.slice;
// turn element or nodeList into an array
utils.makeArray = function( obj ) {
if ( Array.isArray( obj ) ) {
// use object if already an array
return obj;
}
// return empty array if undefined or null. #6
if ( obj === null || obj === undefined ) {
return [];
}
var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
if ( isArrayLike ) {
// convert nodeList to array
return arraySlice.call( obj );
}
// array of single index
return [ obj ];
};
// ----- removeFrom ----- //
utils.removeFrom = function( ary, obj ) {
var index = ary.indexOf( obj );
if ( index != -1 ) {
ary.splice( index, 1 );
}
};
// ----- getParent ----- //
utils.getParent = function( elem, selector ) {
while ( elem.parentNode && elem != document.body ) {
elem = elem.parentNode;
if ( matchesSelector( elem, selector ) ) {
return elem;
}
}
};
// ----- getQueryElement ----- //
// use element as selector string
utils.getQueryElement = function( elem ) {
if ( typeof elem == 'string' ) {
return document.querySelector( elem );
}
return elem;
};
// ----- handleEvent ----- //
// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// ----- filterFindElements ----- //
utils.filterFindElements = function( elems, selector ) {
// make array of elems
elems = utils.makeArray( elems );
var ffElems = [];
elems.forEach( function( elem ) {
// check that elem is an actual element
if ( !( elem instanceof HTMLElement ) ) {
return;
}
// add elem if no selector
if ( !selector ) {
ffElems.push( elem );
return;
}
// filter & find items if we have a selector
// filter
if ( matchesSelector( elem, selector ) ) {
ffElems.push( elem );
}
// find children
var childElems = elem.querySelectorAll( selector );
// concat childElems to filterFound array
for ( var i=0; i < childElems.length; i++ ) {
ffElems.push( childElems[i] );
}
});
return ffElems;
};
// ----- debounceMethod ----- //
utils.debounceMethod = function( _class, methodName, threshold ) {
threshold = threshold || 100;
// original method
var method = _class.prototype[ methodName ];
var timeoutName = methodName + 'Timeout';
_class.prototype[ methodName ] = function() {
var timeout = this[ timeoutName ];
clearTimeout( timeout );
var args = arguments;
var _this = this;
this[ timeoutName ] = setTimeout( function() {
method.apply( _this, args );
delete _this[ timeoutName ];
}, threshold );
};
};
// ----- docReady ----- //
utils.docReady = function( callback ) {
var readyState = document.readyState;
if ( readyState == 'complete' || readyState == 'interactive' ) {
// do async to allow for other scripts to run. metafizzy/flickity#441
setTimeout( callback );
} else {
document.addEventListener( 'DOMContentLoaded', callback );
}
};
// ----- htmlInit ----- //
// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
utils.toDashed = function( str ) {
return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
return $1 + '-' + $2;
}).toLowerCase();
};
var console = window.console;
/**
* allow user to initialize classes via [data-namespace] or .js-namespace class
* htmlInit( Widget, 'widgetName' )
* options are parsed from data-namespace-options
*/
utils.htmlInit = function( WidgetClass, namespace ) {
utils.docReady( function() {
var dashedNamespace = utils.toDashed( namespace );
var dataAttr = 'data-' + dashedNamespace;
var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' );
var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace );
var elems = utils.makeArray( dataAttrElems )
.concat( utils.makeArray( jsDashElems ) );
var dataOptionsAttr = dataAttr + '-options';
var jQuery = window.jQuery;
elems.forEach( function( elem ) {
var attr = elem.getAttribute( dataAttr ) ||
elem.getAttribute( dataOptionsAttr );
var options;
try {
options = attr && JSON.parse( attr );
} catch ( error ) {
// log error, do not initialize
if ( console ) {
console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className +
': ' + error );
}
return;
}
// initialize
var instance = new WidgetClass( elem, options );
// make available via $().data('namespace')
if ( jQuery ) {
jQuery.data( elem, namespace, instance );
}
});
});
};
// ----- ----- //
return utils;
}));
/***/ },
/***/ 928
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
* getSize v2.0.3
* measure size of elements
* MIT license
*/
/* jshint browser: true, strict: true, undef: true, unused: true */
/* globals console: false */
( function( window, factory ) {
/* jshint strict: false */ /* globals define, module */
if ( true ) {
// AMD
!(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
__WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
})( window, function factory() {
'use strict';
// -------------------------- helpers -------------------------- //
// get a number from a string, not a percentage
function getStyleSize( value ) {
var num = parseFloat( value );
// not a percent like '100%', and a number
var isValid = value.indexOf('%') == -1 && !isNaN( num );
return isValid && num;
}
function noop() {}
var logError = typeof console == 'undefined' ? noop :
function( message ) {
console.error( message );
};
// -------------------------- measurements -------------------------- //
var measurements = [
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'borderBottomWidth'
];
var measurementsLength = measurements.length;
function getZeroSize() {
var size = {
width: 0,
height: 0,
innerWidth: 0,
innerHeight: 0,
outerWidth: 0,
outerHeight: 0
};
for ( var i=0; i < measurementsLength; i++ ) {
var measurement = measurements[i];
size[ measurement ] = 0;
}
return size;
}
// -------------------------- getStyle -------------------------- //
/**
* getStyle, get style of element, check for Firefox bug
* https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/
function getStyle( elem ) {
var style = getComputedStyle( elem );
if ( !style ) {
logError( 'Style returned ' + style +
'. Are you running this code in a hidden iframe on Firefox? ' +
'See https://bit.ly/getsizebug1' );
}
return style;
}
// -------------------------- setup -------------------------- //
var isSetup = false;
var isBoxSizeOuter;
/**
* setup
* check isBoxSizerOuter
* do on first getSize() rather than on page load for Firefox bug
*/
function setup() {
// setup once
if ( isSetup ) {
return;
}
isSetup = true;
// -------------------------- box sizing -------------------------- //
/**
* Chrome & Safari measure the outer-width on style.width on border-box elems
* IE11 & Firefox<29 measures the inner-width
*/
var div = document.createElement('div');
div.style.width = '200px';
div.style.padding = '1px 2px 3px 4px';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px 2px 3px 4px';
div.style.boxSizing = 'border-box';
var body = document.body || document.documentElement;
body.appendChild( div );
var style = getStyle( div );
// round value for browser zoom. desandro/masonry#928
isBoxSizeOuter = Math.round( getStyleSize( style.width ) ) == 200;
getSize.isBoxSizeOuter = isBoxSizeOuter;
body.removeChild( div );
}
// -------------------------- getSize -------------------------- //
function getSize( elem ) {
setup();
// use querySeletor if elem is string
if ( typeof elem == 'string' ) {
elem = document.querySelector( elem );
}
// do not proceed on non-objects
if ( !elem || typeof elem != 'object' || !elem.nodeType ) {
return;
}
var style = getStyle( elem );
// if hidden, everything is 0
if ( style.display == 'none' ) {
return getZeroSize();
}
var size = {};
size.width = elem.offsetWidth;
size.height = elem.offsetHeight;
var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';
// get all measurements
for ( var i=0; i < measurementsLength; i++ ) {
var measurement = measurements[i];
var value = style[ measurement ];
var num = parseFloat( value );
// any 'auto', 'medium' value will be 0
size[ measurement ] = !isNaN( num ) ? num : 0;
}
var paddingWidth = size.paddingLeft + size.paddingRight;
var paddingHeight = size.paddingTop + size.paddingBottom;
var marginWidth = size.marginLeft + size.marginRight;
var marginHeight = size.marginTop + size.marginBottom;
var borderWidth = size.borderLeftWidth + size.borderRightWidth;
var borderHeight = size.borderTopWidth + size.borderBottomWidth;
var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
// overwrite width and height if we can get it from style
var styleWidth = getStyleSize( style.width );
if ( styleWidth !== false ) {
size.width = styleWidth +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
}
var styleHeight = getStyleSize( style.height );
if ( styleHeight !== false ) {
size.height = styleHeight +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
}
size.innerWidth = size.width - ( paddingWidth + borderWidth );
size.innerHeight = size.height - ( paddingHeight + borderHeight );
size.outerWidth = size.width + marginWidth;
size.outerHeight = size.height + marginHeight;
return size;
}
return getSize;
});
/***/ },
/***/ 177
() {
"use strict";
jsVectorMap.addMap("world",{insets:[{width:900,top:0,left:0,height:440.70631074413296,bbox:[{y:-12671671.123330014,x:-20004297.151525836},{y:6930392.025135122,x:20026572.39474939}]}],paths:{BD:{path:"M651.84,230.21l-0.6,-2.0l-1.36,-1.71l-2.31,-0.11l-0.41,0.48l0.2,0.94l-0.53,0.99l-0.72,-0.36l-0.68,0.35l-1.2,-0.36l-0.37,-2.0l-0.81,-1.86l0.39,-1.46l-0.22,-0.47l-1.14,-0.53l0.29,-0.5l1.48,-0.94l0.03,-0.65l-1.55,-1.22l0.55,-1.14l1.61,0.94l1.04,0.15l0.18,1.54l0.34,0.35l5.64,0.63l-0.84,1.64l-1.22,0.34l-0.77,1.51l0.07,0.47l1.37,1.37l0.67,-0.19l0.42,-1.39l1.21,3.84l-0.03,1.21l-0.33,-0.15l-0.4,0.28Z",name:"Bangladesh"},BE:{path:"M429.29,144.05l1.91,0.24l2.1,-0.63l2.63,1.99l-0.21,1.66l-0.69,0.4l-0.18,1.2l-1.66,-1.13l-1.39,0.15l-2.73,-2.7l-1.17,-0.18l-0.16,-0.52l1.54,-0.5Z",name:"Belgium"},BF:{path:"M421.42,247.64l-0.11,0.95l0.34,1.16l1.4,1.71l0.07,1.1l0.32,0.37l2.55,0.51l-0.04,1.28l-0.38,0.53l-1.07,0.21l-0.72,1.18l-0.63,0.21l-3.22,-0.25l-0.94,0.39l-5.4,-0.05l-0.39,0.38l0.16,2.73l-1.23,-0.43l-1.17,0.1l-0.89,0.57l-2.27,-1.72l-0.13,-1.11l0.61,-0.96l0.02,-0.93l1.87,-1.98l0.44,-1.81l0.43,-0.39l1.28,0.26l1.05,-0.52l0.47,-0.73l1.84,-1.09l0.55,-0.83l2.2,-1.0l1.15,-0.3l0.72,0.45l1.13,-0.01Z",name:"Burkina Faso"},BG:{path:"M491.65,168.18l-0.86,0.88l-0.91,2.17l0.48,1.34l-1.6,-0.24l-2.55,0.95l-0.28,1.51l-1.8,0.22l-2.0,-1.0l-1.92,0.79l-1.42,-0.07l-0.15,-1.63l-1.05,-0.97l0.0,-0.8l1.2,-1.57l0.01,-0.56l-1.14,-1.23l-0.05,-0.94l0.88,0.97l0.88,-0.2l1.91,0.47l3.68,0.16l1.42,-0.81l2.72,-0.66l2.55,1.24Z",name:"Bulgaria"},BA:{path:"M463.49,163.65l2.1,0.5l1.72,-0.03l1.52,0.68l-0.36,0.78l0.08,0.45l1.04,1.02l-0.25,0.98l-1.81,1.15l-0.38,1.38l-1.67,-0.87l-0.89,-1.2l-2.11,-1.83l-1.63,-2.22l0.23,-0.57l0.48,0.38l0.55,-0.06l0.43,-0.51l0.94,-0.06Z",name:"Bosnia and Herz."},BN:{path:"M707.48,273.58l0.68,-0.65l1.41,-0.91l-0.15,1.63l-0.81,-0.05l-0.61,0.58l-0.53,-0.6Z",name:"Brunei"},BO:{path:"M263.83,340.69l-3.09,-0.23l-0.38,0.23l-0.7,1.52l-1.31,-1.53l-3.28,-0.64l-2.37,2.4l-1.31,0.26l-0.88,-3.26l-1.3,-2.86l0.74,-2.37l-0.13,-0.43l-1.2,-1.01l-0.37,-1.89l-1.08,-1.55l1.45,-2.56l-0.96,-2.33l0.47,-1.06l-0.34,-0.73l0.91,-1.32l0.16,-3.84l0.5,-1.18l-1.81,-3.41l2.46,0.07l0.8,-0.85l3.4,-1.91l2.66,-0.35l-0.19,1.38l0.3,1.07l-0.05,1.97l2.72,2.27l2.88,0.49l0.89,0.86l1.79,0.58l0.98,0.7l1.71,0.05l1.17,0.61l0.6,2.7l-0.7,0.54l0.96,2.99l0.37,0.28l4.3,0.1l-0.25,1.2l0.27,1.02l1.43,0.9l0.5,1.35l-0.41,1.86l-0.65,1.08l0.12,1.35l-2.69,-1.65l-2.4,-0.03l-4.36,0.76l-1.49,2.5l-0.11,1.52l-0.75,2.37Z",name:"Bolivia"},JP:{path:"M781.12,166.87l1.81,0.68l1.62,-0.97l0.39,2.42l-3.35,0.75l-2.23,2.88l-3.63,-1.9l-0.56,0.2l-1.26,3.05l-2.16,0.03l-0.29,-2.51l1.08,-2.03l2.45,-0.16l0.37,-0.33l1.25,-5.94l2.47,2.71l2.03,1.12ZM773.56,187.34l-0.91,2.22l0.37,1.52l-1.14,1.75l-3.02,1.26l-4.58,0.27l-3.34,3.01l-1.25,-0.8l-0.09,-1.9l-0.46,-0.38l-4.35,0.62l-3.0,1.32l-2.85,0.05l-0.37,0.27l0.13,0.44l2.32,1.89l-1.54,4.34l-1.26,0.9l-0.79,-0.7l0.56,-2.27l-0.21,-0.45l-1.47,-0.75l-0.74,-1.4l2.12,-0.84l1.26,-1.7l2.45,-1.42l1.83,-1.91l4.78,-0.81l2.6,0.57l0.44,-0.21l2.39,-4.66l1.29,1.06l0.5,0.01l5.1,-4.02l1.69,-3.73l-0.38,-3.4l0.9,-1.61l2.14,-0.44l1.23,3.72l-0.07,2.18l-2.23,2.84l-0.04,3.16ZM757.78,196.26l0.19,0.56l-1.01,1.21l-1.16,-0.68l-1.28,0.65l-0.69,1.45l-1.02,-0.5l0.01,-0.93l1.14,-1.38l1.57,0.14l0.85,-0.98l1.4,0.46Z",name:"Japan"},BI:{path:"M495.45,295.49l-1.08,-2.99l1.14,-0.11l0.64,-1.19l0.76,0.09l0.65,1.83l-2.1,2.36Z",name:"Burundi"},BJ:{path:"M429.57,255.75l-0.05,0.8l0.5,1.34l-0.42,0.86l0.17,0.79l-1.81,2.12l-0.57,1.76l-0.08,5.42l-1.41,0.2l-0.48,-1.36l0.11,-5.71l-0.52,-0.7l-0.2,-1.35l-1.48,-1.48l0.21,-0.9l0.89,-0.43l0.42,-0.92l1.27,-0.36l1.22,-1.34l0.61,-0.0l1.62,1.24Z",name:"Benin"},BT:{path:"M650.32,213.86l0.84,0.71l-0.12,1.1l-3.76,-0.11l-1.57,0.4l-1.93,-0.87l1.48,-1.96l1.13,-0.57l1.63,0.57l1.33,0.08l0.99,0.65Z",name:"Bhutan"},JM:{path:"M228.38,239.28l-0.8,0.4l-2.26,-1.06l0.84,-0.23l2.14,0.3l1.17,0.56l-1.08,0.03Z",name:"Jamaica"},BW:{path:"M483.92,330.07l2.27,4.01l2.83,2.86l0.96,0.31l0.78,2.43l2.13,0.61l1.02,0.76l-3.0,1.64l-2.32,2.02l-1.54,2.69l-1.52,0.45l-0.64,1.94l-1.34,0.52l-1.85,-0.12l-1.21,-0.74l-1.35,-0.3l-1.22,0.62l-0.75,1.37l-2.31,1.9l-1.4,0.21l-0.35,-0.59l0.16,-1.75l-1.48,-2.54l-0.62,-0.43l-0.0,-7.1l2.08,-0.08l0.39,-0.4l0.07,-8.9l5.19,-0.93l0.8,0.89l0.51,0.07l1.5,-0.95l2.21,-0.49Z",name:"Botswana"},BR:{path:"M259.98,275.05l3.24,0.7l0.65,-0.53l4.55,-1.32l1.08,-1.06l-0.02,-0.63l0.55,-0.05l0.28,0.28l-0.26,0.87l0.22,0.48l0.73,0.32l0.4,0.81l-0.62,0.86l-0.4,2.13l0.82,2.56l1.69,1.43l1.43,0.2l3.17,-1.68l3.18,0.3l0.65,-0.75l-0.27,-0.92l1.9,-0.09l2.39,0.99l1.06,-0.61l0.84,0.78l1.2,-0.18l1.18,-1.06l0.84,-1.94l1.36,-2.11l0.37,-0.05l1.89,5.45l1.33,0.59l0.05,1.28l-1.77,1.94l0.02,0.56l1.02,0.87l4.07,0.36l0.08,2.16l0.66,0.29l1.74,-1.5l6.97,2.32l1.02,1.22l-0.35,1.18l0.49,0.5l2.81,-0.74l4.77,1.3l3.75,-0.08l3.57,2.0l3.29,2.86l1.93,0.72l2.12,0.12l0.71,0.62l1.21,4.51l-0.95,3.98l-4.72,5.06l-1.64,2.92l-1.72,2.05l-0.8,0.3l-0.72,2.03l0.18,4.75l-0.94,5.53l-0.81,1.13l-0.43,3.36l-2.55,3.5l-0.4,2.51l-1.86,1.04l-0.67,1.53l-2.54,0.01l-3.94,1.01l-1.83,1.2l-2.87,0.82l-3.03,2.19l-2.2,2.83l-0.36,2.0l0.4,1.58l-0.44,2.6l-0.51,1.2l-1.77,1.54l-2.75,4.78l-3.83,3.42l-1.24,2.74l-1.18,1.15l-0.36,-0.83l0.95,-1.14l0.01,-0.5l-1.52,-1.97l-4.56,-3.32l-1.03,-0.0l-2.38,-2.02l-0.81,-0.0l5.34,-5.45l3.77,-2.58l0.22,-2.46l-1.35,-1.81l-0.91,0.07l0.58,-2.33l0.01,-1.54l-1.11,-0.83l-1.75,0.3l-0.44,-3.11l-0.52,-0.95l-1.88,-0.88l-1.24,0.47l-2.17,-0.41l0.15,-3.21l-0.62,-1.34l0.66,-0.73l-0.22,-1.34l0.66,-1.13l0.44,-2.04l-0.61,-1.83l-1.4,-0.86l-0.2,-0.75l0.34,-1.39l-0.38,-0.5l-4.52,-0.1l-0.72,-2.22l0.59,-0.42l-0.03,-1.1l-0.5,-0.87l-0.32,-1.7l-1.45,-0.76l-1.63,-0.02l-1.05,-0.72l-1.6,-0.48l-1.13,-0.99l-2.69,-0.4l-2.47,-2.06l0.13,-4.35l-0.45,-0.45l-3.46,0.5l-3.44,1.94l-0.6,0.74l-2.9,-0.17l-1.47,0.42l-0.72,-0.18l0.15,-3.52l-0.63,-0.34l-1.94,1.41l-1.87,-0.06l-0.83,-1.18l-1.37,-0.26l0.21,-1.01l-1.35,-1.49l-0.88,-1.91l0.56,-0.6l-0.0,-0.81l1.29,-0.62l0.22,-0.43l-0.22,-1.19l0.61,-0.91l0.15,-0.99l2.65,-1.58l1.99,-0.47l0.42,-0.36l2.06,0.11l0.42,-0.33l1.19,-8.0l-0.41,-1.56l-1.1,-1.0l0.01,-1.33l1.91,-0.42l0.08,-0.96l-0.33,-0.43l-1.14,-0.2l-0.02,-0.83l4.47,0.05l0.82,-0.67l0.82,1.81l0.8,0.07l1.15,1.1l2.26,-0.05l0.71,-0.83l2.78,-0.96l0.48,-1.13l1.6,-0.64l0.24,-0.47l-0.48,-0.82l-1.83,-0.19l-0.36,-3.22Z",name:"Brazil"},BS:{path:"M226.4,223.87l-0.48,-1.15l-0.84,-0.75l0.36,-1.11l0.95,1.95l0.01,1.06ZM225.56,216.43l-1.87,0.29l-0.04,-0.22l0.74,-0.14l1.17,0.06Z",name:"Bahamas"},BY:{path:"M493.84,128.32l0.29,0.7l0.49,0.23l1.19,-0.38l2.09,0.72l0.19,1.26l-0.45,1.24l1.57,2.26l0.89,0.59l0.17,0.81l1.58,0.56l0.4,0.5l-0.53,0.41l-1.87,-0.11l-0.73,0.38l-0.13,0.52l1.04,2.74l-1.91,0.26l-0.89,0.99l-0.11,1.18l-2.73,-0.04l-0.53,-0.62l-0.52,-0.08l-0.75,0.46l-0.91,-0.42l-1.92,-0.07l-2.75,-0.79l-2.6,-0.28l-2.0,0.07l-1.5,0.92l-0.67,0.07l-0.08,-1.22l-0.59,-1.19l1.36,-0.88l0.01,-1.35l-0.7,-1.41l-0.07,-1.0l2.16,-0.02l2.72,-1.3l0.75,-2.04l1.91,-1.04l0.2,-0.41l-0.19,-1.25l3.8,-1.78l2.3,0.77Z",name:"Belarus"},BZ:{path:"M198.03,244.38l0.1,-4.49l0.69,-0.06l0.74,-1.3l0.34,0.28l-0.4,1.3l0.17,0.58l-0.34,2.25l-1.3,1.42Z",name:"Belize"},RU:{path:"M491.55,115.25l2.55,-1.85l-0.01,-0.65l-2.2,-1.5l7.32,-6.76l1.03,-2.11l-0.13,-0.49l-3.46,-2.52l0.86,-2.7l-2.11,-2.81l1.56,-3.67l-2.77,-4.52l2.15,-2.99l-0.08,-0.55l-3.65,-2.73l0.3,-2.54l1.81,-0.37l4.26,-1.77l2.42,-1.45l4.06,2.61l6.79,1.04l9.34,4.85l1.78,1.88l0.14,2.46l-2.55,2.02l-3.9,1.06l-11.07,-3.14l-2.06,0.53l-0.13,0.7l3.94,2.94l0.31,5.86l0.26,0.36l5.14,2.24l0.58,-0.29l0.32,-1.94l-1.35,-1.78l1.13,-1.09l6.13,2.42l2.11,-0.98l0.18,-0.56l-1.51,-2.67l5.41,-3.76l2.07,0.22l2.26,1.41l0.57,-0.16l1.46,-2.87l-0.05,-0.44l-1.92,-2.32l1.12,-2.32l-1.32,-2.27l5.87,1.16l1.04,1.75l-2.59,0.43l-0.33,0.4l0.02,2.36l2.46,1.83l3.87,-0.91l0.86,-2.8l13.69,-5.65l0.99,0.11l-1.92,2.06l0.23,0.67l3.11,0.45l2.0,-1.48l4.56,-0.12l3.64,-1.73l2.65,2.44l0.56,-0.01l2.85,-2.88l-0.01,-0.57l-2.35,-2.29l0.9,-1.01l7.14,1.3l3.41,1.36l9.05,4.97l0.51,-0.11l1.67,-2.27l-0.05,-0.53l-2.43,-2.21l-0.06,-0.78l-0.34,-0.36l-2.52,-0.36l0.64,-1.93l-1.32,-3.46l-0.06,-1.21l4.48,-4.06l1.69,-4.29l1.6,-0.81l6.23,1.18l0.44,2.21l-2.29,3.64l0.06,0.5l1.47,1.39l0.76,3.0l-0.56,6.03l2.69,2.82l-0.96,2.57l-4.86,5.95l0.23,0.64l2.86,0.61l0.42,-0.17l0.93,-1.4l2.64,-1.03l0.87,-2.24l2.09,-1.96l0.07,-0.5l-1.36,-2.28l1.09,-2.69l-0.32,-0.55l-2.47,-0.33l-0.5,-2.06l1.94,-4.38l-0.06,-0.42l-2.96,-3.4l4.12,-2.88l0.16,-0.4l-0.51,-2.93l0.54,-0.05l1.13,2.25l-0.96,4.35l0.27,0.47l2.68,0.84l0.5,-0.51l-1.02,-2.99l3.79,-1.66l5.01,-0.24l4.53,2.61l0.48,-0.06l0.07,-0.48l-2.18,-3.82l-0.23,-4.67l3.98,-0.9l5.97,0.21l5.49,-0.64l0.27,-0.65l-1.83,-2.31l2.56,-2.9l2.87,-0.17l4.8,-2.47l6.54,-0.67l1.03,-1.42l6.25,-0.45l2.32,1.11l5.53,-2.7l4.5,0.08l0.39,-0.28l0.66,-2.15l2.26,-2.12l5.69,-2.11l3.21,1.29l-2.46,0.94l-0.25,0.42l0.34,0.35l5.41,0.77l0.61,2.33l0.58,0.25l2.2,-1.22l7.13,0.07l5.51,2.47l1.79,1.72l-0.53,2.24l-9.16,4.15l-1.97,1.52l0.16,0.71l6.77,1.91l2.16,-0.78l1.13,2.74l0.67,0.11l1.01,-1.15l3.81,-0.73l7.7,0.77l0.54,1.99l0.36,0.29l10.47,0.71l0.43,-0.38l0.13,-3.23l4.87,0.78l3.95,-0.02l3.83,2.4l1.03,2.71l-1.35,1.79l0.02,0.5l3.15,3.64l4.07,1.96l0.53,-0.18l2.23,-4.47l3.95,1.93l4.16,-1.21l4.73,1.39l2.05,-1.26l3.94,0.62l0.43,-0.55l-1.68,-4.02l2.89,-1.8l22.31,3.03l2.16,2.75l6.55,3.51l10.29,-0.81l4.82,0.73l1.85,1.66l-0.29,3.08l0.25,0.41l3.08,1.26l3.56,-0.88l4.35,-0.11l4.8,0.87l4.57,-0.47l4.23,3.79l0.43,0.07l3.1,-1.4l0.16,-0.6l-1.88,-2.62l0.85,-1.52l7.71,1.21l5.22,-0.26l7.09,2.09l9.59,5.22l6.35,4.11l-0.2,2.38l1.88,1.41l0.6,-0.42l-0.48,-2.53l6.15,0.57l4.4,3.51l-1.97,1.43l-4.0,0.41l-0.36,0.39l-0.06,3.79l-0.74,0.62l-2.07,-0.11l-1.91,-1.39l-3.14,-1.11l-0.78,-1.85l-2.72,-0.68l-2.63,0.49l-1.04,-1.1l0.46,-1.31l-0.5,-0.51l-3.0,0.98l-0.22,0.58l0.99,1.7l-1.21,1.48l-3.04,1.68l-3.12,-0.28l-0.4,0.23l0.09,0.46l2.2,2.09l1.46,3.2l1.15,1.1l0.24,1.33l-0.42,0.67l-4.63,-0.77l-6.96,2.9l-2.19,0.44l-7.6,5.06l-0.84,1.45l-3.61,-2.37l-6.24,2.82l-0.94,-1.15l-0.53,-0.08l-2.28,1.52l-3.2,-0.49l-0.44,0.27l-0.78,2.37l-3.05,3.78l0.09,1.47l0.29,0.36l2.54,0.72l-0.29,4.53l-1.97,0.11l-0.35,0.26l-1.07,2.94l0.8,1.45l-3.91,1.58l-1.05,3.95l-3.48,0.77l-0.3,0.3l-0.72,3.29l-3.09,2.65l-0.7,-1.74l-2.44,-12.44l1.16,-4.71l2.04,-2.06l0.22,-1.64l3.8,-0.86l4.46,-4.61l4.28,-3.81l4.48,-3.01l2.17,-5.63l-0.42,-0.54l-3.04,0.33l-1.77,3.31l-5.86,3.86l-1.86,-4.25l-0.45,-0.23l-6.46,1.3l-6.47,6.44l-0.01,0.55l1.58,1.74l-8.24,1.17l0.15,-2.2l-0.34,-0.42l-3.89,-0.56l-3.25,1.81l-7.62,-0.62l-8.45,1.19l-17.71,15.41l0.22,0.7l3.74,0.41l1.36,2.17l2.43,0.76l1.88,-1.68l2.4,0.2l3.4,3.54l0.08,2.6l-1.95,3.42l-0.21,3.9l-1.1,5.06l-3.71,4.54l-0.87,2.21l-8.29,8.89l-3.19,1.7l-1.32,0.03l-1.45,-1.36l-0.49,-0.04l-2.27,1.5l0.41,-3.65l-0.59,-2.47l1.75,-0.89l2.91,0.53l0.42,-0.2l1.68,-3.03l0.87,-3.46l0.97,-1.18l1.32,-2.88l-0.45,-0.56l-4.14,0.95l-2.19,1.25l-3.41,-0.0l-1.06,-2.93l-2.97,-2.3l-4.28,-1.06l-1.75,-5.07l-2.66,-5.01l-2.29,-1.29l-3.75,-1.01l-3.44,0.08l-3.18,0.62l-2.24,1.77l0.05,0.66l1.18,0.69l0.02,1.43l-1.33,1.05l-2.26,3.51l-0.04,1.43l-3.16,1.84l-2.82,-1.16l-3.01,0.23l-1.35,-1.07l-1.5,-0.35l-3.9,2.31l-3.22,0.52l-2.27,0.79l-3.05,-0.51l-2.21,0.03l-1.48,-1.6l-2.6,-1.63l-2.63,-0.43l-5.46,1.01l-3.23,-1.25l-0.72,-2.57l-5.2,-1.24l-2.75,-1.36l-0.5,0.12l-2.59,3.45l0.84,2.1l-2.06,1.93l-3.41,-0.77l-2.42,-0.12l-1.83,-1.54l-2.53,-0.05l-2.42,-0.98l-3.86,1.57l-4.72,2.78l-3.3,0.75l-1.55,-1.92l-3.0,0.41l-1.11,-1.33l-1.62,-0.59l-1.31,-1.94l-1.38,-0.6l-3.7,0.79l-3.31,-1.83l-0.51,0.11l-0.99,1.29l-5.29,-8.05l-2.96,-2.48l0.65,-0.77l0.01,-0.51l-0.5,-0.11l-6.2,3.21l-1.84,0.15l0.15,-1.39l-0.26,-0.42l-3.22,-1.17l-2.46,0.7l-0.69,-3.16l-0.32,-0.31l-4.5,-0.75l-2.47,1.47l-6.19,1.27l-1.29,0.86l-9.51,1.3l-1.15,1.17l-0.03,0.53l1.47,1.9l-1.89,0.69l-0.22,0.56l0.31,0.6l-2.11,1.44l0.03,0.68l3.75,2.12l-0.39,0.98l-3.23,-0.13l-0.86,0.86l-3.09,-1.59l-3.97,0.07l-2.66,1.35l-8.32,-3.56l-4.07,0.06l-5.39,3.68l-0.39,2.0l-2.03,-1.5l-0.59,0.13l-2.0,3.59l0.57,0.93l-1.28,2.16l0.06,0.48l2.13,2.17l1.95,0.04l1.37,1.82l-0.23,1.46l0.25,0.43l0.83,0.33l-0.8,1.31l-2.49,0.62l-2.49,3.2l0.0,0.49l2.17,2.78l-0.15,2.18l2.5,3.24l-1.58,1.59l-0.7,-0.13l-1.63,-1.72l-2.29,-0.84l-0.94,-1.31l-2.34,-0.63l-1.48,0.4l-0.43,-0.47l-3.51,-1.48l-5.76,-1.01l-0.45,0.19l-2.89,-2.34l-2.9,-1.2l-1.53,-1.29l1.29,-0.43l2.08,-2.61l-0.05,-0.55l-0.89,-0.79l3.05,-1.06l0.27,-0.42l-0.07,-0.69l-0.49,-0.35l-1.73,0.39l0.04,-0.68l1.04,-0.72l2.66,-0.48l0.4,-1.32l-0.5,-1.6l0.92,-1.54l0.03,-1.17l-0.29,-0.37l-3.69,-1.06l-1.41,0.02l-1.42,-1.41l-2.19,0.38l-2.77,-1.01l-0.03,-0.59l-0.89,-1.43l-2.0,-0.32l-0.11,-0.54l0.49,-0.53l0.01,-0.53l-1.6,-1.9l-3.58,0.02l-0.88,0.73l-0.46,-0.07l-1.0,-2.79l2.22,-0.02l0.97,-0.74l0.07,-0.57l-0.9,-1.04l-1.35,-0.48l-0.11,-0.7l-0.95,-0.58l-1.38,-1.99l0.46,-0.98l-0.51,-1.96l-2.45,-0.84l-1.21,0.3l-0.46,-0.76l-2.46,-0.83l-0.72,-1.87l-0.21,-1.69l-0.99,-0.85l0.85,-1.17l-0.7,-3.21l1.66,-1.97l-0.16,-0.79ZM749.2,170.72l-0.6,0.4l-0.13,0.16l-0.01,-0.51l0.74,-0.05ZM874.85,67.94l-5.63,0.48l-0.26,-0.84l3.15,-1.89l1.94,0.01l3.19,1.16l-2.39,1.09ZM797.39,48.49l-2.0,1.36l-3.8,-0.42l-4.25,-1.8l0.35,-0.97l9.69,1.83ZM783.67,46.12l-1.63,3.09l-8.98,-0.13l-4.09,1.14l-4.54,-2.97l1.16,-3.01l3.05,-0.89l6.5,0.22l8.54,2.56ZM778.2,134.98l-0.56,-0.9l0.27,-0.12l0.29,1.01ZM778.34,135.48l0.94,3.53l-0.05,3.38l1.05,3.39l2.18,5.0l-2.89,-0.83l-0.49,0.26l-1.54,4.65l2.42,3.5l-0.04,1.13l-1.24,-1.24l-0.61,0.06l-1.09,1.61l-0.28,-1.61l0.27,-3.1l-0.28,-3.4l0.58,-2.47l0.11,-4.39l-1.46,-3.36l0.21,-4.32l2.15,-1.46l0.07,-0.34ZM771.95,56.61l1.76,-1.42l2.89,-0.42l3.28,1.71l0.14,0.6l-3.27,0.03l-4.81,-0.5ZM683.76,31.09l-13.01,1.93l4.03,-6.35l1.82,-0.56l1.73,0.34l5.99,2.98l-0.56,1.66ZM670.85,27.93l-5.08,0.64l-6.86,-1.57l-3.99,-2.05l-2.1,-4.16l-2.6,-0.87l5.72,-3.5l5.2,-1.28l4.69,2.85l5.59,5.4l-0.56,4.53ZM564.15,68.94l-0.64,0.17l-7.85,-0.57l-0.86,-2.04l-4.28,-1.17l-0.28,-1.94l2.27,-0.89l0.25,-0.39l-0.08,-2.38l4.81,-3.97l-0.15,-0.7l-1.47,-0.38l5.3,-3.81l0.15,-0.44l-0.58,-1.94l5.28,-2.51l8.21,-3.27l8.28,-0.96l4.35,-1.94l4.6,-0.64l1.36,1.61l-1.34,1.28l-16.43,4.94l-7.97,4.88l-7.74,9.63l0.66,4.14l4.16,3.27ZM548.81,18.48l-5.5,1.18l-0.58,1.02l-2.59,0.84l-2.13,-1.07l1.12,-1.42l-0.3,-0.65l-2.33,-0.07l1.68,-0.36l3.47,-0.06l0.42,1.29l0.66,0.16l1.38,-1.34l2.15,-0.88l2.94,1.01l-0.39,0.36ZM477.37,133.15l-4.08,0.05l-2.56,-0.32l0.33,-0.87l3.17,-1.03l3.24,0.96l-0.09,1.23Z",name:"Russia"},RW:{path:"M497.0,288.25l0.71,1.01l-0.11,1.09l-1.63,0.03l-1.04,1.39l-0.83,-0.11l0.51,-1.2l0.08,-1.34l0.42,-0.41l0.7,0.14l1.19,-0.61Z",name:"Rwanda"},RS:{path:"M469.4,163.99l0.42,-0.5l-0.01,-0.52l-1.15,-1.63l1.43,-0.62l1.33,0.12l1.17,1.06l0.46,1.13l1.34,0.64l0.35,1.35l1.46,0.9l0.76,-0.29l0.2,0.69l-0.48,0.78l0.22,1.12l1.05,1.22l-0.77,0.8l-0.37,1.52l-1.21,0.08l0.24,-0.64l-0.39,-0.54l-2.08,-1.64l-0.9,0.05l-0.48,0.94l-2.12,-1.37l0.53,-1.6l-1.11,-1.37l0.51,-1.1l-0.41,-0.57Z",name:"Serbia"},LT:{path:"M486.93,129.3l0.17,1.12l-1.81,0.98l-0.72,2.02l-2.47,1.18l-2.1,-0.02l-0.73,-1.05l-1.06,-0.3l-0.09,-1.87l-3.56,-1.13l-0.43,-2.36l2.48,-0.94l4.12,0.22l2.25,-0.31l0.52,0.69l1.24,0.21l2.19,1.56Z",name:"Lithuania"},LU:{path:"M436.08,149.45l-0.48,-0.07l0.3,-1.28l0.27,0.4l-0.09,0.96Z",name:"Luxembourg"},LR:{path:"M399.36,265.97l0.18,1.54l-0.48,0.99l0.08,0.47l2.47,1.8l-0.33,2.8l-2.65,-1.13l-5.78,-4.61l0.58,-1.32l2.1,-2.33l0.86,-0.22l0.77,1.14l-0.14,0.85l0.59,0.87l1.0,0.14l0.76,-0.99Z",name:"Liberia"},RO:{path:"M487.53,154.23l0.6,0.24l2.87,3.98l-0.17,2.69l0.45,1.42l1.32,0.81l1.35,-0.42l0.76,0.36l0.02,0.31l-0.83,0.45l-0.59,-0.22l-0.54,0.3l-0.62,3.3l-1.0,-0.22l-2.07,-1.13l-2.95,0.71l-1.25,0.76l-3.51,-0.15l-1.89,-0.47l-0.87,0.16l-0.82,-1.3l0.29,-0.26l-0.06,-0.64l-1.09,-0.34l-0.56,0.5l-1.05,-0.64l-0.39,-1.39l-1.36,-0.65l-0.35,-1.0l-0.83,-0.75l1.54,-0.54l2.66,-4.21l2.4,-1.24l2.96,0.34l1.48,0.73l0.79,-0.45l1.78,-0.3l0.75,-0.74l0.79,0.0Z",name:"Romania"},GW:{path:"M386.23,253.6l-0.29,0.84l0.15,0.6l-2.21,0.59l-0.86,0.96l-1.04,-0.83l-1.09,-0.23l-0.54,-1.06l-0.66,-0.49l2.41,-0.48l4.13,0.1Z",name:"Guinea-Bissau"},GT:{path:"M195.08,249.77l-2.48,-0.37l-1.03,-0.45l-1.14,-0.89l0.3,-0.99l-0.24,-0.68l0.96,-1.66l2.98,-0.01l0.4,-0.37l-0.19,-1.28l-1.67,-1.4l0.51,-0.4l0.0,-1.05l3.85,0.02l-0.21,4.53l0.4,0.43l1.46,0.38l-1.48,0.98l-0.35,0.7l0.12,0.57l-2.2,1.96Z",name:"Guatemala"},GR:{path:"M487.07,174.59l-0.59,1.43l-0.37,0.21l-2.84,-0.35l-3.03,0.77l-0.18,0.68l1.28,1.23l-0.61,0.23l-1.14,0.0l-1.2,-1.39l-0.63,0.03l-0.53,1.01l0.56,1.76l1.03,1.19l-0.56,0.38l-0.05,0.62l2.52,2.12l0.02,0.87l-1.78,-0.59l-0.48,0.56l0.5,1.0l-1.07,0.2l-0.3,0.53l0.75,2.01l-0.98,0.02l-1.84,-1.12l-1.37,-4.2l-2.21,-2.95l-0.11,-0.56l1.04,-1.28l0.2,-0.95l0.85,-0.66l0.03,-0.46l1.32,-0.21l1.01,-0.64l1.22,0.05l0.65,-0.56l2.26,-0.0l1.82,-0.75l1.85,1.0l2.28,-0.28l0.35,-0.39l0.01,-0.77l0.34,0.22ZM480.49,192.16l0.58,0.4l-0.68,-0.12l0.11,-0.28ZM482.52,192.82l2.51,0.06l0.24,0.32l-1.99,0.13l-0.77,-0.51Z",name:"Greece"},GQ:{path:"M448.79,279.62l0.02,2.22l-4.09,0.0l0.69,-2.27l3.38,0.05Z",name:"Eq. Guinea"},GY:{path:"M277.42,270.07l-0.32,1.83l-1.32,0.57l-0.23,0.46l-0.28,2.0l1.11,1.82l0.83,0.19l0.32,1.25l1.13,1.62l-1.21,-0.19l-1.08,0.71l-1.77,0.5l-0.44,0.46l-0.86,-0.09l-1.32,-1.01l-0.77,-2.27l0.36,-1.9l0.68,-1.23l-0.57,-1.17l-0.74,-0.43l0.12,-1.16l-0.9,-0.69l-1.1,0.09l-1.31,-1.48l0.53,-0.72l-0.04,-0.84l1.99,-0.86l0.05,-0.59l-0.71,-0.78l0.14,-0.57l1.66,-1.24l1.36,0.77l1.41,1.49l0.06,1.15l0.37,0.38l0.8,0.05l2.06,1.86Z",name:"Guyana"},GE:{path:"M521.71,168.93l5.29,0.89l4.07,2.01l1.41,-0.44l2.07,0.56l0.68,1.1l1.07,0.55l-0.12,0.59l0.98,1.29l-1.01,-0.13l-1.81,-0.83l-0.94,0.47l-3.23,0.43l-2.29,-1.39l-2.33,0.05l0.21,-0.97l-0.76,-2.26l-1.45,-1.12l-1.43,-0.39l-0.41,-0.42Z",name:"Georgia"},GB:{path:"M412.61,118.72l-2.19,3.22l-0.0,0.45l5.13,-0.3l-0.53,2.37l-2.2,3.12l0.29,0.63l2.37,0.21l2.33,4.3l1.76,0.69l2.2,5.12l2.94,0.77l-0.23,1.62l-1.15,0.88l-0.1,0.52l0.82,1.42l-1.86,1.43l-3.3,-0.02l-4.12,0.87l-1.04,-0.58l-0.47,0.06l-1.51,1.41l-2.12,-0.34l-1.86,1.18l-0.6,-0.29l3.19,-3.0l2.16,-0.69l0.28,-0.41l-0.34,-0.36l-3.73,-0.53l-0.4,-0.76l2.2,-0.87l0.17,-0.61l-1.26,-1.67l0.36,-1.7l3.38,0.28l0.43,-0.33l0.37,-1.99l-1.79,-2.49l-3.11,-0.72l-0.38,-0.59l0.79,-1.35l-0.04,-0.46l-0.82,-0.97l-0.61,0.01l-0.68,0.84l-0.1,-2.34l-1.23,-1.88l0.85,-3.47l1.77,-2.68l1.85,0.26l2.17,-0.22ZM406.26,132.86l-1.01,1.77l-1.57,-0.59l-1.16,0.01l0.37,-1.54l-0.39,-1.39l1.45,-0.1l2.3,1.84Z",name:"United Kingdom"},GA:{path:"M453.24,279.52l-0.08,0.98l0.7,1.29l2.36,0.24l-0.98,2.63l1.18,1.79l0.25,1.78l-0.29,1.52l-0.6,0.93l-1.84,-0.09l-1.23,-1.11l-0.66,0.23l-0.15,0.84l-1.42,0.26l-1.02,0.7l-0.11,0.52l0.77,1.35l-1.34,0.97l-3.94,-4.3l-1.44,-2.45l0.06,-0.6l0.54,-0.81l1.05,-3.46l4.17,-0.07l0.4,-0.4l-0.02,-2.66l2.39,0.21l1.25,-0.27Z",name:"Gabon"},GN:{path:"M391.8,254.11l0.47,0.8l1.11,-0.32l0.98,0.7l1.07,0.2l2.26,-1.22l0.64,0.44l1.13,1.56l-0.48,1.4l0.8,0.3l-0.08,0.48l0.46,0.68l-0.35,1.36l1.05,2.61l-1.0,0.69l0.03,1.41l-0.72,-0.06l-1.08,1.0l-0.24,-0.27l0.07,-1.11l-1.05,-1.54l-1.79,0.21l-0.35,-2.01l-1.6,-2.18l-2.0,-0.0l-1.31,0.54l-1.95,2.18l-1.86,-2.19l-1.2,-0.78l-0.3,-1.11l-0.8,-0.85l0.65,-0.72l0.81,-0.03l1.64,-0.8l0.23,-1.87l2.67,0.64l0.89,-0.3l1.21,0.15Z",name:"Guinea"},GM:{path:"M379.31,251.39l0.1,-0.35l2.43,-0.07l0.74,-0.61l0.51,-0.03l0.77,0.49l-1.03,-0.3l-1.87,0.9l-1.65,-0.04ZM384.03,250.91l0.91,0.05l0.75,-0.24l-0.59,0.31l-1.08,-0.13Z",name:"Gambia"},GL:{path:"M353.02,1.2l14.69,4.67l-3.68,1.89l-22.97,0.86l-0.36,0.27l0.12,0.43l1.55,1.18l8.79,-0.66l7.48,2.07l4.86,-1.77l1.66,1.73l-2.53,3.19l-0.01,0.48l0.46,0.15l6.35,-2.2l12.06,-2.31l7.24,1.13l1.09,1.99l-9.79,4.01l-1.44,1.32l-7.87,0.98l-0.35,0.41l0.38,0.38l5.07,0.24l-2.53,3.58l-2.07,3.81l0.08,6.05l2.57,3.11l-3.22,0.2l-4.12,1.66l-0.05,0.72l4.45,2.65l0.51,3.75l-2.3,0.4l-0.25,0.64l2.79,3.69l-4.82,0.31l-0.36,0.29l0.16,0.44l2.62,1.8l-0.59,1.22l-3.3,0.7l-3.45,0.01l-0.29,0.68l3.03,3.12l0.02,1.34l-4.4,-1.73l-1.72,1.35l0.15,0.66l3.31,1.15l3.13,2.71l0.81,3.16l-3.85,0.75l-4.89,-4.26l-0.47,-0.03l-0.17,0.44l0.79,2.86l-2.71,2.21l-0.13,0.44l0.37,0.27l8.73,0.34l-12.32,6.64l-7.24,1.48l-2.94,0.08l-2.69,1.75l-3.43,4.41l-5.24,2.84l-1.73,0.18l-7.12,2.1l-2.15,2.52l-0.13,2.99l-1.19,2.45l-4.01,3.09l-0.14,0.44l0.97,2.9l-2.28,6.48l-3.1,0.2l-3.83,-3.07l-4.86,-0.02l-2.25,-1.93l-1.7,-3.79l-4.3,-4.84l-1.21,-2.49l-0.44,-3.8l-3.32,-3.63l0.84,-2.86l-1.56,-1.7l2.28,-4.6l3.83,-1.74l1.03,-1.96l0.52,-3.47l-0.59,-0.41l-4.17,2.21l-2.07,0.58l-2.72,-1.28l-0.15,-2.71l0.85,-2.09l2.01,-0.06l5.06,1.2l0.46,-0.23l-0.14,-0.49l-6.54,-4.47l-2.67,0.55l-1.58,-0.86l2.56,-4.01l-0.03,-0.48l-1.5,-1.74l-4.98,-8.5l-3.13,-1.96l0.03,-1.88l-0.24,-0.37l-6.85,-3.02l-5.36,-0.38l-12.7,0.58l-2.78,-1.57l-3.66,-2.77l5.73,-1.45l5.0,-0.28l0.38,-0.38l-0.35,-0.41l-10.67,-1.38l-5.3,-2.06l0.25,-1.54l18.41,-5.26l1.22,-2.27l-0.25,-0.55l-6.14,-1.86l1.68,-1.77l8.55,-4.03l3.59,-0.63l0.3,-0.54l-0.88,-2.27l5.47,-1.47l7.65,-0.95l7.55,-0.05l3.04,1.85l6.48,-3.27l5.81,2.22l3.56,0.5l5.16,1.94l0.5,-0.21l-0.17,-0.52l-5.71,-3.13l0.28,-2.13l8.12,-3.6l8.7,0.28l3.35,-2.34l8.71,-0.6l19.93,0.8Z",name:"Greenland"},KW:{path:"M540.81,207.91l0.37,0.86l-0.17,0.76l0.6,1.53l-0.95,0.04l-0.82,-1.28l-1.57,-0.18l1.31,-1.88l1.22,0.17Z",name:"Kuwait"},GH:{path:"M420.53,257.51l-0.01,0.72l0.96,1.2l0.24,3.73l0.59,0.95l-0.51,2.1l0.19,1.41l1.02,2.21l-6.97,2.84l-1.8,-0.57l0.04,-0.89l-1.02,-2.04l0.61,-2.65l1.07,-2.32l-0.96,-6.47l5.01,0.07l0.94,-0.39l0.61,0.11Z",name:"Ghana"},OM:{path:"M568.09,230.93l-0.91,1.67l-1.22,0.04l-0.6,0.76l-0.41,1.51l0.27,1.58l-1.16,0.05l-1.56,0.97l-0.76,1.74l-1.62,0.05l-0.98,0.65l-0.17,1.15l-0.89,0.52l-1.49,-0.18l-2.4,0.94l-2.47,-5.4l7.35,-2.71l1.67,-5.23l-1.12,-2.09l0.05,-0.83l0.67,-1.0l0.07,-1.05l0.9,-0.42l-0.05,-2.07l0.7,-0.01l1.0,1.62l1.51,1.08l3.3,0.84l1.73,2.29l0.81,0.37l-1.23,2.35l-0.99,0.79Z",name:"Oman"},_2:{path:"M531.15,258.94l1.51,0.12l5.13,-0.95l5.3,-1.48l-0.01,4.4l-2.67,3.39l-1.85,0.01l-8.04,-2.94l-2.55,-3.17l1.12,-1.71l2.04,2.34Z",name:"Somaliland"},_1:{path:"M472.77,172.64l-1.08,-1.29l0.96,-0.77l0.29,-0.83l1.98,1.64l-0.36,0.67l-1.79,0.58Z",name:"Kosovo"},_0:{path:"M504.91,192.87l0.34,0.01l0.27,-0.07l-0.29,0.26l-0.31,-0.2Z",name:"N. Cyprus"},JO:{path:"M518.64,201.38l-5.14,1.56l-0.19,0.65l2.16,2.39l-0.89,1.14l-1.71,0.34l-1.71,1.8l-2.34,-0.37l1.21,-4.32l0.56,-4.07l2.8,0.94l4.46,-2.71l0.79,2.66Z",name:"Jordan"},HR:{path:"M455.59,162.84l1.09,0.07l-0.82,0.94l-0.27,-1.01ZM456.96,162.92l0.62,-0.41l1.73,0.45l0.42,-0.4l-0.01,-0.59l0.86,-0.52l0.2,-1.05l1.63,-0.68l2.57,1.68l2.07,0.6l0.87,-0.31l1.05,1.57l-0.52,0.63l-1.05,-0.56l-1.68,0.04l-2.1,-0.5l-1.29,0.06l-0.57,0.49l-0.59,-0.47l-0.62,0.16l-0.46,1.7l1.79,2.42l2.79,2.75l-1.18,-0.87l-2.21,-0.87l-1.67,-1.78l0.13,-0.63l-1.05,-1.19l-0.32,-1.27l-1.42,-0.43Z",name:"Croatia"},HT:{path:"M237.05,238.38l-1.16,0.43l-0.91,-0.55l0.05,-0.2l2.02,0.31ZM237.53,238.43l1.06,0.12l-0.05,0.01l-1.01,-0.12ZM239.25,238.45l0.79,-0.51l0.06,-0.62l-1.02,-1.0l0.02,-0.82l-0.3,-0.4l-0.93,-0.32l3.16,0.45l0.02,1.84l-0.48,0.34l-0.08,0.58l0.54,0.72l-1.78,-0.26Z",name:"Haiti"},HU:{path:"M462.08,157.89l0.65,-1.59l-0.09,-0.44l0.64,-0.0l0.39,-0.34l0.1,-0.69l1.75,0.87l2.32,-0.37l0.43,-0.66l3.49,-0.78l0.69,-0.78l0.57,-0.14l2.57,0.93l0.67,-0.23l1.03,0.65l0.08,0.37l-1.42,0.71l-2.59,4.14l-1.8,0.53l-1.68,-0.1l-2.74,1.23l-1.85,-0.54l-2.54,-1.66l-0.66,-1.1Z",name:"Hungary"},HN:{path:"M199.6,249.52l-1.7,-1.21l0.06,-0.94l3.04,-2.14l2.37,0.28l1.27,-0.09l1.1,-0.52l1.3,0.28l1.14,-0.25l1.38,0.37l2.23,1.37l-2.36,0.93l-1.23,-0.39l-0.88,1.3l-1.28,0.99l-0.98,-0.22l-0.42,0.52l-0.96,0.05l-0.36,0.41l0.04,0.88l-0.52,0.6l-0.3,0.04l-0.3,-0.55l-0.66,-0.31l0.11,-0.67l-0.48,-0.65l-0.87,-0.26l-0.73,0.2Z",name:"Honduras"},PR:{path:"M256.17,238.73l-0.26,0.27l-2.83,0.05l-0.07,-0.55l1.95,-0.1l1.22,0.33Z",name:"Puerto Rico"},PS:{path:"M509.21,203.07l0.1,-0.06l-0.02,0.03l-0.09,0.03ZM509.36,202.91l-0.02,-0.63l-0.33,-0.16l0.31,-1.09l0.24,0.1l-0.2,1.78Z",name:"Palestine"},PT:{path:"M401.84,187.38l-0.64,0.47l-1.13,-0.35l-0.91,0.17l0.28,-1.78l-0.24,-1.78l-1.25,-0.56l-0.45,-0.84l0.17,-1.66l1.01,-1.18l0.69,-2.92l-0.04,-1.39l-0.59,-1.9l1.3,-0.85l0.84,1.35l3.1,-0.3l0.46,0.99l-1.05,0.94l-0.03,2.16l-0.41,0.57l-0.08,1.1l-0.79,0.18l-0.26,0.59l0.91,1.6l-0.63,1.75l0.76,1.09l-1.1,1.52l0.07,1.05Z",name:"Portugal"},PY:{path:"M274.9,336.12l0.74,1.52l-0.16,3.45l0.32,0.41l2.64,0.5l1.11,-0.47l1.4,0.59l0.36,0.6l0.53,3.42l1.27,0.4l0.98,-0.38l0.51,0.27l-0.0,1.18l-1.21,5.32l-2.09,1.9l-1.8,0.4l-4.71,-0.98l2.2,-3.63l-0.32,-1.5l-2.78,-1.28l-3.03,-1.94l-2.07,-0.44l-4.34,-4.06l0.91,-2.9l0.08,-1.42l1.07,-2.04l4.13,-0.72l2.18,0.03l2.05,1.17l0.03,0.59Z",name:"Paraguay"},PA:{path:"M213.8,263.68l0.26,-1.52l-0.36,-0.26l-0.01,-0.49l0.44,-0.1l0.93,1.4l1.26,0.03l0.77,0.49l1.38,-0.23l2.51,-1.11l0.86,-0.72l3.45,0.85l1.4,1.18l0.41,1.74l-0.21,0.34l-0.53,-0.12l-0.47,0.29l-0.16,0.6l-0.68,-1.28l0.45,-0.49l-0.19,-0.66l-0.47,-0.13l-0.54,-0.84l-1.5,-0.75l-1.1,0.16l-0.75,0.99l-1.62,0.84l-0.18,0.96l0.85,0.97l-0.58,0.45l-0.69,0.08l-0.34,-1.18l-1.27,0.03l-0.71,-1.05l-2.59,-0.46Z",name:"Panama"},PG:{path:"M808.58,298.86l2.54,2.56l-0.13,0.26l-0.33,0.12l-0.87,-0.78l-1.22,-2.16ZM801.41,293.04l0.5,0.29l0.26,0.27l-0.49,-0.35l-0.27,-0.21ZM803.17,294.58l0.59,0.5l0.08,1.06l-0.29,-0.91l-0.38,-0.65ZM796.68,298.41l0.52,0.75l1.43,-0.19l2.27,-1.81l-0.01,-1.43l1.12,0.16l-0.04,1.1l-0.7,1.28l-1.12,0.18l-0.62,0.79l-2.46,1.11l-1.17,-0.0l-3.08,-1.25l3.41,0.0l0.45,-0.68ZM789.15,303.55l2.31,1.8l1.59,2.61l1.34,0.13l-0.06,0.66l0.31,0.43l1.06,0.24l0.06,0.65l2.25,1.05l-1.22,0.13l-0.72,-0.63l-4.56,-0.65l-3.22,-2.87l-1.49,-2.34l-3.27,-1.1l-2.38,0.72l-1.59,0.86l-0.2,0.42l0.27,1.55l-1.55,0.68l-1.36,-0.4l-2.21,-0.09l-0.08,-15.41l8.39,2.93l2.95,2.4l0.6,1.64l4.02,1.49l0.31,0.68l-1.76,0.21l-0.33,0.52l0.55,1.68Z",name:"Papua New Guinea"},PE:{path:"M244.96,295.21l-1.26,-0.07l-0.57,0.42l-1.93,0.45l-2.98,1.75l-0.36,1.36l-0.58,0.8l0.12,1.37l-1.24,0.59l-0.22,1.22l-0.62,0.84l1.04,2.27l1.28,1.44l-0.41,0.84l0.32,0.57l1.48,0.13l1.16,1.37l2.21,0.07l1.63,-1.08l-0.13,3.02l0.3,0.4l1.14,0.29l1.31,-0.34l1.9,3.59l-0.48,0.85l-0.17,3.85l-0.94,1.59l0.35,0.75l-0.47,1.07l0.98,1.97l-2.1,3.82l-0.98,0.5l-2.17,-1.28l-0.39,-1.16l-4.95,-2.58l-4.46,-2.79l-1.84,-1.51l-0.91,-1.84l0.3,-0.96l-2.11,-3.33l-4.82,-9.68l-1.04,-1.2l-0.87,-1.94l-3.4,-2.48l0.58,-1.18l-1.13,-2.23l0.66,-1.49l1.45,-1.15l-0.6,0.98l0.07,0.92l0.47,0.36l1.74,0.03l0.97,1.17l0.54,0.07l1.42,-1.03l0.6,-1.84l1.42,-2.02l3.04,-1.04l2.73,-2.62l0.86,-1.74l-0.1,-1.87l1.44,1.02l0.9,1.25l1.06,0.59l1.7,2.73l1.86,0.31l1.45,-0.61l0.96,0.39l1.36,-0.19l1.45,0.89l-1.4,2.21l0.31,0.61l0.59,0.05l0.47,0.5Z",name:"Peru"},PK:{path:"M615.09,192.34l-1.83,1.81l-2.6,0.39l-3.73,-0.68l-1.58,1.33l-0.09,0.42l1.77,4.39l1.7,1.23l-1.69,1.27l-0.12,2.14l-2.33,2.64l-1.6,2.8l-2.46,2.67l-3.03,-0.07l-2.76,2.83l0.05,0.6l1.5,1.11l0.26,1.9l1.44,1.5l0.37,1.68l-5.01,-0.01l-1.78,1.7l-1.42,-0.52l-0.76,-1.87l-2.27,-2.15l-11.61,0.86l0.71,-2.34l3.43,-1.32l0.25,-0.44l-0.21,-1.24l-1.2,-0.65l-0.28,-2.46l-2.29,-1.14l-1.28,-1.94l2.82,0.94l2.62,-0.38l1.42,0.33l0.76,-0.56l1.71,0.19l3.25,-1.14l0.27,-0.36l0.08,-2.19l1.18,-1.32l1.68,0.0l0.58,-0.82l1.6,-0.3l1.19,0.16l0.98,-0.78l0.02,-1.88l0.93,-1.47l1.48,-0.66l0.19,-0.55l-0.66,-1.25l2.04,-0.11l0.69,-1.01l-0.02,-1.16l1.11,-1.06l-0.17,-1.78l-0.49,-1.03l1.15,-0.98l5.42,-0.91l2.6,-0.82l1.6,1.16l0.97,2.34l3.45,0.97Z",name:"Pakistan"},PH:{path:"M737.01,263.84l0.39,2.97l-0.44,1.18l-0.55,-1.53l-0.67,-0.14l-1.17,1.28l0.65,2.09l-0.42,0.69l-2.48,-1.23l-0.57,-1.49l0.65,-1.03l-0.1,-0.54l-1.59,-1.19l-0.56,0.08l-0.65,0.87l-1.23,0.0l-1.58,0.97l0.83,-1.8l2.56,-1.42l0.65,0.84l0.45,0.13l1.9,-0.69l0.56,-1.11l1.5,-0.06l0.38,-0.43l-0.09,-1.19l1.21,0.71l0.36,2.02ZM733.59,256.58l0.05,0.75l0.08,0.26l-0.8,-0.42l-0.18,-0.71l0.85,0.12ZM734.08,256.1l-0.12,-1.12l-1.0,-1.27l1.36,0.03l0.53,0.73l0.51,2.04l-1.27,-0.4ZM733.76,257.68l0.38,0.98l-0.32,0.15l-0.07,-1.13ZM724.65,238.43l1.46,0.7l0.72,-0.31l-0.32,1.17l0.79,1.71l-0.57,1.84l-1.53,1.04l-0.39,2.25l0.56,2.04l1.63,0.57l1.16,-0.27l2.71,1.23l-0.19,1.08l0.76,0.84l-0.08,0.36l-1.4,-0.9l-0.88,-1.27l-0.66,0.0l-0.38,0.55l-1.6,-1.31l-2.15,0.36l-0.87,-0.39l0.07,-0.61l0.66,-0.55l-0.01,-0.62l-0.75,-0.59l-0.72,0.44l-0.74,-0.87l-0.39,-2.49l0.32,0.27l0.66,-0.28l0.26,-3.97l0.7,-2.02l1.14,0.0ZM731.03,258.87l-0.88,0.85l-1.19,1.94l-1.05,-1.19l0.93,-1.1l0.32,-1.47l0.52,-0.06l-0.27,1.15l0.22,0.45l0.49,-0.12l1.0,-1.32l-0.08,0.85ZM726.83,255.78l0.83,0.38l1.17,-0.0l-0.02,0.48l-2.0,1.4l0.03,-2.26ZM724.81,252.09l-0.38,1.27l-1.42,-1.95l1.2,0.05l0.6,0.63ZM716.55,261.82l1.1,-0.95l0.03,-0.03l-0.28,0.36l-0.85,0.61ZM719.22,259.06l0.04,-0.06l0.8,-1.53l0.16,0.75l-1.0,0.84Z",name:"Philippines"},PL:{path:"M468.44,149.42l-1.11,-1.54l-1.86,-0.33l-0.48,-1.05l-1.72,-0.37l-0.65,0.69l-0.72,-0.36l0.11,-0.61l-0.33,-0.46l-1.75,-0.27l-1.04,-0.93l-0.94,-1.94l0.16,-1.22l-0.62,-1.8l-0.78,-1.07l0.57,-1.04l-0.48,-1.43l1.41,-0.83l6.91,-2.71l2.14,0.5l0.52,0.91l5.51,0.44l4.55,-0.05l1.07,0.31l0.48,0.84l0.15,1.58l0.65,1.2l-0.01,0.99l-1.27,0.58l-0.19,0.54l0.73,1.48l0.08,1.55l1.2,2.76l-0.17,0.58l-1.23,0.44l-2.27,2.72l0.18,0.95l-1.97,-1.03l-1.98,0.4l-1.36,-0.28l-1.24,0.58l-1.07,-0.97l-1.16,0.24Z",name:"Poland"},ZM:{path:"M481.47,313.3l0.39,0.31l2.52,0.14l0.99,1.17l2.01,0.35l1.4,-0.64l0.69,1.17l1.78,0.33l1.84,2.35l2.23,0.18l0.4,-0.43l-0.21,-2.74l-0.62,-0.3l-0.48,0.32l-1.98,-1.17l0.72,-5.29l-0.51,-1.18l0.57,-1.3l3.68,-0.62l0.26,0.63l1.21,0.63l0.9,-0.22l2.16,0.67l1.33,0.71l1.07,1.02l0.56,1.87l-0.88,2.7l0.43,2.09l-0.73,0.87l-0.76,2.37l0.59,0.68l-6.6,1.83l-0.29,0.44l0.19,1.45l-1.68,0.35l-1.43,1.02l-0.38,0.87l-0.87,0.26l-3.48,3.69l-4.16,-0.53l-1.52,-1.0l-1.77,-0.13l-1.83,0.52l-3.04,-3.4l0.11,-7.59l4.82,0.03l0.39,-0.49l-0.18,-0.76l0.33,-0.83l-0.4,-1.36l0.24,-1.05Z",name:"Zambia"},EH:{path:"M384.42,230.28l0.25,-0.79l1.06,-1.29l0.8,-3.51l3.38,-2.78l0.7,-1.81l0.06,4.84l-1.98,0.2l-0.94,1.59l0.39,3.56l-3.7,-0.01ZM392.01,218.1l0.7,-1.8l1.77,-0.24l2.09,0.34l0.95,-0.62l1.28,-0.07l-0.0,2.51l-6.79,-0.12Z",name:"W. Sahara"},EE:{path:"M485.71,115.04l2.64,0.6l2.56,0.11l-1.6,1.91l0.61,3.54l-0.81,0.87l-1.78,-0.01l-3.22,-1.76l-1.8,0.45l0.21,-1.53l-0.58,-0.41l-0.69,0.34l-1.26,-1.03l-0.17,-1.63l2.83,-0.92l3.05,-0.52Z",name:"Estonia"},EG:{path:"M492.06,205.03l1.46,0.42l2.95,-1.64l2.04,-0.21l1.53,0.3l0.59,1.19l0.69,0.04l0.41,-0.64l1.81,0.58l1.95,0.16l1.04,-0.51l1.42,4.08l-2.03,4.54l-1.66,-1.77l-1.76,-3.85l-0.64,-0.12l-0.36,0.67l1.04,2.88l3.44,6.95l1.78,3.04l2.03,2.65l-0.36,0.53l0.23,2.01l2.7,2.19l-28.41,0.0l0.0,-18.96l-0.73,-2.2l0.59,-1.56l-0.32,-1.26l0.68,-0.99l3.06,-0.04l4.82,1.52Z",name:"Egypt"},ZA:{path:"M467.14,373.21l-0.13,-1.96l-0.68,-1.56l0.7,-0.68l-0.13,-2.33l-4.56,-8.19l0.77,-0.86l0.6,0.45l0.69,1.31l2.83,0.72l1.5,-0.26l2.24,-1.39l0.19,-9.55l1.35,2.3l-0.21,1.5l0.61,1.2l0.4,0.19l1.79,-0.27l2.6,-2.07l0.69,-1.32l0.96,-0.48l2.19,1.04l2.04,0.13l1.77,-0.65l0.85,-2.12l1.38,-0.33l1.59,-2.76l2.15,-1.89l3.41,-1.87l2.0,0.45l1.02,-0.28l0.99,0.2l1.75,5.29l-0.38,3.25l-0.81,-0.23l-1.0,0.46l-0.87,1.68l-0.05,1.16l1.97,1.84l1.47,-0.29l0.69,-1.18l1.09,0.01l-0.76,3.69l-0.58,1.09l-2.2,1.79l-3.17,4.76l-2.8,2.83l-3.57,2.88l-2.53,1.05l-1.22,0.14l-0.51,0.7l-1.18,-0.32l-1.39,0.5l-2.59,-0.52l-1.61,0.33l-1.18,-0.11l-2.55,1.1l-2.1,0.44l-1.6,1.07l-0.85,0.05l-0.93,-0.89l-0.93,-0.15l-0.97,-1.13l-0.25,0.05ZM491.45,364.19l0.62,-0.93l1.48,-0.59l1.18,-2.19l-0.07,-0.49l-1.99,-1.69l-1.66,0.56l-1.43,1.14l-1.34,1.73l0.02,0.51l1.88,2.11l1.31,-0.16Z",name:"South Africa"},EC:{path:"M231.86,285.53l0.29,1.59l-0.69,1.45l-2.61,2.51l-3.13,1.11l-1.53,2.18l-0.49,1.68l-1.0,0.73l-1.02,-1.11l-1.78,-0.16l0.67,-1.15l-0.24,-0.86l1.25,-2.13l-0.54,-1.09l-0.67,-0.08l-0.72,0.87l-0.87,-0.64l0.35,-0.69l-0.36,-1.96l0.81,-0.51l0.45,-1.51l0.92,-1.57l-0.07,-0.97l2.65,-1.33l2.75,1.35l0.77,1.05l2.12,0.35l0.76,-0.32l1.96,1.21Z",name:"Ecuador"},AL:{path:"M470.32,171.8l0.74,0.03l0.92,0.89l-0.17,1.95l0.36,1.28l1.01,0.82l-1.82,2.83l-0.19,-0.61l-1.25,-0.89l-0.18,-1.2l0.53,-2.82l-0.54,-1.47l0.6,-0.83Z",name:"Albania"},AO:{path:"M461.55,300.03l1.26,3.15l1.94,2.36l2.47,-0.53l1.25,0.32l0.44,-0.18l0.93,-1.92l1.31,-0.08l0.41,-0.44l0.47,-0.0l-0.1,0.41l0.39,0.49l2.65,-0.02l0.03,1.19l0.48,1.01l-0.34,1.52l0.18,1.55l0.83,1.04l-0.13,2.85l0.54,0.39l3.96,-0.41l-0.1,1.79l0.39,1.05l-0.24,1.43l-4.7,-0.03l-0.4,0.39l-0.12,8.13l2.92,3.49l-3.83,0.88l-5.89,-0.36l-1.88,-1.24l-10.47,0.22l-1.3,-1.01l-1.85,-0.16l-2.4,0.77l-0.15,-1.06l0.33,-2.16l1.0,-3.45l1.35,-3.2l2.24,-2.8l0.33,-2.06l-0.13,-1.53l-0.8,-1.08l-1.21,-2.87l0.87,-1.62l-1.27,-4.12l-1.17,-1.53l2.47,-0.63l7.03,0.03ZM451.71,298.87l-0.47,-1.25l1.25,-1.11l0.32,0.3l-0.99,1.03l-0.12,1.03Z",name:"Angola"},KZ:{path:"M552.8,172.89l0.46,-1.27l-0.48,-1.05l-2.96,-1.19l-1.06,-2.58l-1.37,-0.87l-0.03,-0.3l1.95,0.23l0.45,-0.38l0.08,-1.96l1.75,-0.41l2.1,0.45l0.48,-0.33l0.45,-3.04l-0.45,-2.09l-0.41,-0.31l-2.42,0.15l-2.36,-0.73l-2.87,1.37l-2.17,0.61l-0.85,-0.34l0.13,-1.61l-1.6,-2.12l-2.02,-0.08l-1.78,-1.82l1.29,-2.18l-0.57,-0.95l1.62,-2.91l2.21,1.63l0.63,-0.27l0.29,-2.22l4.92,-3.43l3.71,-0.08l8.4,3.6l2.92,-1.36l3.77,-0.06l3.11,1.66l0.51,-0.11l0.6,-0.81l3.31,0.13l0.39,-0.25l0.63,-1.57l-0.17,-0.5l-3.5,-1.98l1.87,-1.27l-0.13,-1.03l1.98,-0.72l0.18,-0.62l-1.59,-2.06l0.81,-0.82l9.23,-1.18l1.33,-0.88l6.18,-1.26l2.26,-1.42l4.08,0.68l0.73,3.33l0.51,0.3l2.48,-0.8l2.79,1.02l-0.17,1.56l0.43,0.44l2.55,-0.24l4.89,-2.53l0.03,0.32l3.15,2.61l5.56,8.47l0.65,0.02l1.12,-1.46l3.15,1.74l3.76,-0.78l1.15,0.49l1.14,1.8l1.84,0.76l0.99,1.29l3.35,-0.25l1.02,1.52l-1.6,1.81l-1.93,0.28l-0.34,0.38l-0.11,3.05l-1.13,1.16l-4.75,-1.0l-0.46,0.27l-1.76,5.47l-1.1,0.59l-4.91,1.23l-0.27,0.54l2.1,4.97l-1.37,0.63l-0.23,0.41l0.13,1.13l-0.88,-0.25l-1.42,-1.13l-7.89,-0.4l-0.92,0.31l-3.73,-1.22l-1.42,0.63l-0.53,1.66l-3.72,-0.94l-1.85,0.43l-0.76,1.4l-4.65,2.62l-1.13,2.08l-0.44,0.01l-0.92,-1.4l-2.87,-0.09l-0.45,-2.14l-0.38,-0.32l-0.8,-0.01l0.0,-2.96l-3.0,-2.22l-7.31,0.58l-2.35,-2.68l-6.71,-3.69l-6.45,1.83l-0.29,0.39l0.1,10.85l-0.7,0.08l-1.62,-2.17l-1.83,-0.96l-3.11,0.59l-0.64,0.51Z",name:"Kazakhstan"},ET:{path:"M516.04,247.79l1.1,0.84l1.63,-0.45l0.68,0.47l1.63,0.03l2.01,0.94l1.73,1.66l1.64,2.07l-1.52,2.04l0.16,1.72l0.39,0.38l2.05,0.0l-0.36,1.03l2.86,3.58l8.32,3.08l1.31,0.02l-6.32,6.75l-3.1,0.11l-2.36,1.77l-1.47,0.04l-0.86,0.79l-1.38,-0.0l-1.32,-0.81l-2.29,1.05l-0.76,0.98l-3.29,-0.41l-3.07,-2.07l-1.8,-0.07l-0.62,-0.6l0.0,-1.24l-0.28,-0.38l-1.15,-0.37l-1.4,-2.59l-1.19,-0.68l-0.47,-1.0l-1.27,-1.23l-1.16,-0.22l0.43,-0.72l1.45,-0.28l0.41,-0.95l-0.03,-2.21l0.68,-2.44l1.05,-0.63l1.43,-3.06l1.57,-1.37l1.02,-2.51l0.35,-1.88l2.52,0.46l0.44,-0.24l0.58,-1.43Z",name:"Ethiopia"},ZW:{path:"M498.91,341.09l-1.11,-0.22l-0.92,0.28l-2.09,-0.44l-1.5,-1.11l-1.89,-0.43l-0.62,-1.4l-0.01,-0.84l-0.3,-0.38l-0.97,-0.25l-2.71,-2.74l-1.92,-3.32l3.83,0.45l3.73,-3.82l1.08,-0.44l0.26,-0.77l1.25,-0.9l1.41,-0.26l0.5,0.89l1.99,-0.05l1.72,1.17l1.11,0.17l1.05,0.66l0.01,2.99l-0.59,3.76l0.38,0.86l-0.23,1.23l-0.39,0.35l-0.63,1.81l-2.43,2.75Z",name:"Zimbabwe"},ES:{path:"M416.0,169.21l1.07,1.17l4.61,1.38l1.06,-0.57l2.6,1.26l2.71,-0.3l0.09,1.12l-2.14,1.8l-3.11,0.61l-0.31,0.31l-0.2,0.89l-1.54,1.69l-0.97,2.4l0.84,1.74l-1.32,1.27l-0.48,1.68l-1.88,0.65l-1.66,2.07l-5.36,-0.01l-1.79,1.08l-0.89,0.98l-0.88,-0.17l-0.79,-0.82l-0.68,-1.59l-2.37,-0.63l-0.11,-0.5l1.21,-1.82l-0.77,-1.13l0.61,-1.68l-0.76,-1.62l0.87,-0.49l0.09,-1.25l0.42,-0.6l0.03,-2.11l0.99,-0.69l0.13,-0.5l-1.03,-1.73l-1.46,-0.11l-0.61,0.38l-1.06,0.0l-0.52,-1.23l-0.53,-0.21l-1.32,0.67l-0.01,-1.49l-0.75,-0.96l3.03,-1.88l2.99,0.53l3.32,-0.02l2.63,0.51l6.01,-0.06Z",name:"Spain"},ER:{path:"M520.38,246.23l3.42,2.43l3.5,3.77l0.84,0.54l-0.95,-0.01l-3.51,-3.89l-2.33,-1.15l-1.73,-0.07l-0.91,-0.51l-1.26,0.51l-1.34,-1.02l-0.61,0.17l-0.66,1.61l-2.35,-0.43l-0.17,-0.67l1.29,-5.29l0.61,-0.61l1.95,-0.53l0.87,-1.01l1.17,2.41l0.68,2.33l1.49,1.43Z",name:"Eritrea"},ME:{path:"M468.91,172.53l-1.22,-1.02l0.47,-1.81l0.89,-0.72l2.26,1.51l-0.5,0.57l-0.75,-0.27l-1.14,1.73Z",name:"Montenegro"},MD:{path:"M488.41,153.73l1.4,-0.27l1.72,0.93l1.07,0.15l0.85,0.65l-0.14,0.84l0.96,0.85l1.12,2.47l-1.15,-0.07l-0.66,-0.41l-0.52,0.25l-0.09,0.86l-1.08,1.89l-0.27,-0.86l0.25,-1.34l-0.16,-1.6l-3.29,-4.34Z",name:"Moldova"},MG:{path:"M545.91,319.14l0.4,3.03l0.62,1.21l-0.21,1.02l-0.57,-0.8l-0.69,-0.01l-0.47,0.76l0.41,2.12l-0.18,0.87l-0.73,0.78l-0.15,2.14l-4.71,15.2l-1.06,2.88l-3.92,1.64l-3.12,-1.49l-0.6,-1.21l-0.19,-2.4l-0.86,-2.05l-0.21,-1.77l0.38,-1.62l1.21,-0.75l0.01,-0.76l1.19,-2.04l0.23,-1.66l-1.06,-2.99l-0.19,-2.21l0.81,-1.33l0.32,-1.46l4.63,-1.22l3.44,-3.0l0.85,-1.4l-0.08,-0.7l0.78,-0.04l1.38,-1.77l0.13,-1.64l0.45,-0.61l1.16,1.69l0.59,1.6Z",name:"Madagascar"},MA:{path:"M378.78,230.02l0.06,-0.59l0.92,-0.73l0.82,-1.37l-0.09,-1.04l0.79,-1.7l1.31,-1.58l0.96,-0.59l0.66,-1.55l0.09,-1.47l0.81,-1.48l1.72,-1.07l1.55,-2.69l1.16,-0.96l2.44,-0.39l1.94,-1.82l1.31,-0.78l2.09,-2.28l-0.51,-3.65l1.24,-3.7l1.5,-1.75l4.46,-2.57l2.37,-4.47l1.44,0.01l1.68,1.21l2.32,-0.19l3.47,0.65l0.8,1.54l0.16,1.71l0.86,2.96l0.56,0.59l-0.26,0.61l-3.05,0.44l-1.26,1.05l-1.33,0.22l-0.33,0.37l-0.09,1.78l-2.68,1.0l-1.07,1.42l-4.47,1.13l-4.04,2.01l-0.54,4.64l-1.15,0.06l-0.92,0.61l-1.96,-0.35l-2.42,0.54l-0.74,1.9l-0.86,0.4l-1.14,3.26l-3.53,3.01l-0.8,3.55l-0.96,1.1l-0.29,0.82l-4.95,0.18Z",name:"Morocco"},UZ:{path:"M598.64,172.75l-1.63,1.52l0.06,0.64l1.85,1.12l1.97,-0.64l2.21,1.17l-2.52,1.68l-2.59,-0.22l-0.18,-0.41l0.46,-1.23l-0.45,-0.53l-3.35,0.69l-2.1,3.51l-1.87,-0.12l-1.03,1.51l0.22,0.55l1.64,0.62l0.46,1.83l-1.19,2.49l-2.66,-0.53l0.05,-1.36l-0.26,-0.39l-3.3,-1.23l-2.56,-1.4l-4.4,-3.34l-1.34,-3.14l-1.08,-0.6l-2.58,0.13l-0.69,-0.44l-0.47,-2.52l-3.37,-1.6l-0.43,0.05l-2.07,1.72l-2.1,1.01l-0.21,0.47l0.28,1.01l-1.91,0.03l-0.09,-10.5l5.99,-1.7l6.19,3.54l2.71,2.84l7.05,-0.67l2.71,2.01l-0.17,2.81l0.39,0.42l0.9,0.02l0.44,2.14l0.38,0.32l2.94,0.09l0.95,1.42l1.28,-0.24l1.05,-2.04l4.43,-2.5Z",name:"Uzbekistan"},MM:{path:"M673.9,230.21l-1.97,1.57l-0.57,0.96l-1.4,0.6l-1.36,1.05l-1.99,0.36l-1.08,2.66l-0.91,0.4l-0.19,0.55l1.21,2.27l2.52,3.43l-0.79,1.91l-0.74,0.41l-0.17,0.52l0.65,1.37l1.61,1.95l0.25,2.58l0.9,2.13l-1.92,3.57l0.68,-2.25l-0.81,-1.74l0.19,-2.65l-1.05,-1.53l-1.24,-6.17l-1.12,-2.26l-0.6,-0.13l-4.34,3.02l-2.39,-0.65l0.77,-2.84l-0.52,-2.61l-1.91,-2.96l0.25,-0.75l-0.29,-0.51l-1.33,-0.3l-1.61,-1.93l-0.1,-1.3l0.82,-0.24l0.04,-1.64l1.02,-0.52l0.21,-0.45l-0.23,-0.95l0.54,-0.96l0.08,-2.22l1.46,0.45l0.47,-0.2l1.12,-2.19l0.16,-1.35l1.33,-2.16l-0.0,-1.52l2.89,-1.66l1.63,0.44l0.5,-0.44l-0.17,-1.4l0.64,-0.36l0.08,-1.04l0.77,-0.11l0.71,1.35l1.06,0.69l-0.03,3.86l-2.38,2.37l-0.3,3.15l0.46,0.43l2.28,-0.38l0.51,2.08l1.47,0.67l-0.6,1.8l0.19,0.48l2.97,1.48l1.64,-0.55l0.02,0.32Z",name:"Myanmar"},ML:{path:"M392.61,254.08l-0.19,-2.37l-0.99,-0.87l-0.44,-1.3l-0.09,-1.28l0.81,-0.58l0.35,-1.24l2.37,0.65l1.31,-0.47l0.86,0.15l0.66,-0.56l9.83,-0.04l0.38,-0.28l0.56,-1.8l-0.44,-0.65l-2.35,-21.95l3.27,-0.04l16.7,11.38l0.74,1.31l2.5,1.09l0.02,1.38l0.44,0.39l2.34,-0.21l0.01,5.38l-1.28,1.61l-0.26,1.49l-5.31,0.57l-1.07,0.92l-2.9,0.1l-0.86,-0.48l-1.38,0.36l-2.4,1.08l-0.6,0.87l-1.85,1.09l-0.43,0.7l-0.79,0.39l-1.44,-0.21l-0.81,0.84l-0.34,1.64l-1.91,2.02l-0.06,1.03l-0.67,1.22l0.13,1.16l-0.97,0.39l-0.23,-0.64l-0.52,-0.24l-1.35,0.4l-0.34,0.55l-2.69,-0.28l-0.37,-0.35l-0.02,-0.9l-0.65,-0.35l0.45,-0.64l-0.03,-0.53l-2.12,-2.44l-0.76,-0.01l-2.0,1.16l-0.78,-0.15l-0.8,-0.67l-1.21,0.23Z",name:"Mali"},MN:{path:"M676.61,146.48l3.81,1.68l5.67,-1.0l2.37,0.41l2.34,1.5l1.79,1.75l2.29,-0.03l3.12,0.52l2.47,-0.81l3.41,-0.59l3.53,-2.21l1.25,0.29l1.53,1.13l2.27,-0.21l-2.66,5.01l0.64,1.68l0.47,0.21l1.32,-0.38l2.38,0.48l2.02,-1.11l1.76,0.89l2.06,2.02l-0.13,0.53l-1.72,-0.29l-3.77,0.46l-1.88,0.99l-1.76,1.99l-3.71,1.17l-2.45,1.6l-3.83,-0.87l-0.41,0.17l-1.31,1.99l1.04,2.24l-1.52,0.9l-1.74,1.57l-2.79,1.02l-3.78,0.13l-4.05,1.05l-2.77,1.52l-1.16,-0.85l-2.94,0.0l-3.62,-1.79l-2.58,-0.49l-3.4,0.41l-5.12,-0.67l-2.63,0.06l-1.31,-1.6l-1.4,-3.0l-1.48,-0.33l-3.13,-1.94l-6.16,-0.93l-0.71,-1.06l0.86,-3.82l-1.93,-2.71l-3.5,-1.18l-1.95,-1.58l-0.5,-1.72l2.34,-0.52l4.75,-2.8l3.62,-1.47l2.18,0.97l2.46,0.05l1.81,1.53l2.46,0.12l3.95,0.71l2.43,-2.28l0.08,-0.48l-0.9,-1.72l2.24,-2.98l2.62,1.27l4.94,1.17l0.43,2.24Z",name:"Mongolia"},MK:{path:"M472.8,173.98l0.49,-0.71l3.57,-0.71l1.0,0.77l0.13,1.45l-0.65,0.53l-1.15,-0.05l-1.12,0.67l-1.39,0.22l-0.79,-0.55l-0.29,-1.03l0.19,-0.6Z",name:"Macedonia"},MW:{path:"M505.5,309.31l0.85,1.95l0.15,2.86l-0.69,1.65l0.71,1.8l0.06,1.28l0.49,0.64l0.07,1.06l0.4,0.55l0.8,-0.23l0.55,0.61l0.69,-0.21l0.34,0.6l0.19,2.94l-1.04,0.62l-0.54,1.25l-1.11,-1.08l-0.16,-1.56l0.51,-1.31l-0.32,-1.3l-0.99,-0.65l-0.82,0.12l-2.36,-1.64l0.63,-1.96l0.82,-1.18l-0.46,-2.01l0.9,-2.86l-0.94,-2.51l0.96,0.18l0.29,0.4Z",name:"Malawi"},MR:{path:"M407.36,220.66l-2.58,0.03l-0.39,0.44l2.42,22.56l0.36,0.43l-0.39,1.24l-9.75,0.04l-0.56,0.53l-0.91,-0.11l-1.27,0.45l-1.61,-0.66l-0.97,0.03l-0.36,0.29l-0.38,1.35l-0.42,0.23l-2.93,-3.4l-2.96,-1.52l-1.62,-0.03l-1.27,0.54l-1.12,-0.2l-0.65,0.4l-0.08,-0.49l0.68,-1.29l0.31,-2.43l-0.57,-3.91l0.23,-1.21l-0.69,-1.5l-1.15,-1.02l0.25,-0.39l9.58,0.02l0.4,-0.45l-0.46,-3.68l0.47,-1.04l2.12,-0.21l0.36,-0.4l-0.08,-6.4l7.81,0.13l0.41,-0.4l0.01,-3.31l7.76,5.35Z",name:"Mauritania"},UG:{path:"M498.55,276.32l0.7,-0.46l1.65,0.5l1.96,-0.57l1.7,0.01l1.45,-0.98l0.91,1.33l1.33,3.95l-2.57,4.03l-1.46,-0.4l-2.54,0.91l-1.37,1.61l-0.01,0.81l-2.42,-0.01l-2.26,1.01l-0.17,-1.59l0.58,-1.04l0.14,-1.94l1.37,-2.28l1.78,-1.58l-0.17,-0.65l-0.72,-0.24l0.13,-2.43Z",name:"Uganda"},MY:{path:"M717.47,273.46l-1.39,0.65l-2.12,-0.41l-2.88,-0.0l-0.38,0.28l-0.84,2.75l-0.99,0.96l-1.21,3.29l-1.73,0.45l-2.45,-0.68l-1.39,0.31l-1.33,1.15l-1.59,-0.14l-1.41,0.44l-1.44,-1.19l-0.18,-0.73l1.34,0.53l1.93,-0.47l0.75,-2.22l4.02,-1.03l2.75,-3.21l0.82,0.94l0.64,-0.05l0.4,-0.65l0.96,0.06l0.42,-0.36l0.24,-2.68l1.81,-1.64l1.21,-1.86l0.63,-0.01l1.07,1.05l0.34,1.28l3.44,1.35l-0.06,0.35l-1.37,0.1l-0.35,0.54l0.32,0.88ZM673.68,269.59l0.17,1.09l0.47,0.33l1.65,-0.3l0.87,-0.94l1.61,1.52l0.98,1.56l-0.12,2.81l0.41,2.29l0.95,0.9l0.88,2.44l-1.27,0.12l-5.1,-3.67l-0.34,-1.29l-1.37,-1.59l-0.33,-1.97l-0.88,-1.4l0.25,-1.68l-0.46,-1.05l1.63,0.84Z",name:"Malaysia"},MX:{path:"M133.12,200.41l0.2,0.47l9.63,3.33l6.96,-0.02l0.4,-0.4l0.0,-0.74l3.77,0.0l3.55,2.93l1.39,2.83l1.52,1.04l2.08,0.82l0.47,-0.14l1.46,-2.0l1.73,-0.04l1.59,0.98l2.05,3.35l1.47,1.56l1.26,3.14l2.18,1.02l2.26,0.58l-1.18,3.72l-0.42,5.04l1.79,4.89l1.62,1.89l0.61,1.52l1.2,1.42l2.55,0.66l1.37,1.1l7.54,-1.89l1.86,-1.3l1.14,-4.3l4.1,-1.21l3.57,-0.11l0.32,0.3l-0.06,0.94l-1.26,1.45l-0.67,1.71l0.38,0.7l-0.72,2.27l-0.49,-0.3l-1.0,0.08l-1.0,1.39l-0.47,-0.11l-0.53,0.47l-4.26,-0.02l-0.4,0.4l-0.0,1.06l-1.1,0.26l0.1,0.44l1.82,1.44l0.56,0.91l-3.19,0.21l-1.21,2.09l0.24,0.72l-0.2,0.44l-2.24,-2.18l-1.45,-0.93l-2.22,-0.69l-1.52,0.22l-3.07,1.16l-10.55,-3.85l-2.86,-1.96l-3.78,-0.92l-1.08,-1.19l-2.62,-1.43l-1.18,-1.54l-0.38,-0.81l0.66,-0.63l-0.18,-0.53l0.52,-0.76l0.01,-0.91l-2.0,-3.82l-2.21,-2.63l-2.53,-2.09l-1.19,-1.62l-2.2,-1.17l-0.3,-0.43l0.34,-1.48l-0.21,-0.45l-1.23,-0.6l-1.36,-1.2l-0.59,-1.78l-1.54,-0.47l-2.44,-2.55l-0.16,-0.9l-1.33,-2.03l-0.84,-1.99l-0.16,-1.33l-1.81,-1.1l-0.97,0.05l-1.31,-0.7l-0.57,0.22l-0.4,1.12l0.72,3.77l3.51,3.89l0.28,0.78l0.53,0.26l0.41,1.43l1.33,1.73l1.58,1.41l0.8,2.39l1.43,2.41l0.13,1.32l0.37,0.36l1.04,0.08l1.67,2.28l-0.85,0.76l-0.66,-1.51l-1.68,-1.54l-2.91,-1.87l0.06,-1.82l-0.54,-1.68l-2.91,-2.03l-0.55,0.09l-1.95,-1.1l-0.88,-0.94l0.68,-0.08l0.93,-1.01l0.08,-1.78l-1.93,-1.94l-1.46,-0.77l-3.75,-7.56l4.88,-0.42Z",name:"Mexico"},VU:{path:"M839.04,322.8l0.22,1.14l-0.44,0.03l-0.2,-1.45l0.42,0.27Z",name:"Vanuatu"},FR:{path:"M444.48,172.62l-0.64,1.78l-0.58,-0.31l-0.49,-1.72l0.4,-0.89l1.0,-0.72l0.3,1.85ZM429.64,147.1l1.78,1.58l1.46,-0.13l2.1,1.42l1.35,0.27l1.23,0.83l3.04,0.5l-1.03,1.85l-0.3,2.12l-0.41,0.32l-0.95,-0.24l-0.5,0.43l0.06,0.61l-1.81,1.92l-0.04,1.42l0.55,0.38l0.88,-0.36l0.61,0.97l-0.03,1.0l0.57,0.91l-0.75,1.09l0.65,2.39l1.27,0.57l-0.18,0.82l-2.01,1.53l-4.77,-0.8l-3.82,1.0l-0.53,1.85l-2.49,0.34l-2.71,-1.31l-1.16,0.57l-4.31,-1.29l-0.72,-0.86l1.19,-1.78l0.39,-6.45l-2.58,-3.3l-1.9,-1.66l-3.72,-1.23l-0.19,-1.72l2.81,-0.61l4.12,0.81l0.47,-0.48l-0.6,-2.77l1.94,0.95l5.83,-2.54l0.92,-2.74l1.6,-0.49l0.24,0.78l1.36,0.33l1.05,1.19ZM289.01,278.39l-0.81,0.8l-0.78,0.12l-0.5,-0.66l-0.56,-0.1l-0.91,0.6l-0.46,-0.22l1.09,-2.96l-0.96,-1.77l-0.17,-1.49l1.07,-1.77l2.32,0.75l2.51,2.01l0.3,0.74l-2.14,3.96Z",name:"France"},FI:{path:"M492.17,76.39l-0.23,3.5l3.52,2.63l-2.08,2.88l-0.02,0.44l2.8,4.56l-1.59,3.31l2.16,3.24l-0.94,2.39l0.14,0.47l3.44,2.51l-0.77,1.62l-7.52,6.95l-4.5,0.31l-4.38,1.37l-3.8,0.74l-1.44,-1.96l-2.17,-1.11l0.5,-3.66l-1.16,-3.33l1.09,-2.08l2.21,-2.42l5.67,-4.32l1.64,-0.83l0.21,-0.42l-0.46,-2.02l-3.38,-1.89l-0.75,-1.43l-0.22,-6.74l-6.79,-4.8l0.8,-0.62l2.54,2.12l3.46,-0.12l3.0,0.96l2.51,-2.11l1.17,-3.08l3.55,-1.38l2.76,1.53l-0.95,2.79Z",name:"Finland"},FJ:{path:"M871.53,326.34l-2.8,1.05l-0.08,-0.23l2.97,-1.21l-0.1,0.39ZM867.58,329.25l0.43,0.37l-0.27,0.88l-1.24,0.28l-1.04,-0.24l-0.14,-0.66l0.63,-0.58l0.92,0.26l0.7,-0.31Z",name:"Fiji"},FK:{path:"M274.36,425.85l1.44,1.08l-0.47,0.73l-3.0,0.89l-0.96,-1.0l-0.52,-0.05l-1.83,1.29l-0.73,-0.88l2.46,-1.64l1.93,0.76l1.67,-1.19Z",name:"Falkland Is."},NI:{path:"M202.33,252.67l0.81,-0.18l1.03,-1.02l-0.04,-0.88l0.68,-0.0l0.63,-0.54l0.97,0.22l1.53,-1.26l0.58,-0.99l1.17,0.34l2.41,-0.94l0.13,1.32l-0.81,1.94l0.1,2.74l-0.36,0.37l-0.11,1.75l-0.47,0.81l0.18,1.14l-1.73,-0.85l-0.71,0.27l-1.47,-0.6l-0.52,0.16l-4.01,-3.81Z",name:"Nicaragua"},NL:{path:"M430.31,143.39l0.6,-0.5l2.13,-4.8l3.2,-1.33l1.74,0.08l0.33,0.8l-0.59,2.92l-0.5,0.99l-1.26,0.0l-0.4,0.45l0.33,2.7l-2.2,-1.78l-2.62,0.58l-0.75,-0.11Z",name:"Netherlands"},NO:{path:"M491.44,67.41l6.8,2.89l-2.29,0.86l-0.15,0.65l2.33,2.38l-4.98,1.79l0.84,-2.45l-0.18,-0.48l-3.55,-1.8l-3.89,1.52l-1.42,3.38l-2.12,1.72l-2.64,-1.0l-3.11,0.21l-2.66,-2.22l-0.5,-0.01l-1.41,1.1l-1.44,0.17l-0.35,0.35l-0.32,2.47l-4.32,-0.64l-0.44,0.29l-0.58,2.11l-2.45,0.2l-4.15,7.68l-3.88,5.76l0.78,1.62l-0.64,1.16l-2.24,-0.06l-0.38,0.24l-1.66,3.89l0.15,5.17l1.57,2.04l-0.78,4.16l-2.02,2.48l-0.85,1.63l-1.3,-1.75l-0.58,-0.07l-4.87,4.19l-3.1,0.79l-3.16,-1.7l-0.85,-3.77l-0.77,-8.55l2.14,-2.31l6.55,-3.27l5.02,-4.17l10.63,-13.84l10.98,-8.7l5.35,-1.91l4.34,0.12l3.69,-3.64l4.49,0.19l4.37,-0.89ZM484.55,20.04l4.26,1.75l-3.1,2.55l-7.1,0.65l-7.08,-0.9l-0.37,-1.31l-0.37,-0.29l-3.44,-0.1l-2.08,-2.0l6.87,-1.44l3.9,1.31l2.39,-1.64l6.13,1.4ZM481.69,33.93l-4.45,1.74l-3.54,-0.99l1.12,-0.9l0.05,-0.58l-1.06,-1.22l4.22,-0.89l1.09,1.97l2.57,0.87ZM466.44,24.04l7.43,3.77l-5.41,1.86l-1.58,4.08l-2.26,1.2l-1.12,4.11l-2.61,0.18l-4.79,-2.86l1.84,-1.54l-0.1,-0.68l-3.69,-1.53l-4.77,-4.51l-1.73,-3.89l6.11,-1.82l1.54,1.92l3.57,-0.08l1.2,-1.96l3.32,-0.18l3.05,1.92Z",name:"Norway"},NA:{path:"M474.26,330.66l-0.97,0.04l-0.38,0.4l-0.07,8.9l-2.09,0.08l-0.39,0.4l-0.0,17.42l-1.98,1.23l-1.17,0.17l-2.44,-0.66l-0.48,-1.13l-0.99,-0.74l-0.54,0.05l-0.9,1.01l-1.53,-1.68l-0.93,-1.88l-1.99,-8.56l-0.06,-3.12l-0.33,-1.52l-2.3,-3.34l-1.91,-4.83l-1.96,-2.43l-0.12,-1.57l2.33,-0.79l1.43,0.07l1.81,1.13l10.23,-0.25l1.84,1.23l5.87,0.35ZM474.66,330.64l6.51,-1.6l1.9,0.39l-1.69,0.4l-1.31,0.83l-1.12,-0.94l-4.29,0.92Z",name:"Namibia"},NC:{path:"M838.78,341.24l-0.33,0.22l-2.9,-1.75l-3.26,-3.37l1.65,0.83l4.85,4.07Z",name:"New Caledonia"},NE:{path:"M454.75,226.53l1.33,1.37l0.48,0.07l1.27,-0.7l0.53,3.52l0.94,0.83l0.17,0.92l0.81,0.69l-0.44,0.95l-0.96,5.26l-0.13,3.22l-3.04,2.31l-1.22,3.57l1.02,1.24l-0.0,1.46l0.39,0.4l1.13,0.04l-0.9,1.25l-1.47,-2.42l-0.86,-0.29l-2.09,1.37l-1.74,-0.67l-1.45,-0.17l-0.85,0.35l-1.36,-0.07l-1.64,1.09l-1.06,0.05l-2.94,-1.28l-1.44,0.59l-1.01,-0.03l-0.97,-0.94l-2.7,-0.98l-2.69,0.3l-0.87,0.64l-0.47,1.6l-0.75,1.16l-0.12,1.53l-1.57,-1.1l-1.31,0.24l0.03,-0.81l-0.32,-0.41l-2.59,-0.52l-0.15,-1.16l-1.35,-1.6l-0.29,-1.0l0.13,-0.84l1.29,-0.08l1.08,-0.92l3.31,-0.22l2.22,-0.41l0.32,-0.34l0.2,-1.47l1.39,-1.88l-0.01,-5.66l3.36,-1.12l7.24,-5.12l8.42,-4.92l3.69,1.06Z",name:"Niger"},NG:{path:"M456.32,253.89l0.64,0.65l-0.28,1.04l-2.11,2.01l-2.03,5.18l-1.37,1.16l-1.15,3.18l-1.33,0.66l-1.46,-0.97l-1.21,0.16l-1.38,1.36l-0.91,0.24l-1.79,4.06l-2.33,0.81l-1.11,-0.07l-0.86,0.5l-1.71,-0.05l-1.19,-1.39l-0.89,-1.89l-1.77,-1.66l-3.95,-0.08l0.07,-5.21l0.42,-1.43l1.95,-2.3l-0.14,-0.91l0.43,-1.18l-0.53,-1.41l0.25,-2.92l0.72,-1.07l0.32,-1.34l0.46,-0.39l2.47,-0.28l2.34,0.89l1.15,1.02l1.28,0.04l1.22,-0.58l3.03,1.27l1.49,-0.14l1.36,-1.0l1.33,0.07l0.82,-0.35l3.45,0.8l1.82,-1.32l1.84,2.67l0.66,0.16Z",name:"Nigeria"},NZ:{path:"M857.8,379.65l1.86,3.12l0.44,0.18l0.3,-0.38l0.03,-1.23l0.38,0.27l0.57,2.31l2.02,0.94l1.81,0.27l1.57,-1.06l0.7,0.18l-1.15,3.59l-1.98,0.11l-0.74,1.2l0.2,1.11l-2.42,3.98l-1.49,0.92l-1.04,-0.85l1.21,-2.05l-0.81,-2.01l-2.63,-1.25l0.04,-0.57l1.82,-1.19l0.43,-2.34l-0.16,-2.03l-0.95,-1.82l-0.06,-0.72l-3.11,-3.64l-0.79,-1.52l1.56,1.45l1.76,0.66l0.65,2.34ZM853.83,393.59l0.57,1.24l0.59,0.16l1.42,-0.97l0.46,0.79l0.0,1.03l-2.47,3.48l-1.26,1.2l-0.06,0.5l0.55,0.87l-1.41,0.07l-2.33,1.38l-2.03,5.02l-3.02,2.16l-2.06,-0.06l-1.71,-1.04l-2.47,-0.2l-0.27,-0.73l1.22,-2.1l3.05,-2.94l1.62,-0.59l4.02,-2.82l1.57,-1.67l1.07,-2.16l0.88,-0.7l0.48,-1.75l1.24,-0.97l0.35,0.79Z",name:"New Zealand"},NP:{path:"M641.14,213.62l0.01,3.19l-1.74,0.04l-4.8,-0.86l-1.58,-1.39l-3.37,-0.34l-7.65,-3.7l0.8,-2.09l2.33,-1.7l1.77,0.75l2.49,1.76l1.38,0.41l0.99,1.35l1.9,0.52l1.99,1.17l5.49,0.9Z",name:"Nepal"},CI:{path:"M407.4,259.27l0.86,0.42l0.56,0.9l1.13,0.53l1.19,-0.61l0.97,-0.08l1.42,0.54l0.6,3.24l-1.03,2.08l-0.65,2.84l1.06,2.33l-0.06,0.53l-2.54,-0.47l-1.66,0.03l-3.06,0.46l-4.11,1.6l0.32,-3.06l-1.18,-1.31l-1.32,-0.66l0.42,-0.85l-0.2,-1.4l0.5,-0.67l0.01,-1.59l0.84,-0.32l0.26,-0.5l-1.15,-3.01l0.12,-0.5l0.51,-0.25l0.66,0.31l1.93,0.02l0.67,-0.71l0.71,-0.14l0.25,0.69l0.57,0.22l1.4,-0.61Z",name:"Côte d'Ivoire"},CH:{path:"M444.62,156.35l-0.29,0.87l0.18,0.53l1.13,0.58l1.0,0.1l-0.1,0.65l-0.79,0.38l-1.72,-0.37l-0.45,0.23l-0.45,1.04l-0.75,0.06l-0.84,-0.4l-1.32,1.0l-0.96,0.12l-0.88,-0.55l-0.81,-1.3l-0.49,-0.16l-0.63,0.26l0.02,-0.65l1.71,-1.66l0.1,-0.56l0.93,0.08l0.58,-0.46l1.99,0.02l0.66,-0.61l2.19,0.79Z",name:"Switzerland"},CO:{path:"M242.07,254.93l-1.7,0.59l-0.59,1.18l-1.7,1.69l-0.38,1.93l-0.67,1.43l0.31,0.57l1.03,0.13l0.25,0.9l0.57,0.64l-0.04,2.34l1.64,1.42l3.16,-0.24l1.26,0.28l1.67,2.06l0.41,0.13l4.09,-0.39l0.45,0.22l-0.92,1.95l-0.2,1.8l0.52,1.83l0.75,1.05l-1.12,1.1l0.07,0.63l0.84,0.51l0.74,1.29l-0.39,-0.45l-0.59,-0.01l-0.71,0.74l-4.71,-0.05l-0.4,0.41l0.03,1.57l0.33,0.39l1.11,0.2l-1.68,0.4l-0.29,0.38l-0.01,1.82l1.16,1.14l0.34,1.25l-1.05,7.05l-1.04,-0.87l1.26,-1.99l-0.13,-0.56l-2.18,-1.23l-1.38,0.2l-1.14,-0.38l-1.27,0.61l-1.55,-0.26l-1.38,-2.46l-1.23,-0.75l-0.85,-1.2l-1.67,-1.19l-0.86,0.13l-2.11,-1.32l-1.01,0.31l-1.8,-0.29l-0.52,-0.91l-3.09,-1.68l0.77,-0.52l-0.1,-1.12l0.41,-0.64l1.34,-0.32l2.0,-2.88l-0.11,-0.57l-0.66,-0.43l0.39,-1.38l-0.52,-2.1l0.49,-0.83l-0.4,-2.13l-0.97,-1.35l0.17,-0.66l0.86,-0.08l0.47,-0.75l-0.46,-1.63l1.41,-0.07l1.8,-1.69l0.93,-0.24l0.3,-0.38l0.45,-2.76l1.22,-1.0l1.44,-0.04l0.45,-0.5l1.91,0.12l2.93,-1.84l1.15,-1.14l0.91,0.46l-0.25,0.45Z",name:"Colombia"},CN:{path:"M740.23,148.97l4.57,1.3l2.8,2.17l0.98,2.9l0.38,0.27l3.8,0.0l2.32,-1.28l3.29,-0.75l-0.96,2.09l-1.02,1.28l-0.85,3.4l-1.52,2.73l-2.76,-0.5l-2.4,1.13l-0.21,0.45l0.64,2.57l-0.32,3.2l-0.94,0.06l-0.37,0.89l-0.91,-1.01l-0.64,0.07l-0.92,1.57l-3.73,1.25l-0.26,0.48l0.26,1.06l-1.5,-0.08l-1.09,-0.86l-0.56,0.06l-1.67,2.06l-2.7,1.56l-2.03,1.88l-3.4,0.83l-1.93,1.4l-1.15,0.34l0.33,-0.7l-0.41,-0.89l1.79,-1.79l0.02,-0.54l-1.32,-1.56l-0.48,-0.1l-2.24,1.09l-2.83,2.06l-1.51,1.83l-2.28,0.13l-1.55,1.49l-0.04,0.5l1.32,1.97l2.0,0.58l0.31,1.35l1.98,0.84l3.0,-1.96l2.0,1.02l1.49,0.11l0.22,0.83l-3.37,0.86l-1.12,1.48l-2.5,1.52l-1.29,1.99l0.14,0.56l2.57,1.48l0.97,2.7l3.17,4.63l-0.03,1.66l-1.35,0.65l-0.2,0.51l0.6,1.47l1.4,0.91l-0.89,3.82l-1.43,0.38l-3.85,6.44l-2.27,3.11l-6.78,4.57l-2.73,0.29l-1.45,1.04l-0.62,-0.61l-0.55,-0.01l-1.36,1.25l-3.39,1.27l-2.61,0.4l-1.1,2.79l-0.81,0.09l-0.49,-1.42l0.5,-0.85l-0.25,-0.59l-3.36,-0.84l-1.3,0.4l-2.31,-0.62l-0.94,-0.84l0.33,-1.28l-0.3,-0.49l-2.19,-0.46l-1.13,-0.93l-0.47,-0.02l-2.06,1.36l-4.29,0.28l-2.76,1.05l-0.28,0.43l0.32,2.53l-0.59,-0.03l-0.19,-1.34l-0.55,-0.34l-1.68,0.7l-2.46,-1.23l0.62,-1.87l-0.26,-0.51l-1.37,-0.44l-0.54,-2.22l-0.45,-0.3l-2.13,0.35l0.24,-2.48l2.39,-2.4l0.03,-4.31l-1.19,-0.92l-0.78,-1.49l-0.41,-0.21l-1.41,0.19l-1.98,-0.3l0.46,-1.07l-1.17,-1.7l-0.55,-0.11l-1.63,1.05l-2.25,-0.57l-2.89,1.73l-2.25,1.98l-1.75,0.29l-1.17,-0.71l-3.31,-0.65l-1.48,0.79l-1.04,1.27l-0.12,-1.17l-0.54,-0.34l-1.44,0.54l-5.55,-0.86l-1.98,-1.16l-1.89,-0.54l-0.99,-1.35l-1.34,-0.37l-2.55,-1.79l-2.01,-0.84l-1.21,0.56l-5.57,-3.45l-0.53,-2.31l1.19,0.25l0.48,-0.37l0.08,-1.42l-0.98,-1.56l0.15,-2.44l-2.69,-3.32l-4.12,-1.23l-0.67,-2.0l-1.92,-1.48l-0.38,-0.7l-0.51,-3.01l-1.52,-0.66l-0.7,0.13l-0.48,-2.05l0.55,-0.51l-0.09,-0.82l2.03,-1.19l1.6,-0.54l2.56,0.38l0.42,-0.22l0.85,-1.7l3.0,-0.33l1.1,-1.26l4.05,-1.77l0.39,-0.91l-0.17,-1.44l1.45,-0.67l0.2,-0.52l-2.07,-4.9l4.51,-1.12l1.37,-0.73l1.89,-5.51l4.98,0.86l1.51,-1.7l0.11,-2.87l1.99,-0.38l1.83,-2.06l0.49,-0.13l0.68,2.08l2.23,1.77l3.44,1.16l1.55,2.29l-0.92,3.49l0.96,1.67l6.54,1.13l2.95,1.87l1.47,0.35l1.06,2.62l1.53,1.91l3.05,0.08l5.14,0.67l3.37,-0.41l2.36,0.43l3.65,1.8l3.06,0.04l1.45,0.88l2.87,-1.59l3.95,-1.02l3.83,-0.14l3.06,-1.14l1.77,-1.6l1.72,-1.01l0.17,-0.49l-1.1,-2.05l1.02,-1.54l4.02,0.8l2.45,-1.61l3.76,-1.19l1.96,-2.13l1.63,-0.83l3.51,-0.4l1.92,0.34l0.46,-0.3l0.17,-1.5l-2.27,-2.22l-2.11,-1.09l-2.18,1.11l-2.32,-0.47l-1.29,0.32l-0.4,-0.82l2.73,-5.16l3.02,1.06l3.53,-2.06l0.18,-1.68l2.16,-3.35l1.49,-1.35l-0.03,-1.85l-1.07,-0.85l1.54,-1.26l2.98,-0.59l3.23,-0.09l3.64,0.99l2.04,1.16l3.29,6.71l0.92,3.19ZM696.92,237.31l-1.87,1.08l-1.63,-0.64l-0.06,-1.79l1.03,-0.98l2.58,-0.69l1.16,0.05l0.3,0.54l-0.98,1.06l-0.53,1.37Z",name:"China"},CM:{path:"M457.92,257.49l1.05,1.91l-1.4,0.16l-1.05,-0.23l-0.45,0.22l-0.54,1.19l0.08,0.45l1.48,1.47l1.05,0.45l1.01,2.46l-1.52,2.99l-0.68,0.68l-0.13,3.69l2.38,3.84l1.09,0.8l0.24,2.48l-3.67,-1.14l-11.27,-0.13l0.23,-1.79l-0.98,-1.66l-1.19,-0.54l-0.44,-0.97l-0.6,-0.42l1.71,-4.27l0.75,-0.13l1.38,-1.36l0.65,-0.03l1.71,0.99l1.93,-1.12l1.14,-3.18l1.38,-1.17l2.0,-5.14l2.17,-2.13l0.3,-1.64l-0.86,-0.88l0.03,-0.33l0.94,1.28l0.07,3.22Z",name:"Cameroon"},CL:{path:"M246.5,429.18l-3.14,1.83l-0.57,3.16l-0.64,0.05l-2.68,-1.06l-2.82,-2.33l-3.04,-1.89l-0.69,-1.85l0.63,-2.14l-1.21,-2.11l-0.31,-5.37l1.01,-2.91l2.57,-2.38l-0.18,-0.68l-3.16,-0.77l2.05,-2.47l0.77,-4.65l2.32,0.9l0.54,-0.29l1.31,-6.31l-0.22,-0.44l-1.68,-0.8l-0.56,0.28l-0.7,3.36l-0.81,-0.22l1.56,-9.41l1.15,-2.24l-0.71,-2.82l-0.18,-2.84l1.01,-0.33l3.26,-9.14l1.07,-4.22l-0.56,-4.21l0.74,-2.34l-0.29,-3.27l1.46,-3.34l2.04,-16.59l-0.66,-7.76l1.03,-0.53l0.54,-0.9l0.79,1.14l0.32,1.78l1.25,1.16l-0.69,2.55l1.33,2.9l0.97,3.59l0.46,0.29l1.5,-0.3l0.11,0.23l-0.76,2.44l-2.57,1.23l-0.23,0.37l0.08,4.33l-0.46,0.77l0.56,1.21l-1.58,1.51l-1.68,2.62l-0.89,2.47l0.2,2.7l-1.48,2.73l1.12,5.09l0.64,0.61l-0.01,2.29l-1.38,2.68l0.01,2.4l-1.89,2.04l0.02,2.75l0.69,2.57l-1.43,1.13l-1.26,5.68l0.39,3.51l-0.97,0.89l0.58,3.5l1.02,1.14l-0.65,1.02l0.15,0.57l1.0,0.53l0.16,0.69l-1.03,0.85l0.26,1.75l-0.89,4.03l-1.31,2.66l0.24,1.75l-0.71,1.83l-1.99,1.7l0.3,3.67l0.88,1.19l1.58,0.01l0.01,2.21l1.04,1.95l5.98,0.63ZM248.69,430.79l0.0,7.33l0.4,0.4l3.52,0.05l-0.44,0.75l-1.94,0.98l-2.49,-0.37l-1.88,-1.06l-2.55,-0.49l-5.59,-3.71l-2.38,-2.63l4.1,2.48l3.32,1.23l0.45,-0.12l1.29,-1.57l0.83,-2.32l2.05,-1.24l1.31,0.29Z",name:"Chile"},CA:{path:"M280.06,145.6l-1.67,2.88l0.07,0.49l0.5,0.04l1.46,-0.98l1.0,0.42l-0.56,0.72l0.17,0.62l2.22,0.89l1.35,-0.71l1.95,0.78l-0.66,2.01l0.5,0.51l1.32,-0.42l0.98,3.17l-0.91,2.41l-0.8,0.08l-1.23,-0.45l0.47,-2.25l-0.89,-0.83l-0.48,0.06l-2.78,2.63l-0.34,-0.02l1.02,-0.85l-0.14,-0.69l-2.4,-0.77l-7.4,0.08l-0.17,-0.41l1.3,-0.94l0.02,-0.64l-0.73,-0.58l1.85,-1.74l2.57,-5.16l1.47,-1.79l1.99,-1.05l0.46,0.06l-1.53,2.45ZM68.32,74.16l4.13,0.95l4.02,2.14l2.61,0.4l2.47,-1.89l2.88,-1.31l3.85,0.48l3.71,-1.94l3.82,-1.04l1.56,1.68l0.49,0.08l1.87,-1.04l0.65,-1.98l1.24,0.35l4.16,3.94l0.54,0.01l2.75,-2.49l0.26,2.59l0.49,0.35l3.08,-0.73l1.04,-1.27l2.73,0.23l3.83,1.86l5.86,1.61l3.47,0.75l2.44,-0.26l2.73,1.78l-2.98,1.81l-0.19,0.41l0.31,0.32l4.53,0.92l6.87,-0.5l2.0,-0.69l2.49,2.39l0.53,0.02l2.72,-2.16l-0.02,-0.64l-2.16,-1.54l1.15,-1.06l4.83,-0.61l1.84,0.95l2.48,2.31l3.01,-0.23l4.55,1.92l3.85,-0.67l3.61,0.1l0.41,-0.44l-0.25,-2.36l1.79,-0.61l3.49,1.32l-0.01,3.77l0.31,0.39l0.45,-0.22l1.48,-3.16l1.74,0.1l0.41,-0.3l1.13,-4.37l-2.78,-3.11l-2.8,-1.74l0.19,-4.64l2.71,-3.07l2.98,0.67l2.41,1.95l3.19,4.8l-1.99,1.97l0.21,0.68l4.33,0.84l-0.01,4.15l0.25,0.37l0.44,-0.09l3.07,-3.15l2.54,2.39l-0.61,3.33l2.42,2.88l0.61,0.0l2.61,-3.08l1.88,-3.82l0.17,-4.58l6.72,0.94l3.13,2.04l0.13,1.82l-1.76,2.19l-0.01,0.49l1.66,2.16l-0.26,1.71l-4.68,2.8l-3.28,0.61l-2.47,-1.2l-0.55,0.23l-0.73,2.04l-2.38,3.43l-0.74,1.77l-2.74,2.57l-3.44,0.25l-2.21,1.78l-0.28,2.53l-2.82,0.55l-3.12,3.22l-2.72,4.31l-1.03,3.17l-0.14,4.31l0.33,0.41l3.44,0.57l2.24,5.95l0.45,0.23l3.4,-0.69l4.52,1.51l2.43,1.31l1.91,1.73l3.1,0.96l2.62,1.46l6.6,0.54l-0.35,2.74l0.81,3.53l1.81,3.78l3.83,3.3l0.45,0.04l2.1,-1.28l1.37,-3.69l-1.31,-5.38l-1.45,-1.58l3.57,-1.47l2.84,-2.46l1.52,-2.8l-0.25,-2.55l-1.7,-3.07l-2.85,-2.61l2.8,-3.95l-1.08,-3.37l-0.79,-5.67l1.36,-0.7l6.76,1.41l2.12,-0.96l5.12,3.36l1.05,1.61l4.08,0.26l-0.06,2.87l0.83,4.7l0.3,0.32l2.16,0.54l1.73,2.06l0.5,0.09l3.63,-2.03l2.52,-4.19l1.26,-1.32l7.6,11.72l-0.92,2.04l0.16,0.51l3.3,1.97l2.22,1.98l4.1,0.98l1.43,0.99l0.95,2.79l2.1,0.68l0.84,1.08l0.17,3.45l-3.37,2.26l-4.22,1.24l-3.06,2.63l-4.06,0.51l-5.35,-0.69l-6.39,0.2l-2.3,2.41l-3.26,1.51l-6.47,7.15l-0.06,0.48l0.44,0.19l2.13,-0.52l4.17,-4.24l5.12,-2.62l3.52,-0.3l1.69,1.21l-2.12,2.21l0.81,3.47l1.02,2.61l3.47,1.6l4.14,-0.45l2.15,-2.8l0.26,1.48l1.14,0.8l-2.56,1.69l-5.5,1.82l-2.54,1.27l-2.74,2.15l-1.4,-0.16l-0.07,-2.01l4.14,-2.44l0.18,-0.45l-0.39,-0.29l-6.63,0.45l-1.39,-1.49l-0.14,-4.43l-1.11,-0.91l-1.82,0.39l-0.66,-0.66l-0.6,0.03l-1.91,2.39l-0.82,2.52l-0.8,1.27l-1.67,0.56l-0.46,0.76l-8.31,0.07l-1.21,0.62l-2.35,1.97l-0.71,-0.14l-1.37,0.96l-1.12,-0.48l-4.74,1.26l-0.9,1.17l0.21,0.62l1.73,0.3l-1.81,0.31l-1.85,0.81l-2.11,-0.13l-2.95,1.78l-0.69,-0.09l1.39,-2.1l1.73,-1.21l0.1,-2.29l1.16,-1.99l0.49,0.53l2.03,0.42l1.2,-1.16l0.02,-0.47l-2.66,-3.51l-2.28,-0.61l-5.64,-0.71l-0.4,-0.57l-0.79,0.13l0.2,-0.41l-0.22,-0.55l-0.68,-0.26l0.19,-1.26l-0.78,-0.73l0.31,-0.64l-0.29,-0.57l-2.6,-0.44l-0.75,-1.63l-0.94,-0.66l-4.31,-0.65l-1.13,1.19l-1.48,0.59l-0.85,1.06l-2.83,-0.76l-2.09,0.39l-2.39,-0.97l-4.24,-0.7l-0.57,-0.4l-0.41,-1.63l-0.4,-0.3l-0.85,0.02l-0.39,0.4l-0.01,0.85l-69.13,-0.01l-6.51,-4.52l-4.5,-1.38l-1.26,-2.66l0.33,-1.93l-0.23,-0.43l-3.01,-1.35l-0.55,-2.77l-2.89,-2.38l-0.04,-1.45l1.39,-1.83l-0.28,-2.55l-4.16,-2.2l-4.07,-6.6l-4.02,-3.22l-1.3,-1.88l-0.5,-0.13l-2.51,1.21l-2.23,1.87l-3.85,-3.88l-2.44,-1.04l-2.22,-0.13l0.03,-37.49ZM260.37,148.65l3.04,0.76l2.26,1.2l-3.78,-0.95l-1.53,-1.01ZM249.4,3.81l6.68,0.49l5.32,0.79l4.26,1.57l-0.07,1.1l-5.85,2.53l-6.02,1.21l-2.39,1.39l-0.18,0.45l0.39,0.29l4.01,-0.02l-4.65,2.82l-4.2,1.74l-4.19,4.59l-5.03,0.92l-1.67,1.15l-7.47,0.59l-0.37,0.37l0.32,0.42l2.41,0.49l-0.81,0.47l-0.12,0.59l1.83,2.41l-2.02,1.59l-3.81,1.51l-1.32,2.16l-3.38,1.53l-0.22,0.48l0.35,1.19l0.4,0.29l3.88,-0.18l0.03,0.61l-6.33,2.95l-6.41,-1.4l-7.43,0.79l-3.72,-0.62l-4.4,-0.25l-0.23,-1.83l4.29,-1.11l0.28,-0.51l-1.1,-3.45l1.0,-0.25l6.58,2.28l0.47,-0.16l-0.05,-0.49l-3.41,-3.45l-3.58,-0.98l1.48,-1.55l4.34,-1.29l0.97,-2.19l-0.16,-0.48l-3.42,-2.13l-0.81,-2.26l6.2,0.22l2.24,0.58l3.91,-2.1l0.2,-0.43l-0.35,-0.32l-5.64,-0.67l-8.73,0.36l-4.26,-1.9l-2.12,-2.4l-2.78,-1.66l-0.41,-1.52l3.31,-1.03l2.93,-0.2l4.91,-0.99l3.7,-2.27l2.87,0.3l2.62,1.67l0.56,-0.14l1.82,-3.2l3.13,-0.94l4.44,-0.69l7.53,-0.26l1.48,0.67l7.19,-1.06l10.8,0.79ZM203.85,57.54l0.01,0.42l1.97,2.97l0.68,-0.02l2.24,-3.72l5.95,-1.86l4.01,4.64l-0.35,2.91l0.5,0.43l4.95,-1.36l2.32,-1.8l5.31,2.28l3.27,2.11l0.3,1.84l0.48,0.33l4.42,-0.99l2.64,2.87l5.97,1.77l2.06,1.72l2.11,3.71l-4.19,1.86l-0.01,0.73l5.9,2.83l3.94,0.94l3.78,3.95l3.46,0.25l-0.63,2.37l-4.11,4.47l-2.76,-1.56l-3.9,-3.94l-3.59,0.41l-0.33,0.34l-0.19,2.72l2.63,2.38l3.42,1.89l0.94,0.97l1.55,3.75l-0.7,2.29l-2.74,-0.92l-6.25,-3.15l-0.51,0.13l0.05,0.52l6.07,5.69l0.18,0.59l-6.09,-1.39l-5.31,-2.24l-2.63,-1.66l0.6,-0.77l-0.12,-0.6l-7.39,-4.01l-0.59,0.37l0.03,0.79l-6.73,0.6l-1.69,-1.1l1.36,-2.46l4.51,-0.07l5.15,-0.52l0.31,-0.6l-0.74,-1.3l0.78,-1.84l3.21,-4.05l-0.67,-2.35l-1.11,-1.6l-3.84,-2.1l-4.35,-1.28l0.91,-0.63l0.06,-0.61l-2.65,-2.75l-2.34,-0.36l-1.89,-1.46l-0.53,0.03l-1.24,1.23l-4.36,0.55l-9.04,-0.99l-9.26,-1.98l-1.6,-1.22l2.22,-1.77l0.13,-0.44l-0.38,-0.27l-3.22,-0.02l-0.72,-4.25l1.83,-4.04l2.42,-1.85l5.5,-1.1l-1.39,2.35ZM261.19,159.33l2.07,0.61l1.44,-0.04l-1.15,0.63l-2.94,-1.23l-0.4,-0.68l0.36,-0.37l0.61,1.07ZM230.83,84.39l-2.37,0.18l-0.49,-1.63l0.93,-2.09l1.94,-0.51l1.62,0.99l0.02,1.52l-1.66,1.54ZM229.43,58.25l0.11,0.65l-4.87,-0.21l-2.72,0.62l-3.1,-2.57l0.08,-1.26l0.86,-0.23l5.57,0.51l4.08,2.5ZM222.0,105.02l-0.72,1.49l-0.63,-0.19l-0.48,-0.84l0.81,-0.99l0.65,0.05l0.37,0.46ZM183.74,38.32l2.9,1.7l4.79,-0.01l1.84,1.46l-0.49,1.68l0.23,0.48l2.82,1.14l1.76,1.26l7.01,0.65l4.1,-1.1l5.03,-0.43l3.93,0.35l2.48,1.77l0.46,1.7l-1.3,1.1l-3.56,1.01l-3.23,-0.59l-7.17,0.76l-5.09,0.09l-3.99,-0.6l-6.42,-1.54l-0.79,-2.51l-0.3,-2.49l-2.64,-2.5l-5.32,-0.72l-2.52,-1.4l0.68,-1.57l4.78,0.31ZM207.38,91.35l0.4,1.56l0.56,0.26l1.06,-0.52l1.32,0.96l5.42,2.57l0.2,1.68l0.46,0.35l1.68,-0.28l1.15,0.85l-1.55,0.87l-3.61,-0.88l-1.32,-1.69l-0.57,-0.06l-2.45,2.1l-3.12,1.79l-0.7,-1.87l-0.42,-0.26l-2.16,0.24l1.39,-1.39l0.32,-3.14l0.76,-3.35l1.18,0.22ZM215.49,102.6l-2.67,1.95l-1.4,-0.07l-0.3,-0.58l1.53,-1.48l2.84,0.18ZM202.7,24.12l2.53,1.59l-2.87,1.4l-4.53,4.05l-4.25,0.38l-5.03,-0.68l-2.45,-2.04l0.03,-1.62l1.82,-1.37l0.14,-0.45l-0.38,-0.27l-4.45,0.04l-2.59,-1.76l-1.41,-2.29l1.57,-2.32l1.62,-1.66l2.44,-0.39l0.25,-0.65l-0.6,-0.74l4.86,-0.25l3.24,3.11l8.16,2.3l1.9,3.61ZM187.47,59.2l-2.76,3.49l-2.38,-0.15l-1.44,-3.84l0.04,-2.2l1.19,-1.88l2.3,-1.23l5.07,0.17l4.11,1.02l-3.24,3.72l-2.88,0.89ZM186.07,48.79l-1.08,1.53l-3.34,-0.34l-2.56,-1.1l1.03,-1.75l3.25,-1.23l1.95,1.58l0.75,1.3ZM185.71,35.32l-5.3,-0.2l-0.32,-0.71l4.31,0.07l1.3,0.84ZM180.68,32.48l-3.34,1.0l-1.79,-1.1l-0.98,-1.87l-0.15,-1.73l4.1,0.53l2.67,1.7l-0.51,1.47ZM180.9,76.31l-1.1,1.08l-3.13,-1.23l-2.12,0.43l-2.71,-1.57l1.72,-1.09l1.55,-1.72l3.81,1.9l1.98,2.2ZM169.74,54.87l2.96,0.97l4.17,-0.57l0.41,0.88l-2.14,2.11l0.09,0.64l3.55,1.92l-0.4,3.72l-3.79,1.65l-2.17,-0.35l-1.72,-1.74l-6.02,-3.5l0.03,-0.85l4.68,0.54l0.4,-0.21l-0.05,-0.45l-2.48,-2.81l2.46,-1.95ZM174.45,40.74l1.37,1.73l0.07,2.44l-1.05,3.45l-3.79,0.47l-2.32,-0.69l0.05,-2.64l-0.44,-0.41l-3.68,0.35l-0.12,-3.1l2.45,0.1l3.67,-1.73l3.41,0.29l0.37,-0.26ZM170.05,31.55l0.67,1.56l-3.33,-0.49l-4.22,-1.77l-4.35,-0.16l1.4,-0.94l-0.06,-0.7l-2.81,-1.23l-0.12,-1.39l4.39,0.68l6.62,1.98l1.81,2.47ZM134.5,58.13l-1.02,1.82l0.45,0.58l5.4,-1.39l3.33,2.29l0.49,-0.03l2.6,-2.23l1.94,1.32l2.0,4.5l0.7,0.06l1.3,-2.29l-1.63,-4.46l1.69,-0.54l2.31,0.71l2.65,1.81l2.49,7.92l8.48,4.27l-0.19,1.35l-3.79,0.33l-0.26,0.67l1.4,1.49l-0.58,1.1l-4.23,-0.64l-4.43,-1.19l-3.0,0.28l-4.66,1.47l-10.52,1.04l-1.43,-2.02l-3.42,-1.2l-2.21,0.43l-2.51,-2.86l4.84,-1.05l3.6,0.19l3.27,-0.78l0.31,-0.39l-0.31,-0.39l-4.84,-1.06l-8.79,0.27l-0.85,-1.07l5.26,-1.66l0.27,-0.45l-0.4,-0.34l-3.8,0.06l-3.81,-1.06l1.81,-3.01l1.66,-1.79l6.48,-2.81l1.97,0.71ZM158.7,56.61l-1.7,2.44l-3.2,-2.75l0.37,-0.3l3.11,-0.18l1.42,0.79ZM149.61,42.73l1.01,1.89l0.5,0.18l2.14,-0.82l2.23,0.19l0.36,2.04l-1.33,2.09l-8.28,0.76l-6.35,2.15l-3.41,0.1l-0.19,-0.96l4.9,-2.08l0.23,-0.46l-0.41,-0.31l-11.25,0.59l-2.89,-0.74l3.04,-4.44l2.14,-1.32l6.81,1.69l4.58,3.06l4.37,0.39l0.36,-0.63l-3.36,-4.6l1.85,-1.53l2.18,0.51l0.77,2.26ZM144.76,34.41l-4.36,1.44l-3.0,-1.4l1.46,-1.24l3.47,-0.52l2.96,0.71l-0.52,1.01ZM145.13,29.83l-1.9,0.66l-3.67,-0.0l2.27,-1.61l3.3,0.95ZM118.92,65.79l-6.03,2.02l-1.33,-1.9l-5.38,-2.28l2.59,-5.05l2.16,-3.14l-0.02,-0.48l-1.97,-2.41l7.64,-0.7l3.6,1.02l6.3,0.27l4.42,2.95l-2.53,0.98l-6.24,3.43l-3.1,3.28l-0.11,2.01ZM129.54,35.53l-0.28,3.37l-1.72,1.62l-2.33,0.28l-4.61,2.19l-3.86,0.76l-2.64,-0.87l3.72,-3.4l5.01,-3.34l3.72,0.07l3.0,-0.67ZM111.09,152.69l-0.67,0.24l-3.85,-1.37l-0.83,-1.17l-2.12,-1.07l-0.66,-1.02l-2.4,-0.55l-0.74,-1.71l6.02,1.45l2.0,2.55l2.52,1.39l0.73,1.27ZM87.8,134.64l0.89,0.29l1.86,-0.21l-0.65,3.34l1.69,2.33l-1.31,-1.33l-0.99,-1.62l-1.17,-0.98l-0.33,-1.82Z",name:"Canada"},CG:{path:"M466.72,276.48l-0.1,1.03l-1.25,2.97l-0.19,3.62l-0.46,1.78l-0.23,0.63l-1.61,1.19l-1.21,1.39l-1.09,2.43l0.04,2.09l-3.25,3.24l-0.5,-0.24l-0.5,-0.83l-1.36,-0.02l-0.98,0.89l-1.68,-0.99l-1.54,1.24l-1.52,-1.96l1.57,-1.14l0.11,-0.52l-0.77,-1.35l2.1,-0.66l0.39,-0.73l1.05,0.82l2.21,0.11l1.12,-1.37l0.37,-1.81l-0.27,-2.09l-1.13,-1.5l1.0,-2.69l-0.13,-0.45l-0.92,-0.58l-1.6,0.17l-0.51,-0.94l0.1,-0.61l2.75,0.09l3.97,1.24l0.51,-0.33l0.17,-1.28l1.24,-2.21l1.28,-1.14l2.76,0.49Z",name:"Congo"},CF:{path:"M461.16,278.2l-0.26,-1.19l-1.09,-0.77l-0.84,-1.17l-0.29,-1.0l-1.04,-1.15l0.08,-3.43l0.58,-0.49l1.16,-2.35l1.85,-0.17l0.61,-0.62l0.97,0.58l3.15,-0.96l2.48,-1.92l0.02,-0.96l2.81,0.02l2.36,-1.17l1.93,-2.85l1.16,-0.93l1.11,-0.3l0.27,0.86l1.34,1.47l-0.39,2.01l0.3,1.01l4.01,2.75l0.17,0.93l2.63,2.31l0.6,1.44l2.08,1.4l-3.84,-0.21l-1.94,0.88l-1.23,-0.49l-2.67,1.2l-1.29,-0.18l-0.51,0.36l-0.6,1.22l-3.35,-0.65l-1.57,-0.91l-2.42,-0.83l-1.45,0.91l-0.97,1.27l-0.26,1.56l-3.22,-0.43l-1.49,1.33l-0.94,1.62Z",name:"Central African Rep."},CD:{path:"M487.01,272.38l2.34,-0.14l1.35,1.84l1.34,0.45l0.86,-0.39l1.21,0.12l1.07,-0.41l0.54,0.89l2.04,1.54l-0.14,2.72l0.7,0.54l-1.38,1.13l-1.53,2.54l-0.17,2.05l-0.59,1.08l-0.02,1.72l-0.72,0.84l-0.66,3.01l0.63,1.32l-0.44,4.26l0.64,1.47l-0.37,1.22l0.86,1.8l1.53,1.41l0.3,1.26l0.44,0.5l-4.08,0.75l-0.92,1.81l0.51,1.34l-0.74,5.43l0.17,0.38l2.45,1.46l0.54,-0.1l0.12,1.62l-1.28,-0.01l-1.85,-2.35l-1.94,-0.45l-0.48,-1.13l-0.55,-0.2l-1.41,0.74l-1.71,-0.3l-1.01,-1.18l-2.49,-0.19l-0.44,-0.77l-1.98,-0.21l-2.88,0.36l0.11,-2.41l-0.85,-1.13l-0.16,-1.36l0.32,-1.73l-0.46,-0.89l-0.04,-1.49l-0.4,-0.39l-2.53,0.02l0.1,-0.41l-0.39,-0.49l-1.28,0.01l-0.43,0.45l-1.62,0.32l-0.83,1.79l-1.09,-0.28l-2.4,0.52l-1.37,-1.91l-1.3,-3.3l-0.38,-0.27l-7.39,-0.03l-2.46,0.42l0.5,-0.45l0.37,-1.47l0.66,-0.38l0.92,0.08l0.73,-0.82l0.87,0.02l0.31,0.68l1.4,0.36l3.59,-3.63l0.01,-2.23l1.02,-2.29l2.69,-2.39l0.43,-0.99l0.49,-1.96l0.17,-3.51l1.25,-2.95l0.36,-3.14l0.86,-1.13l1.1,-0.66l3.57,1.73l3.65,0.73l0.46,-0.21l0.8,-1.46l1.24,0.19l2.61,-1.17l0.81,0.44l1.04,-0.03l0.59,-0.66l0.7,-0.16l1.81,0.25Z",name:"Dem. Rep. Congo"},CZ:{path:"M458.46,144.88l1.22,1.01l1.47,0.23l0.13,0.93l1.36,0.68l0.54,-0.2l0.24,-0.55l1.15,0.25l0.53,1.09l1.68,0.18l0.6,0.84l-1.04,0.73l-0.96,1.28l-1.6,0.17l-0.55,0.56l-1.04,-0.46l-1.05,0.15l-2.12,-0.96l-1.05,0.34l-1.2,1.12l-1.56,-0.87l-2.57,-2.1l-0.53,-1.88l4.7,-2.52l0.71,0.26l0.9,-0.28Z",name:"Czech Rep."},CY:{path:"M504.36,193.47l0.43,0.28l-1.28,0.57l-0.92,-0.28l-0.24,-0.46l2.01,-0.13Z",name:"Cyprus"},CR:{path:"M211.34,258.05l0.48,0.99l1.6,1.6l-0.54,0.45l0.29,1.42l-0.25,1.19l-1.09,-0.59l-0.05,-1.25l-2.46,-1.42l-0.28,-0.77l-0.66,-0.45l-0.45,-0.0l-0.11,1.04l-1.32,-0.95l0.31,-1.3l-0.36,-0.6l0.31,-0.27l1.42,0.58l1.29,-0.14l0.56,0.56l0.74,0.17l0.55,-0.27Z",name:"Costa Rica"},CU:{path:"M221.21,227.25l1.27,1.02l2.19,-0.28l4.43,3.33l2.08,0.43l-0.1,0.38l0.36,0.5l1.75,0.1l1.48,0.84l-3.11,0.51l-4.15,-0.03l0.77,-0.67l-0.04,-0.64l-1.2,-0.74l-1.49,-0.16l-0.7,-0.61l-0.56,-1.4l-0.4,-0.25l-1.34,0.1l-2.2,-0.66l-0.88,-0.58l-3.18,-0.4l-0.27,-0.16l0.58,-0.74l-0.36,-0.29l-2.72,-0.05l-1.7,1.29l-0.91,0.03l-0.61,0.69l-1.01,0.22l1.11,-1.29l1.01,-0.52l3.69,-1.01l3.98,0.21l2.21,0.84Z",name:"Cuba"},SZ:{path:"M500.35,351.36l0.5,2.04l-0.38,0.89l-1.05,0.21l-1.23,-1.2l-0.02,-0.64l0.83,-1.57l1.34,0.27Z",name:"Swaziland"},SY:{path:"M511.0,199.79l0.05,-1.33l0.54,-1.36l1.28,-0.99l0.13,-0.45l-0.41,-1.11l-1.14,-0.36l-0.19,-1.74l0.52,-1.0l1.29,-1.21l0.2,-1.18l0.59,0.23l2.62,-0.76l1.36,0.52l2.06,-0.01l2.95,-1.08l3.25,-0.26l-0.67,0.94l-1.28,0.66l-0.21,0.4l0.23,2.01l-0.88,3.19l-10.15,5.73l-2.15,-0.85Z",name:"Syria"},KG:{path:"M621.35,172.32l-3.87,1.69l-0.96,1.18l-3.04,0.34l-1.13,1.86l-2.36,-0.35l-1.99,0.63l-2.39,1.4l0.06,0.95l-0.4,0.37l-4.52,0.43l-3.02,-0.93l-2.37,0.17l0.11,-0.79l2.32,0.42l1.13,-0.88l1.99,0.2l3.21,-2.14l-0.03,-0.69l-2.97,-1.57l-1.94,0.65l-1.22,-0.74l1.71,-1.58l-0.12,-0.67l-0.36,-0.15l0.32,-0.77l1.36,-0.35l4.02,1.02l0.49,-0.3l0.35,-1.59l1.09,-0.48l3.42,1.22l1.11,-0.31l7.64,0.39l1.16,1.0l1.23,0.39Z",name:"Kyrgyzstan"},KE:{path:"M506.26,284.69l1.87,-2.56l0.93,-2.15l-1.38,-4.08l-1.06,-1.6l2.82,-2.75l0.79,0.26l0.12,1.41l0.86,0.83l1.9,0.11l3.28,2.13l3.57,0.44l1.05,-1.12l1.96,-0.9l0.82,0.68l1.16,0.09l-1.78,2.45l0.03,9.12l1.3,1.94l-1.37,0.78l-0.67,1.03l-1.08,0.46l-0.34,1.67l-0.81,1.07l-0.45,1.55l-0.68,0.56l-3.2,-2.23l-0.35,-1.58l-8.86,-4.98l0.14,-1.6l-0.57,-1.04Z",name:"Kenya"},SS:{path:"M481.71,263.34l1.07,-0.72l1.2,-3.18l1.36,-0.26l1.61,1.99l0.87,0.34l1.1,-0.41l1.5,0.07l0.57,0.53l2.49,0.0l0.44,-0.63l1.07,-0.4l0.45,-0.84l0.59,-0.33l1.9,1.33l1.6,-0.2l2.83,-3.33l-0.32,-2.21l1.59,-0.52l-0.24,1.6l0.3,1.83l1.35,1.18l0.2,1.87l0.35,0.41l0.02,1.53l-0.23,0.47l-1.42,0.25l-0.85,1.44l0.3,0.6l1.4,0.16l1.11,1.08l0.59,1.13l1.03,0.53l1.28,2.36l-4.41,3.98l-1.74,0.01l-1.89,0.55l-1.47,-0.52l-1.15,0.57l-2.96,-2.62l-1.3,0.49l-1.06,-0.15l-0.79,0.39l-0.82,-0.22l-1.8,-2.7l-1.91,-1.1l-0.66,-1.5l-2.62,-2.32l-0.18,-0.94l-2.37,-1.6Z",name:"S. Sudan"},SR:{path:"M283.12,270.19l2.1,0.53l-1.08,1.95l0.2,1.72l0.93,1.49l-0.59,2.03l-0.43,0.71l-1.12,-0.42l-1.32,0.22l-0.93,-0.2l-0.46,0.26l-0.25,0.73l0.33,0.7l-0.89,-0.13l-1.39,-1.97l-0.31,-1.34l-0.97,-0.31l-0.89,-1.47l0.35,-1.61l1.45,-0.82l0.33,-1.87l2.61,0.44l0.57,-0.47l1.75,-0.16Z",name:"Suriname"},KH:{path:"M689.52,249.39l0.49,1.45l-0.28,2.74l-4.0,1.86l-0.16,0.6l0.68,0.95l-2.06,0.17l-2.05,0.97l-1.82,-0.32l-2.12,-3.7l-0.55,-2.85l1.4,-1.85l3.02,-0.45l2.23,0.35l2.01,0.98l0.51,-0.14l0.95,-1.48l1.74,0.74Z",name:"Cambodia"},SV:{path:"M195.8,250.13l1.4,-1.19l2.24,1.45l0.98,-0.27l0.44,0.2l-0.27,1.05l-1.14,-0.03l-3.64,-1.21Z",name:"El Salvador"},SK:{path:"M476.82,151.17l-1.14,1.9l-2.73,-0.92l-0.82,0.2l-0.74,0.8l-3.46,0.73l-0.47,0.69l-1.76,0.33l-1.88,-1.0l-0.18,-0.81l0.38,-0.75l1.87,-0.32l1.74,-1.89l0.83,0.16l0.79,-0.34l1.51,1.04l1.34,-0.63l1.25,0.3l1.65,-0.42l1.81,0.95Z",name:"Slovakia"},KR:{path:"M737.51,185.84l0.98,-0.1l0.87,-1.17l2.69,-0.32l0.33,-0.29l1.76,2.79l0.58,1.76l0.02,3.12l-0.8,1.32l-2.21,0.55l-1.93,1.13l-1.8,0.19l-0.2,-1.1l0.43,-2.28l-0.95,-2.56l1.43,-0.37l0.23,-0.62l-1.43,-2.06Z",name:"Korea"},SI:{path:"M456.18,162.07l-0.51,-1.32l0.18,-1.05l1.69,0.2l1.42,-0.71l2.09,-0.07l0.62,-0.51l0.21,0.47l-1.61,0.67l-0.44,1.34l-0.66,0.24l-0.26,0.82l-1.22,-0.49l-0.84,0.46l-0.69,-0.04Z",name:"Slovenia"},KP:{path:"M736.77,185.16l-0.92,-0.42l-0.88,0.62l-1.21,-0.88l0.96,-1.15l0.59,-2.59l-0.46,-0.74l-2.09,-0.77l1.64,-1.52l2.72,-1.58l1.58,-1.91l1.11,0.78l2.17,0.11l0.41,-0.5l-0.3,-1.22l3.52,-1.18l0.94,-1.4l0.98,1.08l-2.19,2.18l0.01,2.14l-1.06,0.54l-1.41,1.4l-1.7,0.52l-1.25,1.09l-0.14,1.98l0.94,0.45l1.15,1.04l-0.13,0.26l-2.6,0.29l-1.13,1.29l-1.22,0.08Z",name:"Dem. Rep. Korea"},SO:{path:"M525.13,288.48l-1.13,-1.57l-0.03,-8.86l2.66,-3.38l1.67,-0.13l2.13,-1.69l3.41,-0.23l7.08,-7.55l2.91,-3.69l0.08,-4.82l2.98,-0.67l1.24,-0.86l0.45,-0.0l-0.2,3.0l-1.21,3.62l-2.73,5.97l-2.13,3.65l-5.03,6.16l-8.56,6.4l-2.78,3.08l-0.8,1.56Z",name:"Somalia"},SN:{path:"M390.09,248.21l0.12,1.55l0.49,1.46l0.96,0.82l0.05,1.28l-1.26,-0.19l-0.75,0.33l-1.84,-0.61l-5.84,-0.13l-2.54,0.51l-0.22,-1.03l1.77,0.04l2.01,-0.91l1.03,0.48l1.09,0.04l1.29,-0.62l0.14,-0.58l-0.51,-0.74l-1.81,0.25l-1.13,-0.63l-0.79,0.04l-0.72,0.61l-2.31,0.06l-0.92,-1.77l-0.81,-0.64l0.64,-0.35l2.46,-3.74l1.04,0.19l1.38,-0.56l1.19,-0.02l2.72,1.37l3.03,3.48Z",name:"Senegal"},SL:{path:"M394.46,264.11l-1.73,1.98l-0.58,1.33l-2.07,-1.06l-1.22,-1.26l-0.65,-2.39l1.16,-0.96l0.67,-1.17l1.21,-0.52l1.66,0.0l1.03,1.64l0.52,2.41Z",name:"Sierra Leone"},SB:{path:"M826.69,311.6l-0.61,0.09l-0.2,-0.33l0.37,0.15l0.44,0.09ZM824.18,307.38l-0.26,-0.3l-0.31,-0.91l0.03,0.0l0.54,1.21ZM823.04,309.33l-1.66,-0.22l-0.2,-0.52l1.16,0.28l0.69,0.46ZM819.28,304.68l1.14,0.65l0.02,0.03l-0.81,-0.44l-0.35,-0.23Z",name:"Solomon Is."},SA:{path:"M537.53,210.34l2.0,0.24l0.9,1.32l1.49,-0.06l0.87,2.08l1.29,0.76l0.51,0.99l1.56,1.03l-0.1,1.9l0.32,0.9l1.58,2.47l0.76,0.53l0.7,-0.04l1.68,4.23l7.53,1.33l0.51,-0.29l0.77,1.25l-1.55,4.87l-7.29,2.52l-7.3,1.03l-2.34,1.17l-1.88,2.74l-0.76,0.28l-0.82,-0.78l-0.91,0.12l-2.88,-0.51l-3.51,0.25l-0.86,-0.56l-0.57,0.15l-0.66,1.27l0.16,1.11l-0.43,0.32l-0.93,-1.4l-0.33,-1.16l-1.23,-0.88l-1.27,-2.06l-0.78,-2.22l-1.73,-1.79l-1.14,-0.48l-1.54,-2.31l-0.21,-3.41l-1.44,-2.93l-1.27,-1.16l-1.33,-0.57l-1.31,-3.37l-0.77,-0.67l-0.97,-1.97l-2.8,-4.03l-1.06,-0.17l0.37,-1.96l0.2,-0.72l2.74,0.3l1.08,-0.84l0.6,-0.94l1.74,-0.35l0.65,-1.03l0.71,-0.4l0.1,-0.62l-2.06,-2.28l4.39,-1.22l0.48,-0.37l2.77,0.69l3.66,1.9l7.03,5.5l4.87,0.3Z",name:"Saudi Arabia"},SE:{path:"M480.22,89.3l-4.03,1.17l-2.43,2.86l0.26,2.57l-8.77,6.64l-1.78,5.79l1.78,2.68l2.22,1.96l-2.07,3.77l-2.72,1.13l-0.95,6.04l-1.29,3.01l-2.74,-0.31l-0.4,0.22l-1.31,2.59l-2.34,0.13l-0.75,-3.09l-2.08,-4.03l-1.83,-4.96l1.0,-1.93l2.14,-2.7l0.83,-4.45l-1.6,-2.17l-0.15,-4.94l1.48,-3.39l2.58,-0.15l0.87,-1.59l-0.78,-1.57l3.76,-5.59l4.04,-7.48l2.17,0.01l0.39,-0.29l0.57,-2.07l4.37,0.64l0.46,-0.34l0.33,-2.56l1.1,-0.13l6.94,4.87l0.06,6.32l0.66,1.36Z",name:"Sweden"},SD:{path:"M505.98,259.4l-0.34,-0.77l-1.17,-0.9l-0.26,-1.61l0.29,-1.81l-0.34,-0.46l-1.16,-0.17l-0.54,0.59l-1.23,0.11l-0.28,0.65l0.53,0.65l0.17,1.22l-2.44,3.0l-0.96,0.19l-2.39,-1.4l-0.95,0.52l-0.38,0.78l-1.11,0.41l-0.29,0.5l-1.94,0.0l-0.54,-0.52l-1.81,-0.09l-0.95,0.4l-2.45,-2.35l-2.07,0.54l-0.73,1.26l-0.6,2.1l-1.25,0.58l-0.75,-0.62l0.27,-2.65l-1.48,-1.78l-0.22,-1.48l-0.92,-0.96l-0.02,-1.29l-0.57,-1.16l-0.68,-0.16l0.69,-1.29l-0.18,-1.14l0.65,-0.62l0.03,-0.55l-0.36,-0.41l1.55,-2.97l1.91,0.16l0.43,-0.4l-0.1,-10.94l2.49,-0.01l0.4,-0.4l-0.0,-4.82l29.02,0.0l0.64,2.04l-0.49,0.66l0.36,2.69l0.93,3.16l2.12,1.55l-0.89,1.04l-1.72,0.39l-0.98,0.9l-1.43,5.65l0.24,1.15l-0.38,2.06l-0.96,2.38l-1.53,1.31l-1.32,2.91l-1.22,0.86l-0.37,1.34Z",name:"Sudan"},DO:{path:"M241.8,239.2l0.05,-0.65l-0.46,-0.73l0.42,-0.44l0.19,-1.0l-0.09,-1.53l1.66,0.01l1.99,0.63l0.33,0.67l1.28,0.19l0.33,0.76l1.0,0.08l0.8,0.62l-0.45,0.51l-1.13,-0.47l-1.88,-0.01l-1.27,0.59l-0.75,-0.55l-1.01,0.54l-0.79,1.4l-0.23,-0.61Z",name:"Dominican Rep."},DJ:{path:"M528.43,256.18l-0.45,0.66l-0.58,-0.25l-1.51,0.13l-0.18,-1.01l1.45,-1.95l0.83,0.17l0.77,-0.44l0.2,1.0l-1.2,0.51l-0.06,0.7l0.73,0.47Z",name:"Djibouti"},DK:{path:"M452.28,129.07l-1.19,2.24l-2.13,-1.6l-0.23,-0.95l2.98,-0.95l0.57,1.26ZM447.74,126.31l-0.26,0.57l-0.88,-0.07l-1.8,2.53l0.48,1.69l-1.09,0.36l-1.61,-0.39l-0.89,-1.69l-0.07,-3.43l0.96,-1.73l2.02,-0.2l1.09,-1.07l1.33,-0.67l-0.05,1.06l-0.73,1.41l0.3,1.0l1.2,0.64Z",name:"Denmark"},DE:{path:"M453.14,155.55l-0.55,-0.36l-1.2,-0.1l-1.87,0.57l-2.13,-0.13l-0.56,0.63l-0.86,-0.6l-0.96,0.09l-2.57,-0.93l-0.85,0.67l-1.47,-0.02l0.24,-1.75l1.23,-2.14l-0.28,-0.59l-3.52,-0.58l-0.92,-0.66l0.12,-1.2l-0.48,-0.88l0.27,-2.17l-0.37,-3.03l1.41,-0.22l0.63,-1.26l0.66,-3.19l-0.41,-1.18l0.26,-0.39l1.66,-0.15l0.33,0.54l0.62,0.07l1.7,-1.69l-0.54,-3.02l1.37,0.33l1.31,-0.37l0.31,1.18l2.25,0.71l-0.02,0.92l0.5,0.4l2.55,-0.65l1.34,-0.87l2.57,1.24l1.06,0.98l0.48,1.44l-0.57,0.74l-0.0,0.48l0.87,1.15l0.57,1.64l-0.14,1.29l0.82,1.7l-1.5,-0.07l-0.56,0.57l-4.47,2.15l-0.22,0.54l0.68,2.26l2.58,2.16l-0.66,1.11l-0.79,0.36l-0.23,0.43l0.32,1.87Z",name:"Germany"},YE:{path:"M528.27,246.72l0.26,-0.42l-0.22,-1.01l0.19,-1.5l0.92,-0.69l-0.07,-1.35l0.39,-0.75l1.01,0.47l3.34,-0.27l3.76,0.41l0.95,0.81l1.36,-0.58l1.74,-2.62l2.18,-1.09l6.86,-0.94l2.48,5.41l-1.64,0.76l-0.56,1.9l-6.23,2.16l-2.29,1.8l-1.93,0.05l-1.41,1.02l-4.24,0.74l-1.72,1.49l-3.28,0.19l-0.52,-1.18l0.02,-1.51l-1.34,-3.29Z",name:"Yemen"},AT:{path:"M462.89,152.8l0.04,2.25l-1.07,0.0l-0.33,0.63l0.36,0.51l-1.04,2.13l-2.02,0.07l-1.33,0.7l-5.29,-0.99l-0.47,-0.93l-0.44,-0.21l-2.47,0.55l-0.42,0.51l-3.18,-0.81l0.43,-0.91l1.12,0.78l0.6,-0.17l0.25,-0.58l1.93,0.12l1.86,-0.56l1.0,0.08l0.68,0.57l0.62,-0.15l0.26,-0.77l-0.3,-1.78l0.8,-0.44l0.68,-1.15l1.52,0.85l0.47,-0.06l1.34,-1.25l0.64,-0.17l1.81,0.92l1.28,-0.11l0.7,0.37Z",name:"Austria"},DZ:{path:"M441.46,188.44l-0.32,1.07l0.39,2.64l-0.54,2.16l-1.58,1.82l0.37,2.39l1.91,1.55l0.18,0.8l1.42,1.03l1.84,7.23l0.12,1.16l-0.57,5.0l0.2,1.51l-0.87,0.99l-0.02,0.51l1.41,1.86l0.14,1.2l0.89,1.48l0.5,0.16l0.98,-0.41l1.73,1.08l0.82,1.23l-8.22,4.81l-7.23,5.11l-3.43,1.13l-2.3,0.21l-0.28,-1.59l-2.56,-1.09l-0.67,-1.25l-26.12,-17.86l0.01,-3.47l3.77,-1.88l2.44,-0.41l2.12,-0.75l1.08,-1.42l2.81,-1.05l0.35,-2.08l1.33,-0.29l1.04,-0.94l3.47,-0.69l0.46,-1.08l-0.1,-0.45l-0.58,-0.52l-0.82,-2.81l-0.19,-1.83l-0.78,-1.49l2.03,-1.31l2.63,-0.48l1.7,-1.22l2.31,-0.84l8.24,-0.73l1.49,0.38l2.28,-1.1l2.46,-0.02l0.92,0.6l1.35,-0.05Z",name:"Algeria"},US:{path:"M892.72,99.2l1.31,0.53l1.41,-0.37l1.89,0.98l1.89,0.42l-1.32,0.58l-2.9,-1.53l-2.08,0.22l-0.26,-0.15l0.07,-0.67ZM183.22,150.47l0.37,1.47l1.12,0.85l4.23,0.7l2.39,0.98l2.17,-0.38l1.85,0.5l-1.55,0.65l-3.49,2.61l-0.16,0.77l0.5,0.39l2.33,-0.61l1.77,1.02l5.15,-2.4l-0.31,0.65l0.25,0.56l1.36,0.38l1.71,1.16l4.7,-0.88l0.67,0.85l1.31,0.21l0.58,0.58l-1.34,0.17l-2.18,-0.32l-3.6,0.89l-2.71,3.25l0.35,0.9l0.59,-0.0l0.55,-0.6l-1.36,4.65l0.29,3.09l0.67,1.58l0.61,0.45l1.77,-0.44l1.6,-1.96l0.14,-2.21l-0.82,-1.96l0.11,-1.13l1.19,-2.37l0.44,-0.33l0.48,0.75l0.4,-0.29l0.4,-1.37l0.6,-0.47l0.24,-0.8l1.69,0.49l1.65,1.08l-0.03,2.37l-1.27,1.13l-0.0,1.13l0.87,0.36l1.66,-1.29l0.5,0.17l0.5,2.6l-2.49,3.75l0.17,0.61l1.54,0.62l1.48,0.17l1.92,-0.44l4.72,-2.15l2.16,-1.8l-0.05,-1.24l0.75,-0.22l3.92,0.36l2.12,-1.05l0.21,-0.4l-0.28,-1.48l3.27,-2.4l8.32,-0.02l0.56,-0.82l1.9,-0.77l0.93,-1.51l0.74,-2.37l1.58,-1.98l0.92,0.62l1.47,-0.47l0.8,0.66l-0.0,4.09l1.96,2.6l-2.34,1.31l-5.37,2.09l-1.83,2.72l0.02,1.79l0.83,1.59l0.54,0.23l-6.19,0.94l-2.2,0.89l-0.23,0.48l0.45,0.29l2.99,-0.46l-2.19,0.56l-1.13,0.0l-0.15,-0.32l-0.48,0.08l-0.76,0.82l0.22,0.67l0.32,0.06l-0.41,1.62l-1.27,1.58l-1.48,-1.07l-0.49,-0.04l-0.16,0.46l0.52,1.58l0.61,0.59l0.03,0.79l-0.95,1.38l-1.21,-1.22l-0.27,-2.27l-0.35,-0.35l-0.42,0.25l-0.48,1.27l0.33,1.41l-0.97,-0.27l-0.48,0.24l0.18,0.5l1.52,0.83l0.1,2.52l0.79,0.51l0.52,3.42l-1.42,1.88l-2.47,0.8l-1.71,1.66l-1.31,0.25l-1.27,1.03l-0.43,0.99l-2.69,1.78l-2.64,3.03l-0.45,2.12l0.45,2.08l0.85,2.38l1.09,1.9l0.04,1.2l1.16,3.06l-0.18,2.69l-0.55,1.43l-0.47,0.21l-0.89,-0.23l-0.49,-1.18l-0.87,-0.56l-2.75,-5.16l0.48,-1.68l-0.72,-1.78l-2.01,-2.38l-1.12,-0.53l-2.72,1.18l-1.47,-1.35l-1.57,-0.68l-2.99,0.31l-2.17,-0.3l-2.0,0.19l-1.15,0.46l-0.19,0.58l0.39,0.63l0.14,1.34l-0.84,-0.2l-0.84,0.46l-1.58,-0.07l-2.08,-1.44l-2.09,0.33l-1.91,-0.62l-3.73,0.84l-2.39,2.07l-2.54,1.22l-1.45,1.41l-0.61,1.38l0.34,3.71l-0.29,0.02l-3.5,-1.33l-1.25,-3.11l-1.44,-1.5l-2.24,-3.56l-1.76,-1.09l-2.27,-0.01l-1.71,2.07l-1.76,-0.69l-1.16,-0.74l-1.52,-2.98l-3.93,-3.16l-4.34,-0.0l-0.4,0.4l-0.0,0.74l-6.5,0.02l-9.02,-3.14l-0.34,-0.71l-5.7,0.49l-0.43,-1.29l-1.62,-1.61l-1.14,-0.38l-0.55,-0.88l-1.28,-0.13l-1.01,-0.77l-2.22,-0.27l-0.43,-0.3l-0.36,-1.58l-2.4,-2.83l-2.01,-3.85l-0.06,-0.9l-2.92,-3.26l-0.33,-2.29l-1.3,-1.66l0.52,-2.37l-0.09,-2.57l-0.78,-2.3l0.95,-2.82l0.61,-5.68l-0.47,-4.27l-1.46,-4.08l3.19,0.79l1.26,2.83l0.69,0.08l0.69,-1.14l-1.1,-4.79l68.76,-0.0l0.4,-0.4l0.14,-0.86ZM32.44,67.52l1.73,1.97l0.55,0.05l0.99,-0.79l3.65,0.24l-0.09,0.62l0.32,0.45l3.83,0.77l2.61,-0.43l5.19,1.4l4.84,0.43l1.89,0.57l3.42,-0.7l6.14,1.87l-0.03,38.06l0.38,0.4l2.39,0.11l2.31,0.98l3.9,3.99l0.55,0.04l2.4,-2.03l2.16,-1.04l1.2,1.71l3.95,3.14l4.09,6.63l4.2,2.29l0.06,1.83l-1.02,1.23l-1.16,-1.08l-2.04,-1.03l-0.67,-2.89l-3.28,-3.03l-1.65,-3.57l-6.35,-0.32l-2.82,-1.01l-5.26,-3.85l-6.77,-2.04l-3.53,0.3l-4.81,-1.69l-3.25,-1.63l-2.78,0.8l-0.28,0.46l0.44,2.21l-3.91,0.96l-2.26,1.27l-2.3,0.65l-0.27,-1.65l1.05,-3.42l2.49,-1.09l0.16,-0.6l-0.69,-0.96l-0.55,-0.1l-3.19,2.12l-1.78,2.56l-3.55,2.61l-0.04,0.61l1.56,1.52l-2.07,2.29l-5.11,2.57l-0.77,1.66l-3.76,1.77l-0.92,1.73l-2.69,1.38l-1.81,-0.22l-6.95,3.32l-3.97,0.91l4.85,-2.5l2.59,-1.86l3.26,-0.52l1.19,-1.4l3.42,-2.1l2.59,-2.27l0.42,-2.68l1.23,-2.1l-0.04,-0.46l-0.45,-0.11l-2.68,1.03l-0.63,-0.49l-0.53,0.03l-1.05,1.04l-1.36,-1.54l-0.66,0.08l-0.32,0.62l-0.58,-1.14l-0.56,-0.16l-2.41,1.42l-1.07,-0.0l-0.17,-1.75l0.3,-1.71l-1.61,-1.33l-3.41,0.59l-1.96,-1.63l-1.57,-0.84l-0.15,-2.21l-1.7,-1.43l0.82,-1.88l1.99,-2.12l0.88,-1.92l1.71,-0.24l2.04,0.51l1.87,-1.77l1.91,0.25l1.91,-1.23l0.17,-0.43l-0.47,-1.82l-1.07,-0.7l1.39,-1.17l0.12,-0.45l-0.39,-0.26l-1.65,0.07l-2.66,0.88l-0.75,0.78l-1.92,-0.8l-3.46,0.44l-3.44,-0.91l-1.06,-1.61l-2.65,-1.99l2.91,-1.43l5.5,-2.0l1.52,0.0l-0.26,1.62l0.41,0.46l5.29,-0.16l0.3,-0.65l-2.03,-2.59l-3.14,-1.68l-1.79,-2.12l-2.4,-1.83l-3.09,-1.24l1.04,-1.69l4.23,-0.14l3.36,-2.07l0.73,-2.27l2.39,-1.99l2.42,-0.52l4.65,-1.97l2.46,0.23l3.71,-2.35l3.5,0.89ZM37.6,123.41l-2.25,1.23l-0.95,-0.69l-0.29,-1.24l3.21,-1.63l1.42,0.21l0.67,0.7l-1.8,1.42ZM31.06,234.03l0.98,0.47l0.74,0.87l-1.77,1.07l-0.44,-1.53l0.49,-0.89ZM29.34,232.07l0.18,0.05l0.08,0.05l-0.16,0.03l-0.11,-0.14ZM25.16,230.17l0.05,-0.03l0.18,0.22l-0.13,-0.01l-0.1,-0.18ZM5.89,113.26l-1.08,0.41l-2.21,-1.12l1.53,-0.4l1.62,0.28l0.14,0.83Z",name:"United States"},LV:{path:"M489.16,122.85l0.96,0.66l0.22,1.65l0.68,1.76l-3.65,1.7l-2.23,-1.58l-1.29,-0.26l-0.68,-0.77l-2.42,0.34l-4.16,-0.23l-2.47,0.9l0.06,-1.98l1.13,-2.06l1.95,-1.02l2.12,2.58l2.01,-0.07l0.38,-0.33l0.44,-2.52l1.76,-0.53l3.06,1.7l2.15,0.07Z",name:"Latvia"},UY:{path:"M286.85,372.74l-0.92,1.5l-2.59,1.44l-1.69,-0.52l-1.42,0.26l-2.39,-1.19l-1.52,0.08l-1.27,-1.3l0.16,-1.5l0.56,-0.79l-0.02,-2.73l1.21,-4.74l1.19,-0.21l2.37,2.0l1.08,0.03l4.36,3.17l1.22,1.6l-0.96,1.5l0.61,1.4Z",name:"Uruguay"},LB:{path:"M510.37,198.01l-0.88,0.51l1.82,-3.54l0.62,0.08l0.22,0.61l-1.13,0.88l-0.65,1.47Z",name:"Lebanon"},LA:{path:"M689.54,248.53l-1.76,-0.74l-0.49,0.15l-0.94,1.46l-1.32,-0.64l0.62,-0.98l0.11,-2.17l-2.04,-2.42l-0.25,-2.65l-1.9,-2.1l-2.15,-0.31l-0.78,0.91l-1.12,0.06l-1.05,-0.4l-2.06,1.2l-0.04,-1.59l0.61,-2.68l-0.36,-0.49l-1.35,-0.1l-0.11,-1.23l-0.96,-0.88l1.96,-1.89l0.39,0.36l1.33,0.07l0.42,-0.45l-0.34,-2.66l0.7,-0.21l1.28,1.81l1.11,2.35l0.36,0.23l2.82,0.02l0.71,1.67l-1.39,0.65l-0.72,0.93l0.13,0.6l2.91,1.51l3.6,5.25l1.88,1.78l0.56,1.62l-0.35,1.96Z",name:"Lao PDR"},TW:{path:"M724.01,226.68l-0.74,1.48l-0.9,-1.52l-0.25,-1.74l1.38,-2.44l1.73,-1.74l0.64,0.44l-1.85,5.52Z",name:"Taiwan"},TT:{path:"M266.64,259.32l0.28,-1.16l1.13,-0.22l-0.06,1.2l-1.35,0.18Z",name:"Trinidad and Tobago"},TR:{path:"M513.21,175.47l3.64,1.17l3.05,-0.44l2.1,0.26l3.11,-1.56l2.46,-0.13l2.19,1.33l0.33,0.82l-0.22,1.33l0.25,0.44l2.28,1.13l-1.17,0.57l-0.21,0.45l0.75,3.2l-0.41,1.16l1.13,1.92l-0.55,0.22l-0.9,-0.67l-2.91,-0.37l-1.24,0.46l-4.23,0.41l-2.81,1.05l-1.91,0.01l-1.52,-0.53l-2.58,0.75l-0.66,-0.45l-0.62,0.3l-0.12,1.45l-0.89,0.84l-0.47,-0.67l0.79,-1.3l-0.41,-0.2l-1.43,0.23l-2.0,-0.63l-2.02,1.65l-3.51,0.3l-2.13,-1.53l-2.7,-0.1l-0.86,1.24l-1.38,0.27l-2.29,-1.44l-2.71,-0.01l-1.37,-2.65l-1.68,-1.52l1.07,-1.99l-0.09,-0.49l-1.27,-1.12l2.37,-2.41l3.7,-0.11l1.28,-2.24l4.49,0.37l3.21,-1.97l2.81,-0.82l3.99,-0.06l4.29,2.07ZM488.79,176.72l-1.72,1.31l-0.5,-0.88l1.37,-2.57l-0.7,-0.85l1.7,-0.63l1.8,0.34l0.46,1.17l1.76,0.78l-2.87,0.32l-1.3,1.01Z",name:"Turkey"},LK:{path:"M624.16,268.99l-1.82,0.48l-0.99,-1.67l-0.42,-3.46l0.95,-3.43l1.21,0.98l2.26,4.19l-0.34,2.33l-0.85,0.58Z",name:"Sri Lanka"},TN:{path:"M448.1,188.24l-1.0,1.27l-0.02,1.32l0.84,0.88l-0.28,2.09l-1.53,1.32l-0.12,0.42l0.48,1.54l1.42,0.32l0.53,1.11l0.9,0.52l-0.11,1.67l-3.54,2.64l-0.1,2.38l-0.58,0.3l-0.96,-4.45l-1.54,-1.25l-0.16,-0.78l-1.92,-1.56l-0.18,-1.76l1.51,-1.62l0.59,-2.34l-0.38,-2.78l0.42,-1.21l2.45,-1.05l1.29,0.26l-0.06,1.11l0.58,0.38l1.47,-0.73Z",name:"Tunisia"},TL:{path:"M734.55,307.93l-0.1,-0.97l4.5,-0.86l-2.82,1.28l-1.59,0.55Z",name:"Timor-Leste"},TM:{path:"M553.03,173.76l-0.04,0.34l-0.09,-0.22l0.13,-0.12ZM555.87,172.66l0.45,-0.1l1.48,0.74l2.06,2.43l4.07,-0.18l0.38,-0.51l-0.32,-1.19l1.92,-0.94l1.91,-1.59l2.94,1.39l0.43,2.47l1.19,0.67l2.58,-0.13l0.62,0.4l1.32,3.12l4.54,3.44l2.67,1.45l3.06,1.14l-0.04,1.05l-1.33,-0.75l-0.59,0.19l-0.32,0.84l-2.2,0.81l-0.46,2.13l-1.21,0.74l-1.91,0.42l-0.73,1.33l-1.56,0.31l-2.22,-0.94l-0.2,-2.17l-0.38,-0.36l-1.73,-0.09l-2.76,-2.46l-2.14,-0.4l-2.84,-1.48l-1.78,-0.27l-1.24,0.53l-1.57,-0.08l-2.0,1.69l-1.7,0.43l-0.36,-1.58l0.36,-2.98l-0.22,-0.4l-1.65,-0.84l0.54,-1.69l-0.34,-0.52l-1.22,-0.13l0.36,-1.64l2.22,0.59l2.2,-0.95l0.12,-0.65l-1.77,-1.74l-0.66,-1.57Z",name:"Turkmenistan"},TJ:{path:"M597.75,178.82l-2.54,-0.44l-0.47,0.34l-0.24,1.7l0.43,0.45l2.64,-0.22l3.18,0.95l4.39,-0.41l0.56,2.37l0.52,0.29l0.67,-0.24l1.11,0.49l0.21,2.13l-3.76,-0.21l-1.8,1.32l-1.76,0.74l-0.61,-0.58l0.21,-2.23l-0.64,-0.49l-0.07,-0.93l-1.36,-0.66l-0.45,0.07l-1.08,1.01l-0.55,1.48l-1.31,-0.05l-0.95,1.16l-0.9,-0.35l-1.86,0.74l1.26,-2.83l-0.54,-2.17l-1.67,-0.82l0.33,-0.66l2.18,-0.04l1.19,-1.63l0.76,-1.79l2.43,-0.5l-0.26,1.0l0.73,1.05Z",name:"Tajikistan"},LS:{path:"M491.06,363.48l-0.49,0.15l-1.49,-1.67l1.1,-1.43l2.19,-1.44l1.51,1.27l-0.98,1.82l-1.23,0.38l-0.62,0.93Z",name:"Lesotho"},TH:{path:"M670.27,255.86l-1.41,3.87l0.15,2.0l0.38,0.36l1.38,0.07l0.9,2.04l0.55,2.34l1.4,1.44l1.61,0.38l0.96,0.97l-0.5,0.64l-1.1,0.2l-0.34,-1.18l-2.04,-1.1l-0.63,0.23l-0.63,-0.62l-0.48,-1.3l-2.56,-2.63l-0.73,0.41l0.95,-3.89l2.16,-4.22ZM670.67,254.77l-0.92,-2.18l-0.26,-2.61l-2.14,-3.06l0.71,-0.49l0.89,-2.59l-3.61,-5.45l0.87,-0.51l1.05,-2.58l1.74,-0.18l2.6,-1.59l0.76,0.56l0.13,1.39l0.37,0.36l1.23,0.09l-0.51,2.28l0.05,2.42l0.6,0.34l2.43,-1.42l0.77,0.39l1.47,-0.07l0.71,-0.88l1.48,0.14l1.71,1.88l0.25,2.65l1.92,2.11l-0.1,1.89l-0.61,0.86l-2.22,-0.33l-3.5,0.64l-1.6,2.12l0.36,2.58l-1.51,-0.79l-1.84,-0.01l0.28,-1.52l-0.4,-0.47l-2.21,0.01l-0.4,0.37l-0.19,2.74l-0.34,0.93Z",name:"Thailand"},TF:{path:"M596.68,420.38l-3.2,0.18l-0.05,-1.26l0.39,-1.41l1.3,0.78l2.08,0.35l-0.52,1.36Z",name:"Fr. S. Antarctic Lands"},TG:{path:"M422.7,257.63l-0.09,1.23l1.53,1.52l0.08,1.09l0.5,0.65l-0.11,5.62l0.49,1.47l-1.31,0.35l-1.02,-2.13l-0.18,-1.12l0.53,-2.19l-0.63,-1.16l-0.22,-3.68l-1.01,-1.4l0.07,-0.28l1.37,0.03Z",name:"Togo"},TD:{path:"M480.25,235.49l0.12,9.57l-2.1,0.05l-1.14,1.89l-0.69,1.63l0.34,0.73l-0.66,0.91l0.24,0.89l-0.86,1.95l0.45,0.5l0.6,-0.1l0.34,0.64l0.03,1.38l0.9,1.04l-1.45,0.43l-1.27,1.03l-1.83,2.76l-2.16,1.07l-2.31,-0.15l-0.86,0.25l-0.26,0.49l0.17,0.61l-2.11,1.68l-2.85,0.87l-1.09,-0.57l-0.73,0.66l-1.12,0.1l-1.1,-3.12l-1.25,-0.64l-1.22,-1.22l0.29,-0.64l3.01,0.04l0.35,-0.6l-1.3,-2.2l-0.08,-3.31l-0.97,-1.66l0.22,-1.04l-0.38,-0.48l-1.22,-0.04l0.0,-1.25l-0.98,-1.07l0.96,-3.01l3.25,-2.65l0.13,-3.33l0.95,-5.18l0.52,-1.07l-0.1,-0.48l-0.91,-0.78l-0.2,-0.96l-0.8,-0.58l-0.55,-3.65l2.1,-1.2l19.57,9.83Z",name:"Chad"},LY:{path:"M483.48,203.15l-0.75,1.1l0.29,1.39l-0.6,1.83l0.73,2.14l0.0,24.12l-2.48,0.01l-0.41,0.85l-19.41,-9.76l-4.41,2.28l-1.37,-1.33l-3.82,-1.1l-1.14,-1.65l-1.98,-1.23l-1.22,0.32l-0.66,-1.11l-0.17,-1.26l-1.28,-1.69l0.87,-1.19l-0.07,-4.34l0.43,-2.27l-0.86,-3.45l1.13,-0.76l0.22,-1.16l-0.2,-1.03l3.48,-2.61l0.29,-1.94l2.45,0.8l1.18,-0.21l1.98,0.44l3.15,1.18l1.37,2.54l5.72,1.67l2.64,1.35l1.61,-0.72l1.29,-1.34l-0.44,-2.34l0.66,-1.13l1.67,-1.21l1.57,-0.35l3.14,0.53l1.08,1.28l3.99,0.78l0.36,0.54Z",name:"Libya"},AE:{path:"M550.76,223.97l1.88,-0.4l3.84,0.02l4.78,-4.75l0.19,0.36l0.26,1.58l-0.81,0.01l-0.39,0.35l-0.08,2.04l-0.81,0.63l-0.01,0.96l-0.66,0.99l-0.39,1.41l-7.08,-1.25l-0.7,-1.96Z",name:"United Arab Emirates"},VE:{path:"M240.68,256.69l0.53,0.75l-0.02,1.06l-1.07,1.78l0.95,2.0l0.42,0.22l1.4,-0.44l0.56,-1.83l-0.77,-1.17l-0.1,-1.47l2.82,-0.93l0.26,-0.49l-0.28,-0.96l0.3,-0.28l0.66,1.31l1.96,0.26l1.4,1.22l0.08,0.68l0.39,0.35l4.81,-0.22l1.49,1.11l1.92,0.31l1.67,-0.84l0.22,-0.6l3.44,-0.14l-0.17,0.55l0.86,1.19l2.19,0.35l1.67,1.1l0.37,1.86l0.41,0.32l1.55,0.17l-1.66,1.35l-0.22,0.92l0.65,0.97l-1.67,0.54l-0.3,0.4l0.04,0.99l-0.56,0.57l-0.01,0.55l1.85,2.27l-0.66,0.69l-4.47,1.29l-0.72,0.54l-3.69,-0.9l-0.71,0.27l-0.02,0.7l0.91,0.53l-0.08,1.54l0.35,1.58l0.35,0.31l1.66,0.17l-1.3,0.52l-0.48,1.13l-2.68,0.91l-0.6,0.77l-1.57,0.13l-1.17,-1.13l-0.8,-2.52l-1.25,-1.26l1.02,-1.23l-1.29,-2.95l0.18,-1.62l1.0,-2.21l-0.2,-0.49l-1.14,-0.46l-4.02,0.36l-1.82,-2.1l-1.57,-0.33l-2.99,0.22l-1.06,-0.97l0.25,-1.23l-0.2,-1.01l-0.59,-0.69l-0.29,-1.06l-1.08,-0.39l0.78,-2.79l1.9,-2.11Z",name:"Venezuela"},AF:{path:"M600.7,188.88l-1.57,1.3l-0.1,0.48l0.8,2.31l-1.09,1.04l-0.03,1.27l-0.48,0.71l-2.16,-0.08l-0.37,0.59l0.78,1.48l-1.38,0.69l-1.06,1.69l0.06,1.7l-0.65,0.52l-0.91,-0.21l-1.91,0.36l-0.48,0.77l-1.88,0.13l-1.4,1.56l-0.18,2.32l-2.91,1.02l-1.65,-0.23l-0.71,0.55l-1.41,-0.3l-2.41,0.39l-3.52,-1.17l1.96,-2.35l-0.21,-1.78l-0.3,-0.34l-1.63,-0.4l-0.19,-1.58l-0.75,-2.03l0.95,-1.36l-0.19,-0.6l-0.73,-0.28l1.47,-4.8l2.14,0.9l2.12,-0.36l0.74,-1.34l1.77,-0.39l1.54,-0.92l0.63,-2.31l1.87,-0.5l0.49,-0.81l0.94,0.56l2.13,0.11l2.55,0.92l1.95,-0.83l0.65,0.43l0.56,-0.13l0.69,-1.12l1.57,-0.08l0.72,-1.66l0.79,-0.74l0.8,0.39l-0.17,0.56l0.71,0.58l-0.08,2.39l1.11,0.95ZM601.37,188.71l1.73,-0.71l1.43,-1.18l4.03,0.35l-2.23,0.74l-4.95,0.8Z",name:"Afghanistan"},IQ:{path:"M530.82,187.47l0.79,0.66l1.26,-0.28l1.46,3.08l1.63,0.94l0.14,1.23l-1.22,1.05l-0.53,2.52l1.73,2.67l3.12,1.62l1.15,1.88l-0.38,1.85l0.39,0.48l0.41,-0.0l0.02,1.07l0.76,0.94l-2.47,-0.1l-1.71,2.44l-4.31,-0.2l-7.02,-5.48l-3.73,-1.94l-2.88,-0.73l-0.85,-2.87l5.45,-3.02l0.95,-3.43l-0.19,-1.96l1.27,-0.7l1.22,-1.7l0.87,-0.36l2.69,0.34Z",name:"Iraq"},IS:{path:"M384.14,88.06l-0.37,2.61l2.54,2.51l-2.9,2.75l-9.19,3.4l-9.25,-1.66l1.7,-1.22l-0.1,-0.7l-4.05,-1.47l2.96,-0.53l0.33,-0.43l-0.11,-1.2l-0.33,-0.36l-4.67,-0.85l1.28,-2.04l3.45,-0.56l3.77,2.72l0.44,0.02l3.64,-2.16l3.3,1.08l3.98,-2.16l3.58,0.26Z",name:"Iceland"},IR:{path:"M533.43,187.16l-1.27,-2.15l0.42,-0.98l-0.71,-3.04l1.03,-0.5l0.33,0.83l1.26,1.35l2.05,0.51l1.11,-0.16l2.89,-2.11l0.62,-0.14l0.39,0.46l-0.72,1.2l0.06,0.49l1.56,1.53l0.65,0.04l0.67,1.81l2.56,0.83l1.87,1.48l3.69,0.49l3.91,-0.76l0.47,-0.73l2.17,-0.6l1.66,-1.54l1.51,0.08l1.18,-0.53l1.59,0.24l2.83,1.48l1.88,0.3l2.77,2.47l1.77,0.18l0.18,1.99l-1.68,5.49l0.24,0.5l0.61,0.23l-0.82,1.48l0.8,2.18l0.19,1.71l0.3,0.34l1.63,0.4l0.15,1.32l-2.15,2.35l-0.01,0.53l2.21,3.03l2.34,1.24l0.06,2.14l1.24,0.72l0.11,0.69l-3.31,1.27l-1.08,3.03l-9.68,-1.68l-0.99,-3.05l-1.43,-0.73l-2.17,0.46l-2.47,1.26l-2.83,-0.82l-2.46,-2.02l-2.41,-0.8l-3.42,-6.06l-0.48,-0.2l-1.18,0.39l-1.44,-0.82l-0.5,0.08l-0.65,0.74l-0.97,-1.01l-0.02,-1.31l-0.71,-0.39l0.26,-1.81l-1.29,-2.11l-3.13,-1.63l-1.58,-2.43l0.5,-1.9l1.31,-1.26l-0.19,-1.66l-1.74,-1.1l-1.57,-3.3Z",name:"Iran"},AM:{path:"M536.99,182.33l-0.28,0.03l-1.23,-2.13l-0.93,0.01l-0.62,-0.66l-0.69,-0.07l-0.96,-0.81l-1.56,-0.62l0.19,-1.12l-0.26,-0.79l2.72,-0.36l1.09,1.01l-0.17,0.92l1.02,0.78l-0.47,0.62l0.08,0.56l2.04,1.23l0.04,1.4Z",name:"Armenia"},IT:{path:"M451.59,158.63l3.48,0.94l-0.21,1.17l0.3,0.83l-1.49,-0.24l-2.04,1.1l-0.21,0.39l0.13,1.45l-0.25,1.12l0.82,1.57l2.39,1.63l1.31,2.54l2.79,2.43l2.05,0.08l0.21,0.23l-0.39,0.33l0.09,0.67l4.05,1.97l2.17,1.76l-0.16,0.36l-1.17,-1.08l-2.18,-0.49l-0.44,0.2l-1.05,1.91l0.14,0.54l1.57,0.95l-0.19,0.98l-1.06,0.33l-1.25,2.34l-0.37,0.08l0.0,-0.33l1.0,-2.45l-1.73,-3.17l-1.12,-0.51l-0.88,-1.33l-1.51,-0.51l-1.27,-1.25l-1.75,-0.18l-4.12,-3.21l-1.62,-1.65l-1.03,-3.19l-3.53,-1.36l-1.3,0.51l-1.69,1.41l0.16,-0.72l-0.28,-0.47l-1.14,-0.33l-0.53,-1.96l0.72,-0.78l0.04,-0.48l-0.65,-1.17l0.8,0.39l1.4,-0.23l1.11,-0.84l0.52,0.35l1.19,-0.1l0.75,-1.2l1.53,0.33l1.36,-0.56l0.35,-1.14l1.08,0.32l0.68,-0.64l1.98,-0.44l0.42,0.82ZM459.19,184.75l-0.65,1.65l0.32,1.05l-0.31,0.89l-1.5,-0.85l-4.5,-1.67l0.19,-0.82l2.67,0.23l3.78,-0.48ZM443.93,176.05l1.18,1.66l-0.3,3.32l-1.06,-0.01l-0.77,0.73l-0.53,-0.44l-0.1,-3.37l-0.39,-1.22l1.04,0.01l0.92,-0.68Z",name:"Italy"},VN:{path:"M690.56,230.25l-2.7,1.82l-2.09,2.46l-0.63,1.95l4.31,6.45l2.32,1.65l1.43,1.94l1.11,4.59l-0.32,4.24l-1.93,1.54l-2.84,1.61l-2.11,2.15l-2.73,2.06l-0.59,-1.05l0.63,-1.53l-0.13,-0.47l-1.34,-1.04l1.51,-0.71l2.55,-0.18l0.3,-0.63l-0.82,-1.14l4.0,-2.07l0.31,-3.05l-0.57,-1.77l0.42,-2.66l-0.73,-1.97l-1.86,-1.76l-3.63,-5.29l-2.72,-1.46l0.36,-0.47l1.5,-0.64l0.21,-0.52l-0.97,-2.27l-0.37,-0.24l-2.83,-0.02l-2.24,-3.9l0.83,-0.4l4.39,-0.29l2.06,-1.31l1.15,0.89l1.88,0.4l-0.17,1.51l1.35,1.16l1.67,0.45Z",name:"Vietnam"},AR:{path:"M249.29,428.93l-2.33,-0.52l-5.83,-0.43l-0.89,-1.66l0.05,-2.37l-0.45,-0.4l-1.43,0.18l-0.67,-0.91l-0.2,-3.13l1.88,-1.47l0.79,-2.04l-0.25,-1.7l1.3,-2.68l0.91,-4.15l-0.22,-1.69l0.85,-0.45l0.2,-0.44l-0.27,-1.16l-0.98,-0.68l0.59,-0.92l-0.05,-0.5l-1.04,-1.07l-0.52,-3.1l0.97,-0.86l-0.42,-3.58l1.2,-5.43l1.38,-0.98l0.16,-0.43l-0.75,-2.79l-0.01,-2.43l1.78,-1.75l0.06,-2.57l1.43,-2.85l0.01,-2.58l-0.69,-0.74l-1.09,-4.52l1.47,-2.7l-0.18,-2.79l0.85,-2.35l1.59,-2.46l1.73,-1.64l0.05,-0.52l-0.6,-0.84l0.44,-0.85l-0.07,-4.19l2.7,-1.44l0.86,-2.75l-0.21,-0.71l1.76,-2.01l2.9,0.57l1.38,1.78l0.68,-0.08l0.87,-1.87l2.39,0.09l4.95,4.77l2.17,0.49l3.0,1.92l2.47,1.0l0.25,0.82l-2.37,3.93l0.23,0.59l5.39,1.16l2.12,-0.44l2.45,-2.16l0.5,-2.38l0.76,-0.31l0.98,1.2l-0.04,1.8l-3.67,2.51l-2.85,2.66l-3.43,3.88l-1.3,5.07l0.01,2.72l-0.54,0.73l-0.36,3.28l3.14,2.64l-0.16,2.11l1.4,1.11l-0.1,1.09l-2.29,3.52l-3.55,1.49l-4.92,0.6l-2.71,-0.29l-0.43,0.51l0.5,1.65l-0.49,2.1l0.38,1.42l-1.19,0.83l-2.36,0.38l-2.3,-1.04l-1.38,0.83l0.41,3.64l1.69,0.91l1.4,-0.71l0.36,0.76l-2.04,0.86l-2.01,1.89l-0.97,4.63l-2.34,0.1l-2.09,1.78l-0.61,2.75l2.46,2.31l2.17,0.63l-0.7,2.32l-2.83,1.73l-1.73,3.86l-2.17,1.22l-1.16,1.67l0.75,3.76l1.04,1.28ZM256.71,438.88l-2.0,0.15l-1.4,-1.22l-3.82,-0.1l-0.0,-5.83l1.6,3.05l3.26,2.07l3.08,0.78l-0.71,1.1Z",name:"Argentina"},AU:{path:"M705.8,353.26l0.26,0.04l0.17,-0.47l-0.48,-1.42l0.92,1.11l0.45,0.15l0.27,-0.39l-0.1,-1.56l-1.98,-3.63l1.09,-3.31l-0.24,-1.57l0.34,-0.62l0.38,1.06l0.43,-0.19l0.99,-1.7l1.91,-0.83l1.29,-1.15l1.81,-0.91l0.96,-0.17l0.92,0.26l1.92,-0.95l1.47,-0.28l1.03,-0.8l1.43,0.04l2.78,-0.84l1.36,-1.15l0.71,-1.45l1.41,-1.26l0.3,-2.58l1.27,-1.59l0.78,1.65l0.54,0.19l1.07,-0.51l0.15,-0.6l-0.73,-1.0l0.45,-0.71l0.78,0.39l0.58,-0.3l0.28,-1.82l1.87,-2.14l1.12,-0.39l0.28,-0.58l0.62,0.17l0.53,-0.73l1.87,-0.57l1.65,1.05l1.35,1.48l3.39,0.38l0.43,-0.54l-0.46,-1.23l1.05,-1.79l1.04,-0.61l0.14,-0.55l-0.25,-0.41l0.88,-1.17l1.31,-0.77l1.3,0.27l2.1,-0.48l0.31,-0.4l-0.05,-1.3l-0.92,-0.77l1.48,0.56l1.41,1.07l2.11,0.65l0.81,-0.2l1.4,0.7l1.69,-0.66l0.8,0.19l0.64,-0.33l0.71,0.77l-1.33,1.94l-0.71,0.07l-0.35,0.51l0.24,0.86l-1.52,2.35l0.12,1.05l2.15,1.65l1.97,0.85l3.04,2.36l1.97,0.65l0.55,0.88l2.72,0.85l1.84,-1.1l2.07,-5.97l-0.42,-3.59l0.3,-1.73l0.47,-0.87l-0.31,-0.68l1.09,-3.28l0.46,-0.47l0.4,0.71l0.16,1.51l0.65,0.52l0.16,1.04l0.85,1.21l0.12,2.38l0.9,2.0l0.57,0.18l1.3,-0.78l1.69,1.7l-0.2,1.08l0.53,2.2l0.39,1.3l0.68,0.48l0.6,1.95l-0.19,1.48l0.81,1.76l6.01,3.69l-0.11,0.76l1.38,1.58l0.95,2.77l0.58,0.22l0.72,-0.41l0.8,0.9l0.61,0.01l0.46,2.41l4.81,4.71l0.66,2.02l-0.07,3.31l1.14,2.2l-0.13,2.24l-1.1,3.68l0.03,1.64l-0.47,1.89l-1.05,2.4l-1.9,1.47l-1.72,3.51l-2.38,6.09l-0.24,2.82l-1.14,0.8l-2.85,0.15l-2.31,1.19l-2.51,2.25l-3.09,-1.57l0.3,-1.15l-0.54,-0.47l-1.5,0.63l-2.01,1.94l-7.12,-2.18l-1.48,-1.63l-1.14,-3.74l-1.45,-1.26l-1.81,-0.26l0.56,-1.18l-0.61,-2.1l-0.72,-0.1l-1.14,1.82l-0.9,0.21l0.63,-0.82l0.36,-1.55l0.92,-1.31l-0.13,-2.34l-0.7,-0.22l-2.0,2.34l-1.51,0.93l-0.94,2.01l-1.35,-0.81l-0.02,-1.52l-1.57,-2.04l-1.09,-0.88l0.24,-0.33l-0.14,-0.59l-3.21,-1.69l-1.83,-0.12l-2.54,-1.35l-4.58,0.28l-6.02,1.9l-2.53,-0.13l-2.62,1.41l-2.13,0.63l-1.49,2.6l-3.49,0.31l-2.29,-0.5l-3.48,0.43l-1.6,1.47l-0.81,-0.04l-2.37,1.63l-3.26,-0.1l-3.72,-2.21l0.04,-1.05l1.19,-0.46l0.49,-0.89l0.21,-2.97l-0.28,-1.64l-1.34,-2.86l-0.38,-1.47l0.05,-1.72l-0.95,-1.7l-0.18,-0.97l-1.01,-0.99l-0.29,-1.98l-1.13,-1.75ZM784.92,393.44l2.65,1.02l3.23,-0.96l1.09,0.14l0.15,3.06l-0.85,1.13l-0.17,1.63l-0.87,-0.24l-1.57,1.91l-1.68,-0.18l-1.4,-2.36l-0.37,-2.04l-1.39,-2.51l0.04,-0.8l1.15,0.18Z",name:"Australia"},IL:{path:"M507.76,203.05l0.4,-0.78l0.18,0.4l-0.33,1.03l0.52,0.44l0.68,-0.22l-0.86,3.6l-1.16,-3.32l0.59,-0.74l-0.03,-0.41ZM508.73,200.34l0.37,-1.02l0.64,0.0l0.52,-0.51l-0.49,1.53l-0.56,-0.24l-0.48,0.23Z",name:"Israel"},IN:{path:"M623.34,207.03l-1.24,1.04l-0.97,2.55l0.22,0.51l8.04,3.87l3.42,0.37l1.57,1.38l4.92,0.88l2.18,-0.04l0.38,-0.3l0.29,-1.24l-0.32,-1.64l0.14,-0.87l0.82,-0.31l0.45,2.48l2.28,1.02l1.77,-0.38l4.14,0.1l0.38,-0.36l0.18,-1.66l-0.5,-0.65l1.37,-0.29l2.25,-1.99l2.7,-1.62l1.93,0.62l1.8,-0.98l0.79,1.14l-0.68,0.91l0.26,0.63l2.42,0.36l0.09,0.47l-0.83,0.75l0.13,1.07l-1.52,-0.29l-3.24,1.86l-0.13,1.78l-1.32,2.14l-0.18,1.39l-0.93,1.82l-1.64,-0.5l-0.52,0.37l-0.09,2.63l-0.56,1.11l0.19,0.81l-0.53,0.27l-1.18,-3.73l-1.08,-0.27l-0.38,0.31l-0.24,1.0l-0.66,-0.66l0.54,-1.06l1.22,-0.34l1.15,-2.25l-0.24,-0.56l-1.57,-0.47l-4.34,-0.28l-0.18,-1.56l-0.35,-0.35l-1.11,-0.12l-1.91,-1.12l-0.56,0.17l-0.88,1.82l0.11,0.49l1.36,1.07l-1.09,0.69l-0.69,1.11l0.18,0.56l1.24,0.57l-0.32,1.54l0.85,1.94l0.36,2.01l-0.22,0.59l-4.58,0.52l-0.33,0.42l0.13,1.8l-1.17,1.36l-3.65,1.81l-2.79,3.03l-4.32,3.28l-0.18,1.27l-4.65,1.79l-0.77,2.16l0.64,5.3l-1.06,2.49l-0.01,3.94l-1.24,0.28l-1.14,1.93l0.39,0.84l-1.68,0.53l-1.04,1.83l-0.65,0.47l-2.06,-2.05l-2.1,-6.02l-2.2,-3.64l-1.05,-4.75l-2.29,-3.57l-1.76,-8.2l0.01,-3.11l-0.49,-2.53l-0.55,-0.29l-3.53,1.52l-1.53,-0.27l-2.86,-2.77l0.85,-0.67l0.08,-0.55l-0.74,-1.03l-2.67,-2.06l1.24,-1.32l5.34,0.01l0.39,-0.49l-0.5,-2.29l-1.42,-1.46l-0.27,-1.93l-1.43,-1.2l2.31,-2.37l3.05,0.06l2.62,-2.85l1.6,-2.81l2.4,-2.73l0.07,-2.04l1.97,-1.48l-0.02,-0.65l-1.93,-1.31l-0.82,-1.78l-0.8,-2.21l0.9,-0.89l3.59,0.65l2.92,-0.42l2.33,-2.19l2.31,2.85l-0.24,2.13l0.99,1.59l-0.05,0.82l-1.34,-0.28l-0.47,0.48l0.7,3.06l2.62,1.99l2.99,1.65Z",name:"India"},TZ:{path:"M495.56,296.42l2.8,-3.12l-0.02,-0.81l-0.64,-1.3l0.68,-0.52l0.14,-1.47l-0.76,-1.25l0.31,-0.11l2.26,0.03l-0.51,2.76l0.76,1.3l0.5,0.12l1.05,-0.53l1.19,-0.12l0.61,0.24l1.43,-0.62l0.1,-0.67l-0.71,-0.62l1.57,-1.7l8.65,4.86l0.32,1.53l3.34,2.33l-1.05,2.8l0.13,1.61l1.63,1.12l-0.6,1.76l-0.01,2.33l1.89,4.03l0.57,0.43l-1.46,1.08l-2.61,0.94l-1.43,-0.04l-1.06,0.77l-2.29,0.36l-2.87,-0.68l-0.83,0.07l-0.63,-0.75l-0.31,-2.78l-1.32,-1.35l-3.25,-0.77l-3.96,-1.58l-1.18,-2.41l-0.32,-1.75l-1.76,-1.49l0.42,-1.05l-0.44,-0.89l0.08,-0.96l-0.46,-0.58l0.06,-0.56Z",name:"Tanzania"},AZ:{path:"M539.29,175.73l1.33,0.32l1.94,-1.8l2.3,3.34l1.43,0.43l-1.26,0.15l-0.35,0.32l-0.8,3.14l-0.99,0.96l0.05,1.11l-1.26,-1.13l0.7,-1.18l-0.04,-0.47l-0.74,-0.86l-1.48,0.15l-2.34,1.71l-0.03,-1.27l-2.03,-1.35l0.47,-0.62l-0.08,-0.56l-1.03,-0.79l0.29,-0.43l-0.14,-0.58l-1.13,-0.86l1.89,0.68l1.69,0.06l0.37,-0.87l-0.81,-1.37l0.42,0.06l1.63,1.72ZM533.78,180.57l0.61,0.46l0.69,-0.0l0.59,1.15l-0.68,-0.15l-1.21,-1.45Z",name:"Azerbaijan"},IE:{path:"M405.08,135.42l0.35,2.06l-1.75,2.78l-4.22,1.88l-2.84,-0.4l1.73,-3.0l-1.18,-3.53l4.6,-3.74l0.32,1.15l-0.49,1.74l0.4,0.51l1.47,-0.04l1.6,0.6Z",name:"Ireland"},ID:{path:"M756.47,287.89l0.69,4.01l2.79,1.78l0.51,-0.1l2.04,-2.59l2.71,-1.43l2.05,-0.0l3.9,1.73l2.46,0.45l0.08,15.12l-1.75,-1.54l-2.54,-0.51l-0.88,0.71l-2.32,0.06l0.69,-1.33l1.45,-0.64l0.23,-0.46l-0.65,-2.74l-1.24,-2.21l-5.04,-2.29l-2.09,-0.23l-3.68,-2.27l-0.55,0.13l-0.65,1.07l-0.52,0.12l-0.55,-1.89l-1.21,-0.78l1.84,-0.62l1.72,0.05l0.39,-0.52l-0.21,-0.66l-0.38,-0.28l-3.45,-0.0l-1.13,-1.48l-2.1,-0.43l-0.52,-0.6l2.69,-0.48l1.28,-0.78l3.66,0.94l0.3,0.71ZM757.91,300.34l-0.62,0.82l-0.1,-0.8l0.59,-1.12l0.13,1.1ZM747.38,292.98l0.34,0.72l-1.22,-0.57l-4.68,-0.1l0.27,-0.62l2.78,-0.09l2.52,0.67ZM741.05,285.25l-0.67,-2.88l0.64,-2.01l0.41,0.86l1.21,0.18l0.16,0.7l-0.1,1.68l-0.84,-0.16l-0.46,0.3l-0.34,1.34ZM739.05,293.5l-0.5,0.44l-1.34,-0.36l-0.17,-0.37l1.73,-0.08l0.27,0.36ZM721.45,284.51l-0.19,1.97l2.24,2.23l0.54,0.02l1.27,-1.07l2.75,-0.5l-0.9,1.21l-2.11,0.93l-0.16,0.6l2.22,3.01l-0.3,1.07l1.36,1.74l-2.26,0.85l-0.28,-0.31l0.12,-1.19l-1.64,-1.34l0.17,-2.23l-0.56,-0.39l-1.67,0.76l-0.23,0.39l0.3,6.17l-1.1,0.25l-0.69,-0.47l0.64,-2.21l-0.39,-2.42l-0.39,-0.34l-0.8,-0.01l-0.58,-1.29l0.98,-1.6l0.35,-1.96l1.32,-3.87ZM728.59,296.27l0.38,0.49l-0.02,1.28l-0.88,0.49l-0.53,-0.47l1.04,-1.79ZM729.04,286.98l0.27,-0.05l-0.02,0.13l-0.24,-0.08ZM721.68,284.05l0.16,-0.32l1.89,-1.65l1.83,0.68l3.16,0.35l2.94,-0.1l2.39,-1.66l-1.73,2.13l-1.66,0.43l-2.41,-0.48l-4.17,0.13l-2.39,0.51ZM730.55,310.47l1.11,-1.93l2.03,-0.82l0.08,0.62l-1.45,1.67l-1.77,0.46ZM728.12,305.88l-0.1,0.38l-3.46,0.66l-2.91,-0.27l-0.0,-0.25l1.54,-0.41l1.66,0.73l1.67,-0.19l1.61,-0.65ZM722.9,310.24l-0.64,0.03l-2.26,-1.2l1.11,-0.24l1.78,1.41ZM716.26,305.77l0.88,0.51l1.28,-0.17l0.2,0.35l-4.65,0.73l0.39,-0.67l1.15,-0.02l0.75,-0.73ZM711.66,293.84l-0.38,-0.16l-2.54,1.01l-1.12,-1.44l-1.69,-0.13l-1.16,-0.75l-3.04,0.77l-1.1,-1.15l-3.31,-0.11l-0.35,-3.05l-1.35,-0.95l-1.11,-1.98l-0.33,-2.06l0.27,-2.14l0.9,-1.01l0.37,1.15l2.09,1.49l1.53,-0.48l1.82,0.08l1.38,-1.19l1.0,-0.18l2.28,0.67l2.26,-0.53l1.52,-3.64l1.01,-0.99l0.78,-2.57l4.1,0.3l-1.11,1.77l0.02,0.46l1.7,2.2l-0.23,1.39l2.07,1.71l-2.33,0.42l-0.88,1.9l0.1,2.05l-2.4,1.9l-0.06,2.45l-0.7,2.79ZM692.58,302.03l0.35,0.26l4.8,0.25l0.78,-0.97l4.17,1.09l1.13,1.68l3.69,0.45l2.13,1.04l-1.8,0.6l-2.77,-0.99l-4.8,-0.12l-5.24,-1.41l-1.84,-0.25l-1.11,0.3l-4.26,-0.97l-0.7,-1.14l-1.59,-0.13l1.18,-1.65l2.74,0.13l2.87,1.13l0.26,0.68ZM685.53,299.17l-2.22,0.04l-2.06,-2.03l-3.15,-2.01l-2.93,-3.51l-3.11,-5.33l-2.2,-2.12l-1.64,-4.06l-2.32,-1.69l-1.27,-2.07l-1.96,-1.5l-2.51,-2.65l-0.11,-0.66l4.81,0.53l2.15,2.38l3.31,2.74l2.35,2.66l2.7,0.17l1.95,1.59l1.54,2.17l1.59,0.95l-0.84,1.71l0.15,0.52l1.44,0.87l0.79,0.1l0.4,1.58l0.87,1.4l1.96,0.39l1.0,1.31l-0.6,3.01l-0.09,3.5Z",name:"Indonesia"},UA:{path:"M492.5,162.44l1.28,-2.49l1.82,0.19l0.66,-0.23l0.09,-0.71l-0.25,-0.75l-0.79,-0.72l-0.33,-1.21l-0.86,-0.62l-0.02,-1.19l-1.13,-0.86l-1.15,-0.19l-2.04,-1.0l-1.66,0.32l-0.66,0.47l-0.92,-0.0l-0.84,0.78l-2.48,0.7l-1.18,-0.71l-3.07,-0.36l-0.89,0.43l-0.24,-0.55l-1.11,-0.7l0.35,-0.93l1.26,-1.02l-0.54,-1.23l2.04,-2.43l1.4,-0.62l0.25,-1.19l-1.04,-2.39l0.83,-0.13l1.28,-0.84l1.8,-0.07l2.47,0.26l2.86,0.81l1.88,0.06l0.86,0.44l1.04,-0.41l0.77,0.66l2.18,-0.15l0.92,0.3l0.52,-0.34l0.15,-1.53l0.56,-0.54l2.85,-0.05l0.84,-0.72l3.04,-0.18l1.23,1.46l-0.48,0.77l0.21,1.03l0.36,0.32l1.8,0.14l0.93,2.08l3.18,1.15l1.94,-0.45l1.67,1.49l1.4,-0.03l3.35,0.96l0.02,0.54l-0.96,1.59l0.47,1.97l-0.26,0.7l-2.36,0.28l-1.29,0.89l-0.23,1.38l-1.83,0.27l-1.58,0.97l-2.41,0.21l-2.16,1.17l-0.21,0.38l0.34,2.26l1.23,0.75l2.13,-0.08l-0.14,0.31l-2.65,0.53l-3.23,1.69l-0.87,-0.39l0.42,-1.1l-0.25,-0.52l-2.21,-0.73l2.35,-1.06l0.12,-0.65l-0.93,-0.82l-3.62,-0.74l-0.13,-0.89l-0.46,-0.34l-2.61,0.59l-0.91,1.69l-1.71,2.04l-0.86,-0.4l-1.62,0.27Z",name:"Ukraine"},QA:{path:"M549.33,221.64l-0.76,-0.23l-0.14,-1.64l0.84,-1.29l0.47,0.52l0.04,1.34l-0.45,1.3Z",name:"Qatar"},MZ:{path:"M508.58,318.75l-0.34,-2.57l0.51,-2.05l3.55,0.63l2.5,-0.38l1.02,-0.76l1.49,0.01l2.74,-0.98l1.66,-1.2l0.5,9.24l0.41,1.23l-0.68,1.67l-0.93,1.71l-1.5,1.5l-5.16,2.28l-2.78,2.73l-1.02,0.53l-1.71,1.8l-0.98,0.57l-0.35,2.41l1.16,1.94l0.49,2.17l0.43,0.31l-0.06,2.06l-0.39,1.17l0.5,0.72l-0.25,0.73l-0.92,0.83l-5.12,2.39l-1.22,1.36l0.21,1.13l0.58,0.39l-0.11,0.72l-1.22,-0.01l-0.73,-2.97l0.42,-3.09l-1.78,-5.37l2.49,-2.81l0.69,-1.89l0.44,-0.43l0.28,-1.53l-0.39,-0.93l0.59,-3.65l-0.01,-3.26l-1.49,-1.16l-1.2,-0.22l-1.74,-1.17l-1.92,0.01l-0.29,-2.08l7.06,-1.96l1.28,1.09l0.89,-0.1l0.67,0.44l0.1,0.73l-0.51,1.29l0.19,1.81l1.75,1.83l0.65,-0.13l0.71,-1.65l1.17,-0.86l-0.26,-3.47l-1.05,-1.85l-1.04,-0.94Z",name:"Mozambique"}},height:440.70631074413296,width:900,projection:{type:"mill",centralMeridian:11.5}});
/***/ },
/***/ 95
(module) {
const API_URL = 'https://maps.googleapis.com/maps/api/js'
const CALLBACK_NAME = '__googleMapsApiOnLoadCallback'
const optionsKeys = ['channel', 'client', 'key', 'language', 'region', 'v']
let promise = null
module.exports = function (options = {}) {
promise =
promise ||
new Promise(function (resolve, reject) {
// Reject the promise after a timeout
const timeoutId = setTimeout(function () {
window[CALLBACK_NAME] = function () {} // Set the on load callback to a no-op
reject(new Error('Could not load the Google Maps API'))
}, options.timeout || 10000)
// Hook up the on load callback
window[CALLBACK_NAME] = function () {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
resolve(window.google.maps)
delete window[CALLBACK_NAME]
}
// Prepare the `script` tag to be inserted into the page
const scriptElement = document.createElement('script')
const params = [`callback=${CALLBACK_NAME}`]
optionsKeys.forEach(function (key) {
if (options[key]) {
params.push(`${key}=${options[key]}`)
}
})
if (options.libraries && options.libraries.length) {
params.push(`libraries=${options.libraries.join(',')}`)
}
scriptElement.src = `${options.apiUrl || API_URL}?${params.join('&')}`
// Insert the `script` tag
document.body.appendChild(scriptElement)
})
return promise
}
/***/ },
/***/ 201
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
* Masonry v4.2.2
* Cascading grid layout library
* https://masonry.desandro.com
* MIT License
* by David DeSandro
*/
( function( window, factory ) {
// universal module definition
/* jshint strict: false */ /*globals define, module, require */
if ( true ) {
// AMD
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
__webpack_require__(442),
__webpack_require__(928)
], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( window, function factory( Outlayer, getSize ) {
'use strict';
// -------------------------- masonryDefinition -------------------------- //
// create an Outlayer layout class
var Masonry = Outlayer.create('masonry');
// isFitWidth -> fitWidth
Masonry.compatOptions.fitWidth = 'isFitWidth';
var proto = Masonry.prototype;
proto._resetLayout = function() {
this.getSize();
this._getMeasurement( 'columnWidth', 'outerWidth' );
this._getMeasurement( 'gutter', 'outerWidth' );
this.measureColumns();
// reset column Y
this.colYs = [];
for ( var i=0; i < this.cols; i++ ) {
this.colYs.push( 0 );
}
this.maxY = 0;
this.horizontalColIndex = 0;
};
proto.measureColumns = function() {
this.getContainerWidth();
// if columnWidth is 0, default to outerWidth of first item
if ( !this.columnWidth ) {
var firstItem = this.items[0];
var firstItemElem = firstItem && firstItem.element;
// columnWidth fall back to item of first element
this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
// if first elem has no width, default to size of container
this.containerWidth;
}
var columnWidth = this.columnWidth += this.gutter;
// calculate columns
var containerWidth = this.containerWidth + this.gutter;
var cols = containerWidth / columnWidth;
// fix rounding errors, typically with gutters
var excess = columnWidth - containerWidth % columnWidth;
// if overshoot is less than a pixel, round up, otherwise floor it
var mathMethod = excess && excess < 1 ? 'round' : 'floor';
cols = Math[ mathMethod ]( cols );
this.cols = Math.max( cols, 1 );
};
proto.getContainerWidth = function() {
// container is parent if fit width
var isFitWidth = this._getOption('fitWidth');
var container = isFitWidth ? this.element.parentNode : this.element;
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var size = getSize( container );
this.containerWidth = size && size.innerWidth;
};
proto._getItemLayoutPosition = function( item ) {
item.getSize();
// how many columns does this brick span
var remainder = item.size.outerWidth % this.columnWidth;
var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
// round if off by 1 pixel, otherwise use ceil
var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
colSpan = Math.min( colSpan, this.cols );
// use horizontal or top column position
var colPosMethod = this.options.horizontalOrder ?
'_getHorizontalColPosition' : '_getTopColPosition';
var colPosition = this[ colPosMethod ]( colSpan, item );
// position the brick
var position = {
x: this.columnWidth * colPosition.col,
y: colPosition.y
};
// apply setHeight to necessary columns
var setHeight = colPosition.y + item.size.outerHeight;
var setMax = colSpan + colPosition.col;
for ( var i = colPosition.col; i < setMax; i++ ) {
this.colYs[i] = setHeight;
}
return position;
};
proto._getTopColPosition = function( colSpan ) {
var colGroup = this._getTopColGroup( colSpan );
// get the minimum Y value from the columns
var minimumY = Math.min.apply( Math, colGroup );
return {
col: colGroup.indexOf( minimumY ),
y: minimumY,
};
};
/**
* @param {Number} colSpan - number of columns the element spans
* @returns {Array} colGroup
*/
proto._getTopColGroup = function( colSpan ) {
if ( colSpan < 2 ) {
// if brick spans only one column, use all the column Ys
return this.colYs;
}
var colGroup = [];
// how many different places could this brick fit horizontally
var groupCount = this.cols + 1 - colSpan;
// for each group potential horizontal position
for ( var i = 0; i < groupCount; i++ ) {
colGroup[i] = this._getColGroupY( i, colSpan );
}
return colGroup;
};
proto._getColGroupY = function( col, colSpan ) {
if ( colSpan < 2 ) {
return this.colYs[ col ];
}
// make an array of colY values for that one group
var groupColYs = this.colYs.slice( col, col + colSpan );
// and get the max value of the array
return Math.max.apply( Math, groupColYs );
};
// get column position based on horizontal index. #873
proto._getHorizontalColPosition = function( colSpan, item ) {
var col = this.horizontalColIndex % this.cols;
var isOver = colSpan > 1 && col + colSpan > this.cols;
// shift to next row if item can't fit on current row
col = isOver ? 0 : col;
// don't let zero-size items take up space
var hasSize = item.size.outerWidth && item.size.outerHeight;
this.horizontalColIndex = hasSize ? col + colSpan : this.horizontalColIndex;
return {
col: col,
y: this._getColGroupY( col, colSpan ),
};
};
proto._manageStamp = function( stamp ) {
var stampSize = getSize( stamp );
var offset = this._getElementOffset( stamp );
// get the columns that this stamp affects
var isOriginLeft = this._getOption('originLeft');
var firstX = isOriginLeft ? offset.left : offset.right;
var lastX = firstX + stampSize.outerWidth;
var firstCol = Math.floor( firstX / this.columnWidth );
firstCol = Math.max( 0, firstCol );
var lastCol = Math.floor( lastX / this.columnWidth );
// lastCol should not go over if multiple of columnWidth #425
lastCol -= lastX % this.columnWidth ? 0 : 1;
lastCol = Math.min( this.cols - 1, lastCol );
// set colYs to bottom of the stamp
var isOriginTop = this._getOption('originTop');
var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) +
stampSize.outerHeight;
for ( var i = firstCol; i <= lastCol; i++ ) {
this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
}
};
proto._getContainerSize = function() {
this.maxY = Math.max.apply( Math, this.colYs );
var size = {
height: this.maxY
};
if ( this._getOption('fitWidth') ) {
size.width = this._getContainerFitWidth();
}
return size;
};
proto._getContainerFitWidth = function() {
var unusedCols = 0;
// count unused columns
var i = this.cols;
while ( --i ) {
if ( this.colYs[i] !== 0 ) {
break;
}
unusedCols++;
}
// fit container to columns that have been used
return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
};
proto.needsResizeLayout = function() {
var previousWidth = this.containerWidth;
this.getContainerWidth();
return previousWidth != this.containerWidth;
};
return Masonry;
}));
/***/ },
/***/ 396
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
* Outlayer Item
*/
( function( window, factory ) {
// universal module definition
/* jshint strict: false */ /* globals define, module, require */
if ( true ) {
// AMD - RequireJS
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
__webpack_require__(240),
__webpack_require__(928)
], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( window, function factory( EvEmitter, getSize ) {
'use strict';
// ----- helpers ----- //
function isEmptyObj( obj ) {
for ( var prop in obj ) {
return false;
}
prop = null;
return true;
}
// -------------------------- CSS3 support -------------------------- //
var docElemStyle = document.documentElement.style;
var transitionProperty = typeof docElemStyle.transition == 'string' ?
'transition' : 'WebkitTransition';
var transformProperty = typeof docElemStyle.transform == 'string' ?
'transform' : 'WebkitTransform';
var transitionEndEvent = {
WebkitTransition: 'webkitTransitionEnd',
transition: 'transitionend'
}[ transitionProperty ];
// cache all vendor properties that could have vendor prefix
var vendorProperties = {
transform: transformProperty,
transition: transitionProperty,
transitionDuration: transitionProperty + 'Duration',
transitionProperty: transitionProperty + 'Property',
transitionDelay: transitionProperty + 'Delay'
};
// -------------------------- Item -------------------------- //
function Item( element, layout ) {
if ( !element ) {
return;
}
this.element = element;
// parent layout class, i.e. Masonry, Isotope, or Packery
this.layout = layout;
this.position = {
x: 0,
y: 0
};
this._create();
}
// inherit EvEmitter
var proto = Item.prototype = Object.create( EvEmitter.prototype );
proto.constructor = Item;
proto._create = function() {
// transition objects
this._transn = {
ingProperties: {},
clean: {},
onEnd: {}
};
this.css({
position: 'absolute'
});
};
// trigger specified handler for event type
proto.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
proto.getSize = function() {
this.size = getSize( this.element );
};
/**
* apply CSS styles to element
* @param {Object} style
*/
proto.css = function( style ) {
var elemStyle = this.element.style;
for ( var prop in style ) {
// use vendor property if available
var supportedProp = vendorProperties[ prop ] || prop;
elemStyle[ supportedProp ] = style[ prop ];
}
};
// measure position, and sets it
proto.getPosition = function() {
var style = getComputedStyle( this.element );
var isOriginLeft = this.layout._getOption('originLeft');
var isOriginTop = this.layout._getOption('originTop');
var xValue = style[ isOriginLeft ? 'left' : 'right' ];
var yValue = style[ isOriginTop ? 'top' : 'bottom' ];
var x = parseFloat( xValue );
var y = parseFloat( yValue );
// convert percent to pixels
var layoutSize = this.layout.size;
if ( xValue.indexOf('%') != -1 ) {
x = ( x / 100 ) * layoutSize.width;
}
if ( yValue.indexOf('%') != -1 ) {
y = ( y / 100 ) * layoutSize.height;
}
// clean up 'auto' or other non-integer values
x = isNaN( x ) ? 0 : x;
y = isNaN( y ) ? 0 : y;
// remove padding from measurement
x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;
this.position.x = x;
this.position.y = y;
};
// set settled position, apply padding
proto.layoutPosition = function() {
var layoutSize = this.layout.size;
var style = {};
var isOriginLeft = this.layout._getOption('originLeft');
var isOriginTop = this.layout._getOption('originTop');
// x
var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight';
var xProperty = isOriginLeft ? 'left' : 'right';
var xResetProperty = isOriginLeft ? 'right' : 'left';
var x = this.position.x + layoutSize[ xPadding ];
// set in percentage or pixels
style[ xProperty ] = this.getXValue( x );
// reset other property
style[ xResetProperty ] = '';
// y
var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom';
var yProperty = isOriginTop ? 'top' : 'bottom';
var yResetProperty = isOriginTop ? 'bottom' : 'top';
var y = this.position.y + layoutSize[ yPadding ];
// set in percentage or pixels
style[ yProperty ] = this.getYValue( y );
// reset other property
style[ yResetProperty ] = '';
this.css( style );
this.emitEvent( 'layout', [ this ] );
};
proto.getXValue = function( x ) {
var isHorizontal = this.layout._getOption('horizontal');
return this.layout.options.percentPosition && !isHorizontal ?
( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px';
};
proto.getYValue = function( y ) {
var isHorizontal = this.layout._getOption('horizontal');
return this.layout.options.percentPosition && isHorizontal ?
( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px';
};
proto._transitionTo = function( x, y ) {
this.getPosition();
// get current x & y from top/left
var curX = this.position.x;
var curY = this.position.y;
var didNotMove = x == this.position.x && y == this.position.y;
// save end position
this.setPosition( x, y );
// if did not move and not transitioning, just go to layout
if ( didNotMove && !this.isTransitioning ) {
this.layoutPosition();
return;
}
var transX = x - curX;
var transY = y - curY;
var transitionStyle = {};
transitionStyle.transform = this.getTranslate( transX, transY );
this.transition({
to: transitionStyle,
onTransitionEnd: {
transform: this.layoutPosition
},
isCleaning: true
});
};
proto.getTranslate = function( x, y ) {
// flip cooridinates if origin on right or bottom
var isOriginLeft = this.layout._getOption('originLeft');
var isOriginTop = this.layout._getOption('originTop');
x = isOriginLeft ? x : -x;
y = isOriginTop ? y : -y;
return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
};
// non transition + transform support
proto.goTo = function( x, y ) {
this.setPosition( x, y );
this.layoutPosition();
};
proto.moveTo = proto._transitionTo;
proto.setPosition = function( x, y ) {
this.position.x = parseFloat( x );
this.position.y = parseFloat( y );
};
// ----- transition ----- //
/**
* @param {Object} style - CSS
* @param {Function} onTransitionEnd
*/
// non transition, just trigger callback
proto._nonTransition = function( args ) {
this.css( args.to );
if ( args.isCleaning ) {
this._removeStyles( args.to );
}
for ( var prop in args.onTransitionEnd ) {
args.onTransitionEnd[ prop ].call( this );
}
};
/**
* proper transition
* @param {Object} args - arguments
* @param {Object} to - style to transition to
* @param {Object} from - style to start transition from
* @param {Boolean} isCleaning - removes transition styles after transition
* @param {Function} onTransitionEnd - callback
*/
proto.transition = function( args ) {
// redirect to nonTransition if no transition duration
if ( !parseFloat( this.layout.options.transitionDuration ) ) {
this._nonTransition( args );
return;
}
var _transition = this._transn;
// keep track of onTransitionEnd callback by css property
for ( var prop in args.onTransitionEnd ) {
_transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
}
// keep track of properties that are transitioning
for ( prop in args.to ) {
_transition.ingProperties[ prop ] = true;
// keep track of properties to clean up when transition is done
if ( args.isCleaning ) {
_transition.clean[ prop ] = true;
}
}
// set from styles
if ( args.from ) {
this.css( args.from );
// force redraw. http://blog.alexmaccaw.com/css-transitions
var h = this.element.offsetHeight;
// hack for JSHint to hush about unused var
h = null;
}
// enable transition
this.enableTransition( args.to );
// set styles that are transitioning
this.css( args.to );
this.isTransitioning = true;
};
// dash before all cap letters, including first for
// WebkitTransform => -webkit-transform
function toDashedAll( str ) {
return str.replace( /([A-Z])/g, function( $1 ) {
return '-' + $1.toLowerCase();
});
}
var transitionProps = 'opacity,' + toDashedAll( transformProperty );
proto.enableTransition = function(/* style */) {
// HACK changing transitionProperty during a transition
// will cause transition to jump
if ( this.isTransitioning ) {
return;
}
// make `transition: foo, bar, baz` from style object
// HACK un-comment this when enableTransition can work
// while a transition is happening
// var transitionValues = [];
// for ( var prop in style ) {
// // dash-ify camelCased properties like WebkitTransition
// prop = vendorProperties[ prop ] || prop;
// transitionValues.push( toDashedAll( prop ) );
// }
// munge number to millisecond, to match stagger
var duration = this.layout.options.transitionDuration;
duration = typeof duration == 'number' ? duration + 'ms' : duration;
// enable transition styles
this.css({
transitionProperty: transitionProps,
transitionDuration: duration,
transitionDelay: this.staggerDelay || 0
});
// listen for transition end event
this.element.addEventListener( transitionEndEvent, this, false );
};
// ----- events ----- //
proto.onwebkitTransitionEnd = function( event ) {
this.ontransitionend( event );
};
proto.onotransitionend = function( event ) {
this.ontransitionend( event );
};
// properties that I munge to make my life easier
var dashedVendorProperties = {
'-webkit-transform': 'transform'
};
proto.ontransitionend = function( event ) {
// disregard bubbled events from children
if ( event.target !== this.element ) {
return;
}
var _transition = this._transn;
// get property name of transitioned property, convert to prefix-free
var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;
// remove property that has completed transitioning
delete _transition.ingProperties[ propertyName ];
// check if any properties are still transitioning
if ( isEmptyObj( _transition.ingProperties ) ) {
// all properties have completed transitioning
this.disableTransition();
}
// clean style
if ( propertyName in _transition.clean ) {
// clean up style
this.element.style[ event.propertyName ] = '';
delete _transition.clean[ propertyName ];
}
// trigger onTransitionEnd callback
if ( propertyName in _transition.onEnd ) {
var onTransitionEnd = _transition.onEnd[ propertyName ];
onTransitionEnd.call( this );
delete _transition.onEnd[ propertyName ];
}
this.emitEvent( 'transitionEnd', [ this ] );
};
proto.disableTransition = function() {
this.removeTransitionStyles();
this.element.removeEventListener( transitionEndEvent, this, false );
this.isTransitioning = false;
};
/**
* removes style property from element
* @param {Object} style
**/
proto._removeStyles = function( style ) {
// clean up transition styles
var cleanStyle = {};
for ( var prop in style ) {
cleanStyle[ prop ] = '';
}
this.css( cleanStyle );
};
var cleanTransitionStyle = {
transitionProperty: '',
transitionDuration: '',
transitionDelay: ''
};
proto.removeTransitionStyles = function() {
// remove transition
this.css( cleanTransitionStyle );
};
// ----- stagger ----- //
proto.stagger = function( delay ) {
delay = isNaN( delay ) ? 0 : delay;
this.staggerDelay = delay + 'ms';
};
// ----- show/hide/remove ----- //
// remove element from DOM
proto.removeElem = function() {
this.element.parentNode.removeChild( this.element );
// remove display: none
this.css({ display: '' });
this.emitEvent( 'remove', [ this ] );
};
proto.remove = function() {
// just remove element if no transition support or no transition
if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
this.removeElem();
return;
}
// start transition
this.once( 'transitionEnd', function() {
this.removeElem();
});
this.hide();
};
proto.reveal = function() {
delete this.isHidden;
// remove display: none
this.css({ display: '' });
var options = this.layout.options;
var onTransitionEnd = {};
var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle');
onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd;
this.transition({
from: options.hiddenStyle,
to: options.visibleStyle,
isCleaning: true,
onTransitionEnd: onTransitionEnd
});
};
proto.onRevealTransitionEnd = function() {
// check if still visible
// during transition, item may have been hidden
if ( !this.isHidden ) {
this.emitEvent('reveal');
}
};
/**
* get style property use for hide/reveal transition end
* @param {String} styleProperty - hiddenStyle/visibleStyle
* @returns {String}
*/
proto.getHideRevealTransitionEndProperty = function( styleProperty ) {
var optionStyle = this.layout.options[ styleProperty ];
// use opacity
if ( optionStyle.opacity ) {
return 'opacity';
}
// get first property
for ( var prop in optionStyle ) {
return prop;
}
};
proto.hide = function() {
// set flag
this.isHidden = true;
// remove display: none
this.css({ display: '' });
var options = this.layout.options;
var onTransitionEnd = {};
var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle');
onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd;
this.transition({
from: options.visibleStyle,
to: options.hiddenStyle,
// keep hidden stuff hidden
isCleaning: true,
onTransitionEnd: onTransitionEnd
});
};
proto.onHideTransitionEnd = function() {
// check if still hidden
// during transition, item may have been un-hidden
if ( this.isHidden ) {
this.css({ display: 'none' });
this.emitEvent('hide');
}
};
proto.destroy = function() {
this.css({
position: '',
left: '',
right: '',
top: '',
bottom: '',
transition: '',
transform: ''
});
};
return Item;
}));
/***/ },
/***/ 442
(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
* Outlayer v2.1.1
* the brains and guts of a layout library
* MIT license
*/
( function( window, factory ) {
'use strict';
// universal module definition
/* jshint strict: false */ /* globals define, module, require */
if ( true ) {
// AMD - RequireJS
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
__webpack_require__(240),
__webpack_require__(928),
__webpack_require__(346),
__webpack_require__(396)
], __WEBPACK_AMD_DEFINE_RESULT__ = (function( EvEmitter, getSize, utils, Item ) {
return factory( window, EvEmitter, getSize, utils, Item);
}).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else // removed by dead control flow
{}
}( window, function factory( window, EvEmitter, getSize, utils, Item ) {
'use strict';
// ----- vars ----- //
var console = window.console;
var jQuery = window.jQuery;
var noop = function() {};
// -------------------------- Outlayer -------------------------- //
// globally unique identifiers
var GUID = 0;
// internal store of all Outlayer intances
var instances = {};
/**
* @param {Element, String} element
* @param {Object} options
* @constructor
*/
function Outlayer( element, options ) {
var queryElement = utils.getQueryElement( element );
if ( !queryElement ) {
if ( console ) {
console.error( 'Bad element for ' + this.constructor.namespace +
': ' + ( queryElement || element ) );
}
return;
}
this.element = queryElement;
// add jQuery
if ( jQuery ) {
this.$element = jQuery( this.element );
}
// options
this.options = utils.extend( {}, this.constructor.defaults );
this.option( options );
// add id for Outlayer.getFromElement
var id = ++GUID;
this.element.outlayerGUID = id; // expando
instances[ id ] = this; // associate via id
// kick it off
this._create();
var isInitLayout = this._getOption('initLayout');
if ( isInitLayout ) {
this.layout();
}
}
// settings are for internal use only
Outlayer.namespace = 'outlayer';
Outlayer.Item = Item;
// default options
Outlayer.defaults = {
containerStyle: {
position: 'relative'
},
initLayout: true,
originLeft: true,
originTop: true,
resize: true,
resizeContainer: true,
// item options
transitionDuration: '0.4s',
hiddenStyle: {
opacity: 0,
transform: 'scale(0.001)'
},
visibleStyle: {
opacity: 1,
transform: 'scale(1)'
}
};
var proto = Outlayer.prototype;
// inherit EvEmitter
utils.extend( proto, EvEmitter.prototype );
/**
* set options
* @param {Object} opts
*/
proto.option = function( opts ) {
utils.extend( this.options, opts );
};
/**
* get backwards compatible option value, check old name
*/
proto._getOption = function( option ) {
var oldOption = this.constructor.compatOptions[ option ];
return oldOption && this.options[ oldOption ] !== undefined ?
this.options[ oldOption ] : this.options[ option ];
};
Outlayer.compatOptions = {
// currentName: oldName
initLayout: 'isInitLayout',
horizontal: 'isHorizontal',
layoutInstant: 'isLayoutInstant',
originLeft: 'isOriginLeft',
originTop: 'isOriginTop',
resize: 'isResizeBound',
resizeContainer: 'isResizingContainer'
};
proto._create = function() {
// get items from children
this.reloadItems();
// elements that affect layout, but are not laid out
this.stamps = [];
this.stamp( this.options.stamp );
// set container style
utils.extend( this.element.style, this.options.containerStyle );
// bind resize method
var canBindResize = this._getOption('resize');
if ( canBindResize ) {
this.bindResize();
}
};
// goes through all children again and gets bricks in proper order
proto.reloadItems = function() {
// collection of item elements
this.items = this._itemize( this.element.children );
};
/**
* turn elements into Outlayer.Items to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - collection of new Outlayer Items
*/
proto._itemize = function( elems ) {
var itemElems = this._filterFindItemElements( elems );
var Item = this.constructor.Item;
// create new Outlayer Items for collection
var items = [];
for ( var i=0; i < itemElems.length; i++ ) {
var elem = itemElems[i];
var item = new Item( elem, this );
items.push( item );
}
return items;
};
/**
* get item elements to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - item elements
*/
proto._filterFindItemElements = function( elems ) {
return utils.filterFindElements( elems, this.options.itemSelector );
};
/**
* getter method for getting item elements
* @returns {Array} elems - collection of item elements
*/
proto.getItemElements = function() {
return this.items.map( function( item ) {
return item.element;
});
};
// ----- init & layout ----- //
/**
* lays out all items
*/
proto.layout = function() {
this._resetLayout();
this._manageStamps();
// don't animate first layout
var layoutInstant = this._getOption('layoutInstant');
var isInstant = layoutInstant !== undefined ?
layoutInstant : !this._isLayoutInited;
this.layoutItems( this.items, isInstant );
// flag for initalized
this._isLayoutInited = true;
};
// _init is alias for layout
proto._init = proto.layout;
/**
* logic before any new layout
*/
proto._resetLayout = function() {
this.getSize();
};
proto.getSize = function() {
this.size = getSize( this.element );
};
/**
* get measurement from option, for columnWidth, rowHeight, gutter
* if option is String -> get element from selector string, & get size of element
* if option is Element -> get size of element
* else use option as a number
*
* @param {String} measurement
* @param {String} size - width or height
* @private
*/
proto._getMeasurement = function( measurement, size ) {
var option = this.options[ measurement ];
var elem;
if ( !option ) {
// default to 0
this[ measurement ] = 0;
} else {
// use option as an element
if ( typeof option == 'string' ) {
elem = this.element.querySelector( option );
} else if ( option instanceof HTMLElement ) {
elem = option;
}
// use size of element, if element
this[ measurement ] = elem ? getSize( elem )[ size ] : option;
}
};
/**
* layout a collection of item elements
* @api public
*/
proto.layoutItems = function( items, isInstant ) {
items = this._getItemsForLayout( items );
this._layoutItems( items, isInstant );
this._postLayout();
};
/**
* get the items to be laid out
* you may want to skip over some items
* @param {Array} items
* @returns {Array} items
*/
proto._getItemsForLayout = function( items ) {
return items.filter( function( item ) {
return !item.isIgnored;
});
};
/**
* layout items
* @param {Array} items
* @param {Boolean} isInstant
*/
proto._layoutItems = function( items, isInstant ) {
this._emitCompleteOnItems( 'layout', items );
if ( !items || !items.length ) {
// no items, emit event with empty array
return;
}
var queue = [];
items.forEach( function( item ) {
// get x/y object from method
var position = this._getItemLayoutPosition( item );
// enqueue
position.item = item;
position.isInstant = isInstant || item.isLayoutInstant;
queue.push( position );
}, this );
this._processLayoutQueue( queue );
};
/**
* get item layout position
* @param {Outlayer.Item} item
* @returns {Object} x and y position
*/
proto._getItemLayoutPosition = function( /* item */ ) {
return {
x: 0,
y: 0
};
};
/**
* iterate over array and position each item
* Reason being - separating this logic prevents 'layout invalidation'
* thx @paul_irish
* @param {Array} queue
*/
proto._processLayoutQueue = function( queue ) {
this.updateStagger();
queue.forEach( function( obj, i ) {
this._positionItem( obj.item, obj.x, obj.y, obj.isInstant, i );
}, this );
};
// set stagger from option in milliseconds number
proto.updateStagger = function() {
var stagger = this.options.stagger;
if ( stagger === null || stagger === undefined ) {
this.stagger = 0;
return;
}
this.stagger = getMilliseconds( stagger );
return this.stagger;
};
/**
* Sets position of item in DOM
* @param {Outlayer.Item} item
* @param {Number} x - horizontal position
* @param {Number} y - vertical position
* @param {Boolean} isInstant - disables transitions
*/
proto._positionItem = function( item, x, y, isInstant, i ) {
if ( isInstant ) {
// if not transition, just set CSS
item.goTo( x, y );
} else {
item.stagger( i * this.stagger );
item.moveTo( x, y );
}
};
/**
* Any logic you want to do after each layout,
* i.e. size the container
*/
proto._postLayout = function() {
this.resizeContainer();
};
proto.resizeContainer = function() {
var isResizingContainer = this._getOption('resizeContainer');
if ( !isResizingContainer ) {
return;
}
var size = this._getContainerSize();
if ( size ) {
this._setContainerMeasure( size.width, true );
this._setContainerMeasure( size.height, false );
}
};
/**
* Sets width or height of container if returned
* @returns {Object} size
* @param {Number} width
* @param {Number} height
*/
proto._getContainerSize = noop;
/**
* @param {Number} measure - size of width or height
* @param {Boolean} isWidth
*/
proto._setContainerMeasure = function( measure, isWidth ) {
if ( measure === undefined ) {
return;
}
var elemSize = this.size;
// add padding and border width if border box
if ( elemSize.isBorderBox ) {
measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
elemSize.borderLeftWidth + elemSize.borderRightWidth :
elemSize.paddingBottom + elemSize.paddingTop +
elemSize.borderTopWidth + elemSize.borderBottomWidth;
}
measure = Math.max( measure, 0 );
this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
};
/**
* emit eventComplete on a collection of items events
* @param {String} eventName
* @param {Array} items - Outlayer.Items
*/
proto._emitCompleteOnItems = function( eventName, items ) {
var _this = this;
function onComplete() {
_this.dispatchEvent( eventName + 'Complete', null, [ items ] );
}
var count = items.length;
if ( !items || !count ) {
onComplete();
return;
}
var doneCount = 0;
function tick() {
doneCount++;
if ( doneCount == count ) {
onComplete();
}
}
// bind callback
items.forEach( function( item ) {
item.once( eventName, tick );
});
};
/**
* emits events via EvEmitter and jQuery events
* @param {String} type - name of event
* @param {Event} event - original event
* @param {Array} args - extra arguments
*/
proto.dispatchEvent = function( type, event, args ) {
// add original event to arguments
var emitArgs = event ? [ event ].concat( args ) : args;
this.emitEvent( type, emitArgs );
if ( jQuery ) {
// set this.$element
this.$element = this.$element || jQuery( this.element );
if ( event ) {
// create jQuery event
var $event = jQuery.Event( event );
$event.type = type;
this.$element.trigger( $event, args );
} else {
// just trigger with type if no event available
this.$element.trigger( type, args );
}
}
};
// -------------------------- ignore & stamps -------------------------- //
/**
* keep item in collection, but do not lay it out
* ignored items do not get skipped in layout
* @param {Element} elem
*/
proto.ignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
item.isIgnored = true;
}
};
/**
* return item to layout collection
* @param {Element} elem
*/
proto.unignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
delete item.isIgnored;
}
};
/**
* adds elements to stamps
* @param {NodeList, Array, Element, or String} elems
*/
proto.stamp = function( elems ) {
elems = this._find( elems );
if ( !elems ) {
return;
}
this.stamps = this.stamps.concat( elems );
// ignore
elems.forEach( this.ignore, this );
};
/**
* removes elements to stamps
* @param {NodeList, Array, or Element} elems
*/
proto.unstamp = function( elems ) {
elems = this._find( elems );
if ( !elems ){
return;
}
elems.forEach( function( elem ) {
// filter out removed stamp elements
utils.removeFrom( this.stamps, elem );
this.unignore( elem );
}, this );
};
/**
* finds child elements
* @param {NodeList, Array, Element, or String} elems
* @returns {Array} elems
*/
proto._find = function( elems ) {
if ( !elems ) {
return;
}
// if string, use argument as selector string
if ( typeof elems == 'string' ) {
elems = this.element.querySelectorAll( elems );
}
elems = utils.makeArray( elems );
return elems;
};
proto._manageStamps = function() {
if ( !this.stamps || !this.stamps.length ) {
return;
}
this._getBoundingRect();
this.stamps.forEach( this._manageStamp, this );
};
// update boundingLeft / Top
proto._getBoundingRect = function() {
// get bounding rect for container element
var boundingRect = this.element.getBoundingClientRect();
var size = this.size;
this._boundingRect = {
left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
top: boundingRect.top + size.paddingTop + size.borderTopWidth,
right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
};
};
/**
* @param {Element} stamp
**/
proto._manageStamp = noop;
/**
* get x/y position of element relative to container element
* @param {Element} elem
* @returns {Object} offset - has left, top, right, bottom
*/
proto._getElementOffset = function( elem ) {
var boundingRect = elem.getBoundingClientRect();
var thisRect = this._boundingRect;
var size = getSize( elem );
var offset = {
left: boundingRect.left - thisRect.left - size.marginLeft,
top: boundingRect.top - thisRect.top - size.marginTop,
right: thisRect.right - boundingRect.right - size.marginRight,
bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
};
return offset;
};
// -------------------------- resize -------------------------- //
// enable event handlers for listeners
// i.e. resize -> onresize
proto.handleEvent = utils.handleEvent;
/**
* Bind layout to window resizing
*/
proto.bindResize = function() {
window.addEventListener( 'resize', this );
this.isResizeBound = true;
};
/**
* Unbind layout to window resizing
*/
proto.unbindResize = function() {
window.removeEventListener( 'resize', this );
this.isResizeBound = false;
};
proto.onresize = function() {
this.resize();
};
utils.debounceMethod( Outlayer, 'onresize', 100 );
proto.resize = function() {
// don't trigger if size did not change
// or if resize was unbound. See #9
if ( !this.isResizeBound || !this.needsResizeLayout() ) {
return;
}
this.layout();
};
/**
* check if layout is needed post layout
* @returns Boolean
*/
proto.needsResizeLayout = function() {
var size = getSize( this.element );
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var hasSizes = this.size && size;
return hasSizes && size.innerWidth !== this.size.innerWidth;
};
// -------------------------- methods -------------------------- //
/**
* add items to Outlayer instance
* @param {Array or NodeList or Element} elems
* @returns {Array} items - Outlayer.Items
**/
proto.addItems = function( elems ) {
var items = this._itemize( elems );
// add items to collection
if ( items.length ) {
this.items = this.items.concat( items );
}
return items;
};
/**
* Layout newly-appended item elements
* @param {Array or NodeList or Element} elems
*/
proto.appended = function( elems ) {
var items = this.addItems( elems );
if ( !items.length ) {
return;
}
// layout and reveal just the new items
this.layoutItems( items, true );
this.reveal( items );
};
/**
* Layout prepended elements
* @param {Array or NodeList or Element} elems
*/
proto.prepended = function( elems ) {
var items = this._itemize( elems );
if ( !items.length ) {
return;
}
// add items to beginning of collection
var previousItems = this.items.slice(0);
this.items = items.concat( previousItems );
// start new layout
this._resetLayout();
this._manageStamps();
// layout new stuff without transition
this.layoutItems( items, true );
this.reveal( items );
// layout previous items
this.layoutItems( previousItems );
};
/**
* reveal a collection of items
* @param {Array of Outlayer.Items} items
*/
proto.reveal = function( items ) {
this._emitCompleteOnItems( 'reveal', items );
if ( !items || !items.length ) {
return;
}
var stagger = this.updateStagger();
items.forEach( function( item, i ) {
item.stagger( i * stagger );
item.reveal();
});
};
/**
* hide a collection of items
* @param {Array of Outlayer.Items} items
*/
proto.hide = function( items ) {
this._emitCompleteOnItems( 'hide', items );
if ( !items || !items.length ) {
return;
}
var stagger = this.updateStagger();
items.forEach( function( item, i ) {
item.stagger( i * stagger );
item.hide();
});
};
/**
* reveal item elements
* @param {Array}, {Element}, {NodeList} items
*/
proto.revealItemElements = function( elems ) {
var items = this.getItems( elems );
this.reveal( items );
};
/**
* hide item elements
* @param {Array}, {Element}, {NodeList} items
*/
proto.hideItemElements = function( elems ) {
var items = this.getItems( elems );
this.hide( items );
};
/**
* get Outlayer.Item, given an Element
* @param {Element} elem
* @param {Function} callback
* @returns {Outlayer.Item} item
*/
proto.getItem = function( elem ) {
// loop through items to get the one that matches
for ( var i=0; i < this.items.length; i++ ) {
var item = this.items[i];
if ( item.element == elem ) {
// return item
return item;
}
}
};
/**
* get collection of Outlayer.Items, given Elements
* @param {Array} elems
* @returns {Array} items - Outlayer.Items
*/
proto.getItems = function( elems ) {
elems = utils.makeArray( elems );
var items = [];
elems.forEach( function( elem ) {
var item = this.getItem( elem );
if ( item ) {
items.push( item );
}
}, this );
return items;
};
/**
* remove element(s) from instance and DOM
* @param {Array or NodeList or Element} elems
*/
proto.remove = function( elems ) {
var removeItems = this.getItems( elems );
this._emitCompleteOnItems( 'remove', removeItems );
// bail if no items to remove
if ( !removeItems || !removeItems.length ) {
return;
}
removeItems.forEach( function( item ) {
item.remove();
// remove item from collection
utils.removeFrom( this.items, item );
}, this );
};
// ----- destroy ----- //
// remove and disable Outlayer instance
proto.destroy = function() {
// clean up dynamic styles
var style = this.element.style;
style.height = '';
style.position = '';
style.width = '';
// destroy items
this.items.forEach( function( item ) {
item.destroy();
});
this.unbindResize();
var id = this.element.outlayerGUID;
delete instances[ id ]; // remove reference to instance by id
delete this.element.outlayerGUID;
// remove data for jQuery
if ( jQuery ) {
jQuery.removeData( this.element, this.constructor.namespace );
}
};
// -------------------------- data -------------------------- //
/**
* get Outlayer instance from element
* @param {Element} elem
* @returns {Outlayer}
*/
Outlayer.data = function( elem ) {
elem = utils.getQueryElement( elem );
var id = elem && elem.outlayerGUID;
return id && instances[ id ];
};
// -------------------------- create Outlayer class -------------------------- //
/**
* create a layout class
* @param {String} namespace
*/
Outlayer.create = function( namespace, options ) {
// sub-class Outlayer
var Layout = subclass( Outlayer );
// apply new options and compatOptions
Layout.defaults = utils.extend( {}, Outlayer.defaults );
utils.extend( Layout.defaults, options );
Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions );
Layout.namespace = namespace;
Layout.data = Outlayer.data;
// sub-class Item
Layout.Item = subclass( Item );
// -------------------------- declarative -------------------------- //
utils.htmlInit( Layout, namespace );
// -------------------------- jQuery bridge -------------------------- //
// make into jQuery plugin
if ( jQuery && jQuery.bridget ) {
jQuery.bridget( namespace, Layout );
}
return Layout;
};
function subclass( Parent ) {
function SubClass() {
Parent.apply( this, arguments );
}
SubClass.prototype = Object.create( Parent.prototype );
SubClass.prototype.constructor = SubClass;
return SubClass;
}
// ----- helpers ----- //
// how many milliseconds are in each unit
var msUnits = {
ms: 1,
s: 1000
};
// munge time-like parameter into millisecond number
// '0.4s' -> 40
function getMilliseconds( time ) {
if ( typeof time == 'number' ) {
return time;
}
var matches = time.match( /(^\d*\.?\d*)(\w*)/ );
var num = matches && matches[1];
var unit = matches && matches[2];
if ( !num.length ) {
return 0;
}
num = parseFloat( num );
var mult = msUnits[ unit ] || 1;
return num * mult;
}
// ----- fin ----- //
// back in global
Outlayer.Item = Item;
return Outlayer;
}));
/***/ },
/***/ 658
(module) {
"use strict";
/* jshint browser:true, node:true */
module.exports = function(global) {
/* Set up a RequestAnimationFrame shim so we can animate efficiently FOR
* GREAT JUSTICE. */
var requestInterval, cancelInterval;
(function() {
var raf = global.requestAnimationFrame ||
global.webkitRequestAnimationFrame ||
global.mozRequestAnimationFrame ||
global.oRequestAnimationFrame ||
global.msRequestAnimationFrame ,
caf = global.cancelAnimationFrame ||
global.webkitCancelAnimationFrame ||
global.mozCancelAnimationFrame ||
global.oCancelAnimationFrame ||
global.msCancelAnimationFrame ;
if(raf && caf) {
requestInterval = function(fn, delay) {
var handle = {value: null};
function loop() {
handle.value = raf(loop);
fn();
}
loop();
return handle;
};
cancelInterval = function(handle) {
caf(handle.value);
};
}
else {
requestInterval = setInterval;
cancelInterval = clearInterval;
}
}());
/* Catmull-rom spline stuffs. */
/*
function upsample(n, spline) {
var polyline = [],
len = spline.length,
bx = spline[0],
by = spline[1],
cx = spline[2],
cy = spline[3],
dx = spline[4],
dy = spline[5],
i, j, ax, ay, px, qx, rx, sx, py, qy, ry, sy, t;
for(i = 6; i !== spline.length; i += 2) {
ax = bx;
bx = cx;
cx = dx;
dx = spline[i ];
px = -0.5 * ax + 1.5 * bx - 1.5 * cx + 0.5 * dx;
qx = ax - 2.5 * bx + 2.0 * cx - 0.5 * dx;
rx = -0.5 * ax + 0.5 * cx ;
sx = bx ;
ay = by;
by = cy;
cy = dy;
dy = spline[i + 1];
py = -0.5 * ay + 1.5 * by - 1.5 * cy + 0.5 * dy;
qy = ay - 2.5 * by + 2.0 * cy - 0.5 * dy;
ry = -0.5 * ay + 0.5 * cy ;
sy = by ;
for(j = 0; j !== n; ++j) {
t = j / n;
polyline.push(
((px * t + qx) * t + rx) * t + sx,
((py * t + qy) * t + ry) * t + sy
);
}
}
polyline.push(
px + qx + rx + sx,
py + qy + ry + sy
);
return polyline;
}
function downsample(n, polyline) {
var len = 0,
i, dx, dy;
for(i = 2; i !== polyline.length; i += 2) {
dx = polyline[i ] - polyline[i - 2];
dy = polyline[i + 1] - polyline[i - 1];
len += Math.sqrt(dx * dx + dy * dy);
}
len /= n;
var small = [],
target = len,
min = 0,
max, t;
small.push(polyline[0], polyline[1]);
for(i = 2; i !== polyline.length; i += 2) {
dx = polyline[i ] - polyline[i - 2];
dy = polyline[i + 1] - polyline[i - 1];
max = min + Math.sqrt(dx * dx + dy * dy);
if(max > target) {
t = (target - min) / (max - min);
small.push(
polyline[i - 2] + dx * t,
polyline[i - 1] + dy * t
);
target += len;
}
min = max;
}
small.push(polyline[polyline.length - 2], polyline[polyline.length - 1]);
return small;
}
*/
/* Define skycon things. */
/* FIXME: I'm *really really* sorry that this code is so gross. Really, I am.
* I'll try to clean it up eventually! Promise! */
var KEYFRAME = 500,
STROKE = 0.08,
TAU = 2.0 * Math.PI,
TWO_OVER_SQRT_2 = 2.0 / Math.sqrt(2);
function circle(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, TAU, false);
ctx.fill();
}
function line(ctx, ax, ay, bx, by) {
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(bx, by);
ctx.stroke();
}
function puff(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var c = Math.cos(t * TAU),
s = Math.sin(t * TAU);
rmax -= rmin;
circle(
ctx,
cx - s * rx,
cy + c * ry + rmax * 0.5,
rmin + (1 - c * 0.5) * rmax
);
}
function puffs(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var i;
for(i = 5; i--; )
puff(ctx, t + i / 5, cx, cy, rx, ry, rmin, rmax);
}
function cloud(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.12,
c = cw * 0.24,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
function sun(ctx, t, cx, cy, cw, s, color) {
t /= 120000;
var a = cw * 0.25 - s * 0.5,
b = cw * 0.32 + s * 0.5,
c = cw * 0.50 - s * 0.5,
i, p, cos, sin;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(cx, cy, a, 0, TAU, false);
ctx.stroke();
for(i = 8; i--; ) {
p = (t + i / 8) * TAU;
cos = Math.cos(p);
sin = Math.sin(p);
line(ctx, cx + cos * b, cy + sin * b, cx + cos * c, cy + sin * c);
}
}
function moon(ctx, t, cx, cy, cw, s, color) {
t /= 15000;
var a = cw * 0.29 - s * 0.5,
b = cw * 0.05,
c = Math.cos(t * TAU),
p = c * TAU / -16;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
cx += c * b;
ctx.beginPath();
ctx.arc(cx, cy, a, p + TAU / 8, p + TAU * 7 / 8, false);
ctx.arc(cx + Math.cos(p) * a * TWO_OVER_SQRT_2, cy + Math.sin(p) * a * TWO_OVER_SQRT_2, a, p + TAU * 5 / 8, p + TAU * 3 / 8, true);
ctx.closePath();
ctx.stroke();
}
function rain(ctx, t, cx, cy, cw, s, color) {
t /= 1350;
var a = cw * 0.16,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.fillStyle = color;
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a;
y = cy + p * p * cw;
ctx.beginPath();
ctx.moveTo(x, y - s * 1.5);
ctx.arc(x, y, s * 0.75, b, c, false);
ctx.fill();
}
}
function sleet(ctx, t, cx, cy, cw, s, color) {
t /= 750;
var a = cw * 0.1875,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = Math.floor(cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a) + 0.5;
y = cy + p * cw;
line(ctx, x, y - s * 1.5, x, y + s * 1.5);
}
}
function snow(ctx, t, cx, cy, cw, s, color) {
t /= 3000;
var a = cw * 0.16,
b = s * 0.75,
u = t * TAU * 0.7,
ux = Math.cos(u) * b,
uy = Math.sin(u) * b,
v = u + TAU / 3,
vx = Math.cos(v) * b,
vy = Math.sin(v) * b,
w = u + TAU * 2 / 3,
wx = Math.cos(w) * b,
wy = Math.sin(w) * b,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + Math.sin((p + i / 4) * TAU) * a;
y = cy + p * cw;
line(ctx, x - ux, y - uy, x + ux, y + uy);
line(ctx, x - vx, y - vy, x + vx, y + vy);
line(ctx, x - wx, y - wy, x + wx, y + wy);
}
}
function fogbank(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.06,
c = cw * 0.21,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
/*
var WIND_PATHS = [
downsample(63, upsample(8, [
-1.00, -0.28,
-0.75, -0.18,
-0.50, 0.12,
-0.20, 0.12,
-0.04, -0.04,
-0.07, -0.18,
-0.19, -0.18,
-0.23, -0.05,
-0.12, 0.11,
0.02, 0.16,
0.20, 0.15,
0.50, 0.07,
0.75, 0.18,
1.00, 0.28
])),
downsample(31, upsample(16, [
-1.00, -0.10,
-0.75, 0.00,
-0.50, 0.10,
-0.25, 0.14,
0.00, 0.10,
0.25, 0.00,
0.50, -0.10,
0.75, -0.14,
1.00, -0.10
]))
];
*/
var WIND_PATHS = [
[
-0.7500, -0.1800, -0.7219, -0.1527, -0.6971, -0.1225,
-0.6739, -0.0910, -0.6516, -0.0588, -0.6298, -0.0262,
-0.6083, 0.0065, -0.5868, 0.0396, -0.5643, 0.0731,
-0.5372, 0.1041, -0.5033, 0.1259, -0.4662, 0.1406,
-0.4275, 0.1493, -0.3881, 0.1530, -0.3487, 0.1526,
-0.3095, 0.1488, -0.2708, 0.1421, -0.2319, 0.1342,
-0.1943, 0.1217, -0.1600, 0.1025, -0.1290, 0.0785,
-0.1012, 0.0509, -0.0764, 0.0206, -0.0547, -0.0120,
-0.0378, -0.0472, -0.0324, -0.0857, -0.0389, -0.1241,
-0.0546, -0.1599, -0.0814, -0.1876, -0.1193, -0.1964,
-0.1582, -0.1935, -0.1931, -0.1769, -0.2157, -0.1453,
-0.2290, -0.1085, -0.2327, -0.0697, -0.2240, -0.0317,
-0.2064, 0.0033, -0.1853, 0.0362, -0.1613, 0.0672,
-0.1350, 0.0961, -0.1051, 0.1213, -0.0706, 0.1397,
-0.0332, 0.1512, 0.0053, 0.1580, 0.0442, 0.1624,
0.0833, 0.1636, 0.1224, 0.1615, 0.1613, 0.1565,
0.1999, 0.1500, 0.2378, 0.1402, 0.2749, 0.1279,
0.3118, 0.1147, 0.3487, 0.1015, 0.3858, 0.0892,
0.4236, 0.0787, 0.4621, 0.0715, 0.5012, 0.0702,
0.5398, 0.0766, 0.5768, 0.0890, 0.6123, 0.1055,
0.6466, 0.1244, 0.6805, 0.1440, 0.7147, 0.1630,
0.7500, 0.1800
],
[
-0.7500, 0.0000, -0.7033, 0.0195, -0.6569, 0.0399,
-0.6104, 0.0600, -0.5634, 0.0789, -0.5155, 0.0954,
-0.4667, 0.1089, -0.4174, 0.1206, -0.3676, 0.1299,
-0.3174, 0.1365, -0.2669, 0.1398, -0.2162, 0.1391,
-0.1658, 0.1347, -0.1157, 0.1271, -0.0661, 0.1169,
-0.0170, 0.1046, 0.0316, 0.0903, 0.0791, 0.0728,
0.1259, 0.0534, 0.1723, 0.0331, 0.2188, 0.0129,
0.2656, -0.0064, 0.3122, -0.0263, 0.3586, -0.0466,
0.4052, -0.0665, 0.4525, -0.0847, 0.5007, -0.1002,
0.5497, -0.1130, 0.5991, -0.1240, 0.6491, -0.1325,
0.6994, -0.1380, 0.7500, -0.1400
]
],
WIND_OFFSETS = [
{start: 0.36, end: 0.11},
{start: 0.56, end: 0.16}
];
function leaf(ctx, t, x, y, cw, s, color) {
var a = cw / 8,
b = a / 3,
c = 2 * b,
d = (t % 1) * TAU,
e = Math.cos(d),
f = Math.sin(d);
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(x , y , a, d , d + Math.PI, false);
ctx.arc(x - b * e, y - b * f, c, d + Math.PI, d , false);
ctx.arc(x + c * e, y + c * f, b, d + Math.PI, d , true );
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
}
function swoosh(ctx, t, cx, cy, cw, s, index, total, color) {
t /= 2500;
var path = WIND_PATHS[index],
a = (t + index - WIND_OFFSETS[index].start) % total,
c = (t + index - WIND_OFFSETS[index].end ) % total,
e = (t + index ) % total,
b, d, f, i;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
if(a < 1) {
ctx.beginPath();
a *= path.length / 2 - 1;
b = Math.floor(a);
a -= b;
b *= 2;
b += 2;
ctx.moveTo(
cx + (path[b - 2] * (1 - a) + path[b ] * a) * cw,
cy + (path[b - 1] * (1 - a) + path[b + 1] * a) * cw
);
if(c < 1) {
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
for(i = b; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
}
else
for(i = b; i !== path.length; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.stroke();
}
else if(c < 1) {
ctx.beginPath();
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
ctx.moveTo(cx + path[0] * cw, cy + path[1] * cw);
for(i = 2; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
ctx.stroke();
}
if(e < 1) {
e *= path.length / 2 - 1;
f = Math.floor(e);
e -= f;
f *= 2;
f += 2;
leaf(
ctx,
t,
cx + (path[f - 2] * (1 - e) + path[f ] * e) * cw,
cy + (path[f - 1] * (1 - e) + path[f + 1] * e) * cw,
cw,
s,
color
);
}
}
var Skycons = function(opts) {
this.list = [];
this.interval = null;
this.color = opts && opts.color ? opts.color : "black";
this.resizeClear = !!(opts && opts.resizeClear);
};
Skycons.CLEAR_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.CLEAR_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.625, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.667, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.CLOUDY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
cloud(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.RAIN = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
rain(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SLEET = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sleet(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SNOW = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
snow(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.WIND = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 0, 2, color);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 1, 2, color);
};
Skycons.FOG = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h),
k = s * STROKE;
fogbank(ctx, t, w * 0.5, h * 0.32, s * 0.75, k, color);
t /= 5000;
var a = Math.cos((t ) * TAU) * s * 0.02,
b = Math.cos((t + 0.25) * TAU) * s * 0.02,
c = Math.cos((t + 0.50) * TAU) * s * 0.02,
d = Math.cos((t + 0.75) * TAU) * s * 0.02,
n = h * 0.936,
e = Math.floor(n - k * 0.5) + 0.5,
f = Math.floor(n - k * 2.5) + 0.5;
ctx.strokeStyle = color;
ctx.lineWidth = k;
ctx.lineCap = "round";
ctx.lineJoin = "round";
line(ctx, a + w * 0.2 + k * 0.5, e, b + w * 0.8 - k * 0.5, e);
line(ctx, c + w * 0.2 + k * 0.5, f, d + w * 0.8 - k * 0.5, f);
};
Skycons.prototype = {
_determineDrawingFunction: function(draw) {
if(typeof draw === "string")
draw = Skycons[draw.toUpperCase().replace(/-/g, "_")] || null;
return draw;
},
add: function(el, draw) {
var obj;
if(typeof el === "string")
el = document.getElementById(el);
// Does nothing if canvas name doesn't exists
if(el === null)
return;
draw = this._determineDrawingFunction(draw);
// Does nothing if the draw function isn't actually a function
if(typeof draw !== "function")
return;
obj = {
element: el,
context: el.getContext("2d"),
drawing: draw
};
this.list.push(obj);
this.draw(obj, KEYFRAME);
},
set: function(el, draw) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list[i].drawing = this._determineDrawingFunction(draw);
this.draw(this.list[i], KEYFRAME);
return;
}
this.add(el, draw);
},
remove: function(el) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list.splice(i, 1);
return;
}
},
draw: function(obj, time) {
var canvas = obj.context.canvas;
if(this.resizeClear)
canvas.width = canvas.width;
else
obj.context.clearRect(0, 0, canvas.width, canvas.height);
obj.drawing(obj.context, time, this.color);
},
play: function() {
var self = this;
this.pause();
this.interval = requestInterval(function() {
var now = Date.now(),
i;
for(i = self.list.length; i--; )
self.draw(self.list[i], now);
}, 1000 / 60);
},
pause: function() {
var i;
if(this.interval) {
cancelInterval(this.interval);
this.interval = null;
}
}
};
return Skycons;
};
/***/ }
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
(() => {
"use strict";
;// ../assets/scripts/utils/dom.js
/**
* Adminator DOM Utility Functions
* Provides jQuery-like functionality using vanilla JavaScript
*
* @module utils/dom
* @example
* import { DOM } from './utils/dom';
*
* // Select elements
* const button = DOM.select('.my-button');
* const items = DOM.selectAll('.list-item');
*
* // Add event listeners
* DOM.on(button, 'click', () => console.log('Clicked!'));
*
* // Manipulate classes
* DOM.addClass(button, 'active');
* DOM.toggleClass(button, 'loading');
*/
/**
* DOM utility object providing jQuery-like methods
* @namespace
*/
const DOM = {
/**
* Select a single element matching the selector
* Replaces jQuery's $('selector').first()
*
* @param {string} selector - CSS selector
* @param {Document|Element} [context=document] - Context to search within
* @returns {Element|null} The matched element or null
*
* @example
* const header = DOM.select('.header');
* const navItem = DOM.select('.nav-item', sidebar);
*/
select: function (selector) {
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
return context.querySelector(selector);
},
/**
* Select all elements matching the selector
* Replaces jQuery's $('selector')
*
* @param {string} selector - CSS selector
* @param {Document|Element} [context=document] - Context to search within
* @returns {Element[]} Array of matched elements
*
* @example
* const buttons = DOM.selectAll('.btn');
* buttons.forEach(btn => DOM.addClass(btn, 'initialized'));
*/
selectAll: function (selector) {
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
return Array.from(context.querySelectorAll(selector));
},
/**
* Check if an element matching the selector exists
*
* @param {string} selector - CSS selector
* @returns {boolean} True if element exists
*
* @example
* if (DOM.exists('.sidebar')) {
* initSidebar();
* }
*/
exists: selector => {
return document.querySelector(selector) !== null;
},
/**
* Add an event listener to an element
* Replaces jQuery's $.on()
*
* @param {Element|string} element - Element or selector
* @param {string} event - Event name (e.g., 'click', 'change')
* @param {Function} handler - Event handler function
* @param {Object} [options={}] - addEventListener options
*
* @example
* DOM.on('.btn', 'click', handleClick);
* DOM.on(button, 'click', handleClick, { once: true });
*/
on: function (element, event, handler) {
let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.addEventListener(event, handler, options);
}
},
/**
* Remove an event listener from an element
* Replaces jQuery's $.off()
*
* @param {Element|string} element - Element or selector
* @param {string} event - Event name
* @param {Function} handler - Event handler to remove
*
* @example
* DOM.off(button, 'click', handleClick);
*/
off: (element, event, handler) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.removeEventListener(event, handler);
}
},
/**
* Add a class to an element
* Replaces jQuery's $.addClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to add
*
* @example
* DOM.addClass('.menu', 'open');
*/
addClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.add(className);
}
},
/**
* Remove a class from an element
* Replaces jQuery's $.removeClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to remove
*
* @example
* DOM.removeClass('.menu', 'open');
*/
removeClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.remove(className);
}
},
/**
* Toggle a class on an element
* Replaces jQuery's $.toggleClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to toggle
*
* @example
* DOM.toggleClass('.dropdown', 'show');
*/
toggleClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.toggle(className);
}
},
/**
* Check if an element has a class
* Replaces jQuery's $.hasClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to check
* @returns {boolean} True if element has the class
*
* @example
* if (DOM.hasClass('.menu', 'open')) {
* closeMenu();
* }
*/
hasClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
return element ? element.classList.contains(className) : false;
},
/**
* Get or set an attribute on an element
* Replaces jQuery's $.attr()
*
* @param {Element|string} element - Element or selector
* @param {string} name - Attribute name
* @param {string} [value] - Value to set (omit to get)
* @returns {string|Element|null} Attribute value when getting, element when setting
*
* @example
* // Get attribute
* const href = DOM.attr(link, 'href');
*
* // Set attribute
* DOM.attr(link, 'href', '/new-page');
*/
attr: (element, name, value) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (value === undefined) {
return element.getAttribute(name);
} else {
element.setAttribute(name, value);
return element;
}
},
/**
* Get or set a data attribute on an element
* Replaces jQuery's $.data()
*
* @param {Element|string} element - Element or selector
* @param {string} name - Data attribute name (without 'data-' prefix)
* @param {string} [value] - Value to set (omit to get)
* @returns {string|Element|null} Data value when getting, element when setting
*
* @example
* // Get data attribute
* const id = DOM.data(row, 'id'); // Gets data-id
*
* // Set data attribute
* DOM.data(row, 'id', '123');
*/
data: (element, name, value) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
const dataName = `data-${name}`;
if (value === undefined) {
return element.getAttribute(dataName);
} else {
element.setAttribute(dataName, value);
return element;
}
},
/**
* Get or set text content of an element
* Replaces jQuery's $.text()
*
* @param {Element|string} element - Element or selector
* @param {string} [content] - Text to set (omit to get)
* @returns {string|Element|null} Text content when getting, element when setting
*
* @example
* const text = DOM.text('.title');
* DOM.text('.title', 'New Title');
*/
text: (element, content) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (content === undefined) {
return element.textContent;
} else {
element.textContent = content;
return element;
}
},
/**
* Get or set HTML content of an element
* Replaces jQuery's $.html()
*
* @param {Element|string} element - Element or selector
* @param {string} [content] - HTML to set (omit to get)
* @returns {string|Element|null} HTML content when getting, element when setting
*
* @example
* const html = DOM.html('.container');
* DOM.html('.container', '<p>New content</p>');
*/
html: (element, content) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (content === undefined) {
return element.innerHTML;
} else {
element.innerHTML = content;
return element;
}
},
/**
* Hide an element
* Replaces jQuery's $.hide()
*
* @param {Element|string} element - Element or selector
*
* @example
* DOM.hide('.modal');
*/
hide: element => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.style.display = 'none';
}
},
/**
* Show an element
* Replaces jQuery's $.show()
*
* @param {Element|string} element - Element or selector
* @param {string} [display='block'] - Display value to use
*
* @example
* DOM.show('.modal');
* DOM.show('.flex-item', 'flex');
*/
show: function (element) {
let display = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'block';
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.style.display = display;
}
},
/**
* Toggle element visibility
* Replaces jQuery's $.toggle()
*
* @param {Element|string} element - Element or selector
* @param {string} [display='block'] - Display value when showing
*
* @example
* DOM.toggle('.menu');
*/
toggle: function (element) {
let display = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'block';
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
if (element.style.display === 'none') {
element.style.display = display;
} else {
element.style.display = 'none';
}
}
},
/**
* Animate element sliding up (collapsing)
* Replaces jQuery's $.slideUp()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise<void>} Resolves when animation completes
*
* @example
* await DOM.slideUp('.panel');
*/
slideUp: function (element) {
let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300;
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise(resolve => {
const height = element.scrollHeight;
element.style.height = `${height}px`;
element.style.overflow = 'hidden';
element.animate([{
height: `${height}px`
}, {
height: '0px'
}], {
duration,
easing: 'ease-in-out'
}).onfinish = () => {
element.style.display = 'none';
element.style.height = '';
element.style.overflow = '';
resolve();
};
});
},
/**
* Animate element sliding down (expanding)
* Replaces jQuery's $.slideDown()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise<void>} Resolves when animation completes
*
* @example
* await DOM.slideDown('.panel');
*/
slideDown: function (element) {
let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300;
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise(resolve => {
element.style.display = 'block';
element.style.height = '0px';
element.style.overflow = 'hidden';
const height = element.scrollHeight;
element.animate([{
height: '0px'
}, {
height: `${height}px`
}], {
duration,
easing: 'ease-in-out'
}).onfinish = () => {
element.style.height = 'auto';
element.style.overflow = 'visible';
resolve();
};
});
},
/**
* Animate element fading in
* Replaces jQuery's $.fadeIn()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise<void>} Resolves when animation completes
*
* @example
* await DOM.fadeIn('.modal');
*/
fadeIn: function (element) {
let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300;
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise(resolve => {
element.style.opacity = '0';
element.style.display = 'block';
element.animate([{
opacity: 0
}, {
opacity: 1
}], {
duration,
easing: 'ease-in-out'
}).onfinish = () => {
element.style.opacity = '';
resolve();
};
});
},
/**
* Animate element fading out
* Replaces jQuery's $.fadeOut()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise<void>} Resolves when animation completes
*
* @example
* await DOM.fadeOut('.modal');
*/
fadeOut: function (element) {
let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300;
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise(resolve => {
element.animate([{
opacity: 1
}, {
opacity: 0
}], {
duration,
easing: 'ease-in-out'
}).onfinish = () => {
element.style.display = 'none';
element.style.opacity = '';
resolve();
};
});
},
/**
* Get element dimensions and position relative to viewport
*
* @param {Element|string} element - Element or selector
* @returns {Object|null} Dimensions object or null
* @property {number} width - Element width
* @property {number} height - Element height
* @property {number} top - Distance from viewport top
* @property {number} left - Distance from viewport left
* @property {number} bottom - Distance from viewport bottom
* @property {number} right - Distance from viewport right
*
* @example
* const { width, height, top, left } = DOM.dimensions('.card');
*/
dimensions: element => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left,
bottom: rect.bottom,
right: rect.right
};
},
/**
* Execute callback when DOM is ready
* Replaces jQuery's $(document).ready()
*
* @param {Function} callback - Function to execute when DOM is ready
*
* @example
* DOM.ready(() => {
* initApp();
* });
*/
ready: callback => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback);
} else {
callback();
}
},
/**
* Create an element with optional attributes and children
*
* @param {string} tag - HTML tag name
* @param {Object} [attrs={}] - Attributes to set
* @param {Array<Element|string>} [children=[]] - Child elements or text
* @returns {Element} The created element
*
* @example
* const button = DOM.create('button', { class: 'btn', type: 'submit' }, ['Submit']);
*/
create: function (tag) {
let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let children = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
const el = document.createElement(tag);
Object.entries(attrs).forEach(_ref => {
let [key, value] = _ref;
if (key === 'class') {
el.className = value;
} else if (key.startsWith('data-')) {
el.setAttribute(key, value);
} else {
el[key] = value;
}
});
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else if (child instanceof Element) {
el.appendChild(child);
}
});
return el;
},
/**
* Find the closest ancestor matching a selector
*
* @param {Element|string} element - Element or selector
* @param {string} selector - Selector to match
* @returns {Element|null} Closest matching ancestor or null
*
* @example
* const form = DOM.closest(input, 'form');
*/
closest: (element, selector) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
return element ? element.closest(selector) : null;
}
};
/* harmony default export */ const dom = ((/* unused pure expression or super */ null && (DOM)));
// EXTERNAL MODULE: ../../node_modules/dayjs/dayjs.min.js
var dayjs_min = __webpack_require__(464);
var dayjs_min_default = /*#__PURE__*/__webpack_require__.n(dayjs_min);
// EXTERNAL MODULE: ../../node_modules/dayjs/plugin/utc.js
var utc = __webpack_require__(657);
var utc_default = /*#__PURE__*/__webpack_require__.n(utc);
// EXTERNAL MODULE: ../../node_modules/dayjs/plugin/timezone.js
var timezone = __webpack_require__(168);
var timezone_default = /*#__PURE__*/__webpack_require__.n(timezone);
// EXTERNAL MODULE: ../../node_modules/dayjs/plugin/relativeTime.js
var relativeTime = __webpack_require__(562);
var relativeTime_default = /*#__PURE__*/__webpack_require__.n(relativeTime);
// EXTERNAL MODULE: ../../node_modules/dayjs/plugin/customParseFormat.js
var customParseFormat = __webpack_require__(630);
var customParseFormat_default = /*#__PURE__*/__webpack_require__.n(customParseFormat);
// EXTERNAL MODULE: ../../node_modules/dayjs/plugin/advancedFormat.js
var advancedFormat = __webpack_require__(258);
var advancedFormat_default = /*#__PURE__*/__webpack_require__.n(advancedFormat);
;// ../assets/scripts/utils/date.js
/**
* Modern Date Utilities
* Using Day.js (2KB) instead of Moment.js (67KB) - 97% size reduction
* Provides consistent date formatting and manipulation across the application
*/
// Enable Day.js plugins
dayjs_min_default().extend((utc_default()));
dayjs_min_default().extend((timezone_default()));
dayjs_min_default().extend((relativeTime_default()));
dayjs_min_default().extend((customParseFormat_default()));
dayjs_min_default().extend((advancedFormat_default()));
const DateUtils = {
/**
* Get current date/time
*/
now: () => dayjs_min_default()(),
/**
* Parse date from string or Date object
*/
parse: function (input) {
let format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return format ? dayjs_min_default()(input, format) : dayjs_min_default()(input);
},
/**
* Format date for display
*/
format: function (date) {
let format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'YYYY-MM-DD';
return dayjs_min_default()(date).format(format);
},
/**
* Common date formatting presets
*/
formatters: {
// Dashboard display formats
shortDate: date => dayjs_min_default()(date).format('MMM DD, YYYY'),
longDate: date => dayjs_min_default()(date).format('MMMM DD, YYYY'),
dateTime: date => dayjs_min_default()(date).format('MMM DD, YYYY h:mm A'),
// Calendar formats
calendarDate: date => dayjs_min_default()(date).format('YYYY-MM-DD'),
calendarDateTime: date => dayjs_min_default()(date).format('YYYY-MM-DD HH:mm:ss'),
// Form input formats
inputDate: date => dayjs_min_default()(date).format('YYYY-MM-DD'),
inputDateTime: date => dayjs_min_default()(date).format('YYYY-MM-DDTHH:mm'),
// Display formats
timeOnly: date => dayjs_min_default()(date).format('h:mm A'),
monthYear: date => dayjs_min_default()(date).format('MMMM YYYY'),
dayMonth: date => dayjs_min_default()(date).format('DD MMM'),
// Relative time
relative: date => dayjs_min_default()(date).fromNow(),
relativeCalendar: date => {
const now = dayjs_min_default()();
const target = dayjs_min_default()(date);
const diffDays = now.diff(target, 'day');
if (diffDays === 0) return 'Today';
if (diffDays === 1) return 'Yesterday';
if (diffDays === -1) return 'Tomorrow';
if (diffDays > 1 && diffDays < 7) return `${diffDays} days ago`;
if (diffDays < -1 && diffDays > -7) return `In ${Math.abs(diffDays)} days`;
return target.format('MMM DD, YYYY');
}
},
/**
* Date manipulation
*/
add: (date, amount, unit) => dayjs_min_default()(date).add(amount, unit),
subtract: (date, amount, unit) => dayjs_min_default()(date).subtract(amount, unit),
startOf: (date, unit) => dayjs_min_default()(date).startOf(unit),
endOf: (date, unit) => dayjs_min_default()(date).endOf(unit),
/**
* Date comparison
*/
isBefore: (date1, date2) => dayjs_min_default()(date1).isBefore(dayjs_min_default()(date2)),
isAfter: (date1, date2) => dayjs_min_default()(date1).isAfter(dayjs_min_default()(date2)),
isSame: function (date1, date2) {
let unit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'day';
return dayjs_min_default()(date1).isSame(dayjs_min_default()(date2), unit);
},
isBetween: (date, start, end) => dayjs_min_default()(date).isBetween(dayjs_min_default()(start), dayjs_min_default()(end)),
/**
* Date validation
*/
isValid: date => dayjs_min_default()(date).isValid(),
/**
* Timezone utilities
*/
timezone: {
convert: (date, tz) => dayjs_min_default()(date).tz(tz),
utc: date => dayjs_min_default()(date).utc(),
local: date => dayjs_min_default()(date).local(),
guess: () => dayjs_min_default().tz.guess()
},
/**
* Calendar utilities
*/
calendar: {
// Get calendar month data for building calendar views
getMonthData: function () {
let date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
const target = date ? dayjs_min_default()(date) : dayjs_min_default()();
const startOfMonth = target.startOf('month');
const endOfMonth = target.endOf('month');
const startOfCalendar = startOfMonth.startOf('week');
const endOfCalendar = endOfMonth.endOf('week');
const days = [];
let current = startOfCalendar;
while (current.isBefore(endOfCalendar) || current.isSame(endOfCalendar, 'day')) {
days.push({
date: current.format('YYYY-MM-DD'),
day: current.date(),
isCurrentMonth: current.isSame(target, 'month'),
isToday: current.isSame(dayjs_min_default()(), 'day'),
dayjs: current.clone()
});
current = current.add(1, 'day');
}
return {
month: target.format('MMMM YYYY'),
year: target.year(),
monthIndex: target.month(),
days
};
},
// Get week data
getWeekData: function () {
let date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
const target = date ? dayjs_min_default()(date) : dayjs_min_default()();
const startOfWeek = target.startOf('week');
const endOfWeek = target.endOf('week');
const days = [];
let current = startOfWeek;
while (current.isBefore(endOfWeek) || current.isSame(endOfWeek, 'day')) {
days.push({
date: current.format('YYYY-MM-DD'),
day: current.date(),
dayName: current.format('dddd'),
shortDayName: current.format('ddd'),
isToday: current.isSame(dayjs_min_default()(), 'day'),
dayjs: current.clone()
});
current = current.add(1, 'day');
}
return {
weekStart: startOfWeek.format('MMM DD'),
weekEnd: endOfWeek.format('MMM DD, YYYY'),
days
};
}
},
/**
* Form utilities
*/
form: {
// Convert date to HTML5 input format
toInputValue: date => dayjs_min_default()(date).format('YYYY-MM-DD'),
toDateTimeInputValue: date => dayjs_min_default()(date).format('YYYY-MM-DDTHH:mm'),
// Parse from HTML5 input
fromInputValue: value => dayjs_min_default()(value),
// Validate date input
validateDateInput: value => {
const parsed = dayjs_min_default()(value);
return parsed.isValid() && value.length >= 8; // Basic validation
}
},
/**
* Chart/Data utilities
*/
charts: {
// Generate date ranges for charts
generateDateRange: function (start, end) {
let interval = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'day';
const dates = [];
let current = dayjs_min_default()(start);
const endDate = dayjs_min_default()(end);
while (current.isBefore(endDate) || current.isSame(endDate, interval)) {
dates.push({
date: current.format('YYYY-MM-DD'),
label: current.format('MMM DD'),
value: current.toISOString(),
dayjs: current.clone()
});
current = current.add(1, interval);
}
return dates;
},
// Get common chart date labels
getChartLabels: function () {
let period = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'week';
const now = dayjs_min_default()();
switch (period) {
case 'week':
return Array.from({
length: 7
}, (_, i) => now.subtract(6 - i, 'day').format('ddd'));
case 'month':
return Array.from({
length: 30
}, (_, i) => now.subtract(29 - i, 'day').format('DD'));
case 'year':
return Array.from({
length: 12
}, (_, i) => now.subtract(11 - i, 'month').format('MMM'));
default:
return [];
}
}
}
};
// Export dayjs instance for direct use when needed
// Default export
/* harmony default export */ const date = (DateUtils);
;// ../assets/scripts/utils/theme.js
/**
* Adminator Theme Manager
* Handles light/dark mode switching with localStorage persistence
*
* @module utils/theme
* @example
* // Get current theme
* const current = Theme.current(); // 'light' or 'dark'
*
* // Toggle theme
* Theme.toggle();
*
* // Apply specific theme
* Theme.apply('dark');
*
* // Listen for theme changes
* window.addEventListener('adminator:themeChanged', (e) => {
* console.log('New theme:', e.detail.theme);
* });
*/
/* global Chart */
/** @constant {string} Storage key for theme preference */
const THEME_KEY = 'adminator-theme';
/** @constant {string[]} Valid theme values */
const VALID_THEMES = ['light', 'dark'];
/**
* Safe localStorage wrapper
* Handles cases where localStorage is unavailable (private browsing, etc.)
* @private
*/
const Storage = {
/**
* Get item from localStorage
* @param {string} key - Storage key
* @returns {string|null} Stored value or null
*/
get(key) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
},
/**
* Set item in localStorage
* @param {string} key - Storage key
* @param {string} value - Value to store
* @returns {boolean} Success status
*/
set(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
}
};
/**
* Theme Manager
* @namespace
*/
const Theme = {
/**
* Apply a theme to the document
* Updates Chart.js defaults if available
*
* @param {('light'|'dark')} theme - Theme to apply
* @fires adminator:themeChanged
* @returns {boolean} Success status
*
* @example
* Theme.apply('dark');
*/
apply(theme) {
// Validate theme
if (!VALID_THEMES.includes(theme)) {
console.warn(`[Adminator] Invalid theme "${theme}". Using "light".`);
theme = 'light';
}
// Apply to document
document.documentElement.setAttribute('data-theme', theme);
// Update Chart.js defaults if available
if (window.Chart && Chart.defaults) {
const isDark = theme === 'dark';
const textColor = isDark ? '#FFFFFF' : '#212529';
const mutedColor = isDark ? '#D1D5DB' : '#6C757D';
const borderColor = isDark ? '#374151' : '#E2E5E8';
const gridColor = isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)';
const tooltipBg = isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)';
// Set global defaults
Chart.defaults.color = textColor;
Chart.defaults.borderColor = borderColor;
Chart.defaults.backgroundColor = tooltipBg;
// Set plugin defaults
Chart.defaults.plugins.legend.labels.color = textColor;
Chart.defaults.plugins.tooltip.backgroundColor = tooltipBg;
Chart.defaults.plugins.tooltip.titleColor = textColor;
Chart.defaults.plugins.tooltip.bodyColor = textColor;
Chart.defaults.plugins.tooltip.borderColor = borderColor;
// Set scale defaults
const scales = ['category', 'linear', 'logarithmic', 'time', 'radialLinear'];
scales.forEach(scale => {
if (Chart.defaults.scales[scale]) {
Chart.defaults.scales[scale].ticks.color = mutedColor;
Chart.defaults.scales[scale].grid.color = gridColor;
}
});
// RadialLinear specific
if (Chart.defaults.scales.radialLinear) {
Chart.defaults.scales.radialLinear.pointLabels.color = mutedColor;
Chart.defaults.scales.radialLinear.angleLines.color = gridColor;
}
}
// Persist to storage
Storage.set(THEME_KEY, theme);
// Update toggle accessibility state if exists
const toggle = document.getElementById('theme-toggle');
if (toggle) {
toggle.setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false');
}
// Dispatch event for other components
window.dispatchEvent(new CustomEvent('adminator:themeChanged', {
detail: {
theme
}
}));
return true;
},
/**
* Toggle between light and dark themes
*
* @returns {('light'|'dark')} The new theme
*
* @example
* const newTheme = Theme.toggle();
* console.log('Switched to:', newTheme);
*/
toggle() {
const next = this.current() === 'dark' ? 'light' : 'dark';
this.apply(next);
return next;
},
/**
* Get the current theme
*
* @returns {('light'|'dark')} Current theme
*
* @example
* if (Theme.current() === 'dark') {
* // Dark mode specific logic
* }
*/
current() {
const stored = Storage.get(THEME_KEY);
return VALID_THEMES.includes(stored) ? stored : 'light';
},
/**
* Initialize the theme system
* Detects OS preference on first visit, otherwise uses stored preference
*
* @returns {('light'|'dark')} The applied theme
*
* @example
* // Call once on app initialization
* Theme.init();
*/
init() {
const stored = Storage.get(THEME_KEY);
if (!stored) {
// First visit - detect OS preference
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = prefersDark ? 'dark' : 'light';
this.apply(theme);
return theme;
}
// Use stored preference
this.apply(this.current());
return this.current();
},
/**
* Get a CSS variable value from the document
*
* @param {string} varName - CSS variable name (with or without --)
* @returns {string} The CSS variable value
*
* @example
* const bgColor = Theme.getCSSVar('--c-bkg-body');
*/
getCSSVar(varName) {
const name = varName.startsWith('--') ? varName : `--${varName}`;
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
},
/**
* Get theme-aware colors for vector maps
*
* @returns {Object} Vector map color configuration
* @property {string} backgroundColor - Map background color
* @property {string} borderColor - Border color
* @property {string} regionColor - Default region fill color
* @property {string} markerFill - Marker fill color
* @property {string} markerStroke - Marker stroke color
* @property {string} hoverColor - Region hover color
* @property {string} selectedColor - Selected region color
* @property {string} scaleStart - Scale gradient start
* @property {string} scaleEnd - Scale gradient end
*/
getVectorMapColors() {
return {
backgroundColor: this.getCSSVar('--vmap-bg-color'),
borderColor: this.getCSSVar('--vmap-border-color'),
regionColor: this.getCSSVar('--vmap-region-color'),
markerFill: this.getCSSVar('--vmap-marker-fill'),
markerStroke: this.getCSSVar('--vmap-marker-stroke'),
hoverColor: this.getCSSVar('--vmap-hover-color'),
selectedColor: this.getCSSVar('--vmap-selected-color'),
scaleStart: this.getCSSVar('--vmap-scale-start'),
scaleEnd: this.getCSSVar('--vmap-scale-end'),
scaleLight: this.getCSSVar('--vmap-scale-light'),
scaleDark: this.getCSSVar('--vmap-scale-dark')
};
},
/**
* Get theme-aware colors for sparkline charts
*
* @returns {Object} Sparkline color configuration
*/
getSparklineColors() {
return {
success: this.getCSSVar('--sparkline-success'),
purple: this.getCSSVar('--sparkline-purple'),
info: this.getCSSVar('--sparkline-info'),
danger: this.getCSSVar('--sparkline-danger'),
light: this.getCSSVar('--sparkline-light')
};
},
/**
* Get theme-aware colors for Chart.js charts
*
* @returns {Object} Chart color configuration
* @property {string} textColor - Main text color
* @property {string} mutedColor - Muted/secondary text color
* @property {string} borderColor - Border color
* @property {string} gridColor - Grid line color
* @property {string} tooltipBg - Tooltip background color
*/
getChartColors() {
const isDark = this.current() === 'dark';
return {
textColor: isDark ? '#FFFFFF' : '#212529',
mutedColor: isDark ? '#D1D5DB' : '#6C757D',
borderColor: isDark ? '#374151' : '#E2E5E8',
gridColor: isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)',
tooltipBg: isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)'
};
},
/**
* Check if dark mode is currently active
*
* @returns {boolean} True if dark mode is active
*
* @example
* if (Theme.isDark()) {
* // Apply dark-specific styles
* }
*/
isDark() {
return this.current() === 'dark';
},
/**
* Check if light mode is currently active
*
* @returns {boolean} True if light mode is active
*/
isLight() {
return this.current() === 'light';
}
};
/* harmony default export */ const theme = (Theme);
;// ../assets/scripts/utils/events.js
/**
* Adminator Event Utilities
* Provides efficient event handling with delegation and cleanup
*
* @module utils/events
*/
/**
* Store for event handlers to enable proper cleanup
* @type {WeakMap<Element, Map<string, Set<Function>>>}
*/
const handlerRegistry = new WeakMap();
/**
* Store for AbortControllers to enable easy cleanup
* @type {WeakMap<Element, AbortController>}
*/
const controllerRegistry = new WeakMap();
/**
* Event utilities namespace
* @namespace
*/
const Events = {
/**
* Add an event listener with automatic cleanup support
* Uses AbortController for efficient removal
*
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @param {Object} [options={}] - Event listener options
* @returns {Function} Cleanup function to remove the listener
*
* @example
* const cleanup = Events.on(button, 'click', handleClick);
* // Later: cleanup() to remove
*/
on(element, event, handler) {
let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
if (!element) return () => {};
// Get or create AbortController for this element
let controller = controllerRegistry.get(element);
if (!controller) {
controller = new AbortController();
controllerRegistry.set(element, controller);
}
// Register handler
if (!handlerRegistry.has(element)) {
handlerRegistry.set(element, new Map());
}
const elementHandlers = handlerRegistry.get(element);
if (!elementHandlers.has(event)) {
elementHandlers.set(event, new Set());
}
elementHandlers.get(event).add(handler);
// Add listener with abort signal
element.addEventListener(event, handler, {
...options,
signal: controller.signal
});
// Return cleanup function
return () => {
var _handlerRegistry$get;
element.removeEventListener(event, handler, options);
const handlers = (_handlerRegistry$get = handlerRegistry.get(element)) === null || _handlerRegistry$get === void 0 ? void 0 : _handlerRegistry$get.get(event);
if (handlers) {
handlers.delete(handler);
}
};
},
/**
* Remove all event listeners from an element
*
* @param {Element} element - Target element
*
* @example
* Events.off(element); // Removes all listeners
*/
off(element) {
if (!element) return;
const controller = controllerRegistry.get(element);
if (controller) {
controller.abort();
controllerRegistry.delete(element);
}
handlerRegistry.delete(element);
},
/**
* Add event delegation - listen on parent for events from children
* More efficient than adding listeners to many elements
*
* @param {Element} parent - Parent element to listen on
* @param {string} event - Event type
* @param {string} selector - CSS selector for target elements
* @param {Function} handler - Event handler (receives event and matched element)
* @param {Object} [options={}] - Event listener options
* @returns {Function} Cleanup function
*
* @example
* // Instead of adding click to every .btn
* Events.delegate(container, 'click', '.btn', (e, btn) => {
* console.log('Button clicked:', btn);
* });
*/
delegate(parent, event, selector, handler) {
let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
if (!parent) return () => {};
const delegatedHandler = e => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e, target);
}
};
return this.on(parent, event, delegatedHandler, options);
},
/**
* Add a one-time event listener
*
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @returns {Function} Cleanup function
*
* @example
* Events.once(button, 'click', handleFirstClick);
*/
once(element, event, handler) {
return this.on(element, event, handler, {
once: true
});
},
/**
* Create a debounced event handler
*
* @param {Function} handler - Original handler
* @param {number} [delay=250] - Debounce delay in ms
* @returns {Function} Debounced handler
*
* @example
* const debouncedResize = Events.debounce(handleResize, 200);
* window.addEventListener('resize', debouncedResize);
*/
debounce(handler) {
let delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250;
let timeoutId;
return function debounced() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
clearTimeout(timeoutId);
timeoutId = setTimeout(() => handler.apply(this, args), delay);
};
},
/**
* Create a throttled event handler
*
* @param {Function} handler - Original handler
* @param {number} [limit=250] - Throttle limit in ms
* @returns {Function} Throttled handler
*
* @example
* const throttledScroll = Events.throttle(handleScroll, 100);
* window.addEventListener('scroll', throttledScroll);
*/
throttle(handler) {
let limit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250;
let inThrottle;
return function throttled() {
if (!inThrottle) {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
handler.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
/**
* Dispatch a custom event
*
* @param {Element|Window} target - Target to dispatch on
* @param {string} eventName - Event name
* @param {Object} [detail={}] - Event detail data
* @param {Object} [options={}] - Event options (bubbles, cancelable)
* @returns {boolean} Whether the event was not cancelled
*
* @example
* Events.emit(element, 'custom:event', { data: 'value' });
*/
emit(target, eventName) {
let detail = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
const event = new CustomEvent(eventName, {
detail,
bubbles: options.bubbles ?? true,
cancelable: options.cancelable ?? true
});
return target.dispatchEvent(event);
}
};
/* harmony default export */ const events = (Events);
;// ../assets/scripts/utils/performance.js
/**
* Adminator Performance Utilities
* Provides ResizeObserver, IntersectionObserver, and lazy loading utilities
*
* @module utils/performance
*/
/**
* Store for ResizeObserver instances
* @type {WeakMap<Element, Function>}
*/
const resizeCallbacks = new WeakMap();
/**
* Shared ResizeObserver instance for efficiency
* @type {ResizeObserver|null}
*/
let sharedResizeObserver = null;
/**
* Store for IntersectionObserver callbacks
* @type {WeakMap<Element, Function>}
*/
const intersectionCallbacks = new WeakMap();
/**
* Map of IntersectionObservers by threshold
* @type {Map<string, IntersectionObserver>}
*/
const intersectionObservers = new Map();
/**
* Performance utilities namespace
* @namespace
*/
const Performance = {
/**
* Observe element resize events efficiently
* Uses shared ResizeObserver for better performance
*
* @param {Element} element - Element to observe
* @param {Function} callback - Callback receiving { width, height, entry }
* @returns {Function} Cleanup function to stop observing
*
* @example
* const unobserve = Performance.onResize(chart, ({ width, height }) => {
* chart.resize(width, height);
* });
*/
onResize(element, callback) {
if (!element || typeof callback !== 'function') {
return () => {};
}
// Create shared observer if needed
if (!sharedResizeObserver) {
sharedResizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const cb = resizeCallbacks.get(entry.target);
if (cb) {
const {
width,
height
} = entry.contentRect;
cb({
width,
height,
entry
});
}
}
});
}
// Store callback and observe
resizeCallbacks.set(element, callback);
sharedResizeObserver.observe(element);
// Return cleanup function
return () => {
var _sharedResizeObserver;
resizeCallbacks.delete(element);
(_sharedResizeObserver = sharedResizeObserver) === null || _sharedResizeObserver === void 0 || _sharedResizeObserver.unobserve(element);
};
},
/**
* Observe when element enters/exits viewport
* Useful for lazy loading and animations
*
* @param {Element} element - Element to observe
* @param {Function} callback - Callback receiving { isIntersecting, entry }
* @param {Object} [options={}] - IntersectionObserver options
* @param {number} [options.threshold=0] - Visibility threshold (0-1)
* @param {string} [options.rootMargin='0px'] - Root margin
* @returns {Function} Cleanup function to stop observing
*
* @example
* const unobserve = Performance.onVisible(element, ({ isIntersecting }) => {
* if (isIntersecting) {
* loadContent();
* unobserve(); // Stop after first trigger
* }
* });
*/
onVisible(element, callback) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
if (!element || typeof callback !== 'function') {
return () => {};
}
const {
threshold = 0,
rootMargin = '0px'
} = options;
const key = `${threshold}-${rootMargin}`;
// Get or create observer for this threshold
if (!intersectionObservers.has(key)) {
const observer = new IntersectionObserver(entries => {
for (const entry of entries) {
const cb = intersectionCallbacks.get(entry.target);
if (cb) {
cb({
isIntersecting: entry.isIntersecting,
ratio: entry.intersectionRatio,
entry
});
}
}
}, {
threshold,
rootMargin
});
intersectionObservers.set(key, observer);
}
const observer = intersectionObservers.get(key);
// Store callback and observe
intersectionCallbacks.set(element, callback);
observer.observe(element);
// Return cleanup function
return () => {
intersectionCallbacks.delete(element);
observer.unobserve(element);
};
},
/**
* Lazy load an element when it becomes visible
* Automatically handles cleanup after loading
*
* @param {Element} element - Element to lazy load
* @param {Function} loadFn - Function to call when visible
* @param {Object} [options={}] - Options
* @param {string} [options.rootMargin='100px'] - Preload margin
* @returns {Function} Cleanup function
*
* @example
* Performance.lazyLoad(chartContainer, () => {
* initializeChart(chartContainer);
* });
*/
lazyLoad(element, loadFn) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const {
rootMargin = '100px'
} = options;
let loaded = false;
const unobserve = this.onVisible(element, _ref => {
let {
isIntersecting
} = _ref;
if (isIntersecting && !loaded) {
loaded = true;
loadFn();
unobserve();
}
}, {
rootMargin
});
return unobserve;
},
/**
* Batch DOM reads and writes to prevent layout thrashing
*
* @param {Function} readFn - Function that reads from DOM
* @param {Function} writeFn - Function that writes to DOM
*
* @example
* Performance.batch(
* () => element.offsetHeight, // Read
* (height) => element.style.minHeight = height + 'px' // Write
* );
*/
batch(readFn, writeFn) {
// Use requestAnimationFrame for batching
requestAnimationFrame(() => {
const value = readFn();
requestAnimationFrame(() => {
writeFn(value);
});
});
},
/**
* Execute callback on next animation frame
*
* @param {Function} callback - Function to execute
* @returns {number} Request ID for cancellation
*
* @example
* const id = Performance.nextFrame(() => updateUI());
* // Cancel: cancelAnimationFrame(id);
*/
nextFrame(callback) {
return requestAnimationFrame(callback);
},
/**
* Execute callback when browser is idle
* Falls back to setTimeout if requestIdleCallback not available
*
* @param {Function} callback - Function to execute
* @param {Object} [options={}] - Options
* @param {number} [options.timeout=1000] - Maximum wait time
* @returns {number} Request ID for cancellation
*
* @example
* Performance.whenIdle(() => {
* // Non-critical work
* preloadNextPage();
* });
*/
whenIdle(callback) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
timeout = 1000
} = options;
if ('requestIdleCallback' in window) {
return requestIdleCallback(callback, {
timeout
});
}
// Fallback for Safari
return setTimeout(callback, 1);
},
/**
* Cancel an idle callback
*
* @param {number} id - Request ID from whenIdle
*/
cancelIdle(id) {
if ('cancelIdleCallback' in window) {
cancelIdleCallback(id);
} else {
clearTimeout(id);
}
},
/**
* Preload a resource (image, script, etc.)
*
* @param {string} url - URL to preload
* @param {string} [as='image'] - Resource type (image, script, style, font)
* @returns {Promise<void>} Resolves when loaded
*
* @example
* await Performance.preload('/images/hero.jpg', 'image');
*/
preload(url) {
let as = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'image';
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = as;
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
},
/**
* Measure execution time of a function
*
* @param {string} label - Label for the measurement
* @param {Function} fn - Function to measure
* @returns {*} Return value of the function
*
* @example
* const result = Performance.measure('render', () => renderChart());
*/
measure(label, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
if (false) // removed by dead control flow
{}
return result;
},
/**
* Cleanup all observers
* Call this when destroying the app
*/
cleanup() {
// Cleanup resize observer
if (sharedResizeObserver) {
sharedResizeObserver.disconnect();
sharedResizeObserver = null;
}
// Cleanup intersection observers
for (const observer of intersectionObservers.values()) {
observer.disconnect();
}
intersectionObservers.clear();
}
};
/* harmony default export */ const utils_performance = (Performance);
;// ../assets/scripts/utils/logger.js
/**
* Adminator Logger Utility
* Development-only logging utility for debugging
*
* @module utils/logger
*/
/**
* Check if we're in development mode
* @returns {boolean}
*/
const isDev = () => {
try {
return false || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
} catch {
return false;
}
};
/**
* Logger object with development-only output
* All methods are no-ops in production for zero overhead
*/
const Logger = {
/**
* Log informational messages (development only)
* @param {string} message - The message to log
* @param {Object} [context] - Additional context data
*/
info(message, context) {
if (isDev()) {
console.info(`[Adminator] ${message}`, context || '');
}
},
/**
* Log warning messages (development only)
* @param {string} message - The warning message
* @param {Object} [context] - Additional context data
*/
warn(message, context) {
if (isDev()) {
console.warn(`[Adminator] ${message}`, context || '');
}
},
/**
* Log error messages (development only)
* @param {string} message - The error message
* @param {Error|Object} [context] - Error object or context data
*/
error(message, context) {
if (isDev()) {
console.error(`[Adminator] ${message}`, context || '');
}
},
/**
* Log debug messages (development only)
* @param {string} message - The debug message
* @param {Object} [context] - Additional context data
*/
debug(message, context) {
if (isDev()) {
console.debug(`[Adminator] ${message}`, context || '');
}
},
/**
* Group related log messages (development only)
* @param {string} label - Group label
*/
group(label) {
if (isDev()) {
console.group(`[Adminator] ${label}`);
}
},
/**
* End a log group (development only)
*/
groupEnd() {
if (isDev()) {
console.groupEnd();
}
},
/**
* Log with timing information (development only)
* @param {string} label - Timer label
*/
time(label) {
if (isDev()) {
console.time(`[Adminator] ${label}`);
}
},
/**
* End timing and log result (development only)
* @param {string} label - Timer label (must match time() call)
*/
timeEnd(label) {
if (isDev()) {
console.timeEnd(`[Adminator] ${label}`);
}
},
/**
* Log a table of data (development only)
* @param {Array|Object} data - Data to display as table
*/
table(data) {
if (isDev()) {
console.table(data);
}
}
};
/* harmony default export */ const logger = (Logger);
;// ../assets/scripts/components/Sidebar.js
/**
* Modern Sidebar Component
* Replaces jQuery-based sidebar functionality with vanilla JavaScript
*/
class Sidebar {
constructor() {
this.sidebar = document.querySelector('.sidebar');
this.sidebarMenu = document.querySelector('.sidebar .sidebar-menu');
this.sidebarToggleLinks = document.querySelectorAll('.sidebar-toggle a');
this.sidebarToggleById = document.querySelector('#sidebar-toggle');
this.app = document.querySelector('.app');
this.init();
}
init() {
if (!this.sidebar || !this.sidebarMenu) {
return;
}
this.setupMenuToggle();
this.setupSidebarToggle();
this.setActiveLink();
}
/**
* Setup dropdown menu functionality
*/
setupMenuToggle() {
const menuLinks = this.sidebarMenu.querySelectorAll('li a');
menuLinks.forEach(link => {
link.addEventListener('click', e => {
const listItem = link.parentElement;
const dropdownMenu = listItem.querySelector('.dropdown-menu');
// If this is a regular navigation link (not dropdown), allow normal navigation
if (!dropdownMenu) {
// Don't prevent default for regular navigation links
return;
}
// Only prevent default for dropdown toggles
e.preventDefault();
if (listItem.classList.contains('open')) {
this.closeDropdown(listItem, dropdownMenu);
} else {
this.closeAllDropdowns();
this.openDropdown(listItem, dropdownMenu);
}
});
});
}
/**
* Open dropdown with smooth animation
*/
openDropdown(listItem, dropdownMenu) {
listItem.classList.add('open');
dropdownMenu.style.display = 'block';
dropdownMenu.style.height = '0px';
dropdownMenu.style.overflow = 'hidden';
// Get the natural height
const height = dropdownMenu.scrollHeight;
// Animate to full height
dropdownMenu.animate([{
height: '0px'
}, {
height: `${height}px`
}], {
duration: 200,
easing: 'ease-out'
}).onfinish = () => {
dropdownMenu.style.height = 'auto';
dropdownMenu.style.overflow = 'visible';
};
}
/**
* Close dropdown with smooth animation
*/
closeDropdown(listItem, dropdownMenu) {
const height = dropdownMenu.scrollHeight;
dropdownMenu.style.height = `${height}px`;
dropdownMenu.style.overflow = 'hidden';
dropdownMenu.animate([{
height: `${height}px`
}, {
height: '0px'
}], {
duration: 200,
easing: 'ease-in'
}).onfinish = () => {
listItem.classList.remove('open');
dropdownMenu.style.display = 'none';
dropdownMenu.style.height = '';
dropdownMenu.style.overflow = '';
};
}
/**
* Close all open dropdowns
*/
closeAllDropdowns() {
const openItems = this.sidebarMenu.querySelectorAll('li.open');
openItems.forEach(item => {
const dropdownMenu = item.querySelector('.dropdown-menu');
if (dropdownMenu) {
this.closeDropdown(item, dropdownMenu);
}
// Also remove the has-active-child class
item.classList.remove('has-active-child');
});
}
/**
* Setup sidebar toggle functionality
*/
setupSidebarToggle() {
// Handle mobile sidebar toggle links (inside .sidebar-toggle divs)
this.sidebarToggleLinks.forEach(link => {
if (link && this.app) {
link.addEventListener('click', e => {
e.preventDefault();
this.toggleSidebar();
});
}
});
// Handle the main topbar sidebar toggle
if (this.sidebarToggleById && this.app) {
this.sidebarToggleById.addEventListener('click', e => {
e.preventDefault();
this.toggleSidebar();
});
}
}
/**
* Toggle sidebar and handle resize events properly
*/
toggleSidebar() {
this.app.classList.toggle('is-collapsed');
// Only trigger resize for masonry, but avoid chart redraw issues
setTimeout(() => {
// Dispatch a custom event instead of generic resize to avoid chart issues
window.dispatchEvent(new CustomEvent('sidebar:toggle', {
detail: {
collapsed: this.app.classList.contains('is-collapsed')
}
}));
// Still trigger resize for masonry but with a specific check
if (window.EVENT) {
window.dispatchEvent(window.EVENT);
}
}, 300);
}
/**
* Set active link based on current URL
*/
setActiveLink() {
// Remove active class from all nav items (including dropdown items)
const allNavItems = this.sidebar.querySelectorAll('.nav-item');
allNavItems.forEach(item => {
item.classList.remove('actived');
});
// Close all dropdowns first
this.closeAllDropdowns();
// Get current page filename
const currentPath = window.location.pathname;
const currentPage = currentPath.split('/').pop() || 'index.html';
// Find and activate the correct nav item
const allLinks = this.sidebar.querySelectorAll('a[href]');
allLinks.forEach(link => {
const href = link.getAttribute('href');
if (!href || href === 'javascript:void(0);' || href === 'javascript:void(0)') return;
// Extract filename from href
const linkPage = href.split('/').pop();
if (linkPage === currentPage) {
const navItem = link.closest('.nav-item');
if (navItem) {
navItem.classList.add('actived');
// If this is inside a dropdown, handle parent dropdown specially
const parentDropdown = navItem.closest('.dropdown-menu');
if (parentDropdown) {
const parentDropdownItem = parentDropdown.closest('.nav-item.dropdown');
if (parentDropdownItem) {
// Open the parent dropdown
parentDropdownItem.classList.add('open');
parentDropdown.style.display = 'block';
// Add special styling to indicate parent has active child
parentDropdownItem.classList.add('has-active-child');
}
}
}
}
});
}
/**
* Public method to refresh active links (useful for SPA navigation)
*/
refreshActiveLink() {
this.setActiveLink();
}
/**
* Public method to toggle sidebar programmatically
*/
toggle() {
if (this.app) {
this.app.classList.toggle('is-collapsed');
}
}
/**
* Public method to check if sidebar is collapsed
*/
isCollapsed() {
return this.app ? this.app.classList.contains('is-collapsed') : false;
}
}
/* harmony default export */ const components_Sidebar = (Sidebar);
;// ../../node_modules/@kurkle/color/dist/color.esm.js
/*!
* @kurkle/color v0.3.4
* https://github.com/kurkle/color#readme
* (c) 2024 Jukka Kurkela
* Released under the MIT License
*/
function round(v) {
return v + 0.5 | 0;
}
const lim = (v, l, h) => Math.max(Math.min(v, h), l);
function p2b(v) {
return lim(round(v * 2.55), 0, 255);
}
function b2p(v) {
return lim(round(v / 2.55), 0, 100);
}
function n2b(v) {
return lim(round(v * 255), 0, 255);
}
function b2n(v) {
return lim(round(v / 2.55) / 100, 0, 1);
}
function n2p(v) {
return lim(round(v * 100), 0, 100);
}
const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15};
const hex = [...'0123456789ABCDEF'];
const h1 = b => hex[b & 0xF];
const h2 = b => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
const eq = b => ((b & 0xF0) >> 4) === (b & 0xF);
const isShort = v => eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
function hexParse(str) {
var len = str.length;
var ret;
if (str[0] === '#') {
if (len === 4 || len === 5) {
ret = {
r: 255 & map$1[str[1]] * 17,
g: 255 & map$1[str[2]] * 17,
b: 255 & map$1[str[3]] * 17,
a: len === 5 ? map$1[str[4]] * 17 : 255
};
} else if (len === 7 || len === 9) {
ret = {
r: map$1[str[1]] << 4 | map$1[str[2]],
g: map$1[str[3]] << 4 | map$1[str[4]],
b: map$1[str[5]] << 4 | map$1[str[6]],
a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
};
}
}
return ret;
}
const alpha = (a, f) => a < 255 ? f(a) : '';
function hexString(v) {
var f = isShort(v) ? h1 : h2;
return v
? '#' + f(v.r) + f(v.g) + f(v.b) + alpha(v.a, f)
: undefined;
}
const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
function hsl2rgbn(h, s, l) {
const a = s * Math.min(l, 1 - l);
const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return [f(0), f(8), f(4)];
}
function hsv2rgbn(h, s, v) {
const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
return [f(5), f(3), f(1)];
}
function hwb2rgbn(h, w, b) {
const rgb = hsl2rgbn(h, 1, 0.5);
let i;
if (w + b > 1) {
i = 1 / (w + b);
w *= i;
b *= i;
}
for (i = 0; i < 3; i++) {
rgb[i] *= 1 - w - b;
rgb[i] += w;
}
return rgb;
}
function hueValue(r, g, b, d, max) {
if (r === max) {
return ((g - b) / d) + (g < b ? 6 : 0);
}
if (g === max) {
return (b - r) / d + 2;
}
return (r - g) / d + 4;
}
function rgb2hsl(v) {
const range = 255;
const r = v.r / range;
const g = v.g / range;
const b = v.b / range;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const l = (max + min) / 2;
let h, s, d;
if (max !== min) {
d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
h = hueValue(r, g, b, d, max);
h = h * 60 + 0.5;
}
return [h | 0, s || 0, l];
}
function calln(f, a, b, c) {
return (
Array.isArray(a)
? f(a[0], a[1], a[2])
: f(a, b, c)
).map(n2b);
}
function hsl2rgb(h, s, l) {
return calln(hsl2rgbn, h, s, l);
}
function hwb2rgb(h, w, b) {
return calln(hwb2rgbn, h, w, b);
}
function hsv2rgb(h, s, v) {
return calln(hsv2rgbn, h, s, v);
}
function hue(h) {
return (h % 360 + 360) % 360;
}
function hueParse(str) {
const m = HUE_RE.exec(str);
let a = 255;
let v;
if (!m) {
return;
}
if (m[5] !== v) {
a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
}
const h = hue(+m[2]);
const p1 = +m[3] / 100;
const p2 = +m[4] / 100;
if (m[1] === 'hwb') {
v = hwb2rgb(h, p1, p2);
} else if (m[1] === 'hsv') {
v = hsv2rgb(h, p1, p2);
} else {
v = hsl2rgb(h, p1, p2);
}
return {
r: v[0],
g: v[1],
b: v[2],
a: a
};
}
function rotate(v, deg) {
var h = rgb2hsl(v);
h[0] = hue(h[0] + deg);
h = hsl2rgb(h);
v.r = h[0];
v.g = h[1];
v.b = h[2];
}
function hslString(v) {
if (!v) {
return;
}
const a = rgb2hsl(v);
const h = a[0];
const s = n2p(a[1]);
const l = n2p(a[2]);
return v.a < 255
? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
: `hsl(${h}, ${s}%, ${l}%)`;
}
const map = {
x: 'dark',
Z: 'light',
Y: 're',
X: 'blu',
W: 'gr',
V: 'medium',
U: 'slate',
A: 'ee',
T: 'ol',
S: 'or',
B: 'ra',
C: 'lateg',
D: 'ights',
R: 'in',
Q: 'turquois',
E: 'hi',
P: 'ro',
O: 'al',
N: 'le',
M: 'de',
L: 'yello',
F: 'en',
K: 'ch',
G: 'arks',
H: 'ea',
I: 'ightg',
J: 'wh'
};
const names$1 = {
OiceXe: 'f0f8ff',
antiquewEte: 'faebd7',
aqua: 'ffff',
aquamarRe: '7fffd4',
azuY: 'f0ffff',
beige: 'f5f5dc',
bisque: 'ffe4c4',
black: '0',
blanKedOmond: 'ffebcd',
Xe: 'ff',
XeviTet: '8a2be2',
bPwn: 'a52a2a',
burlywood: 'deb887',
caMtXe: '5f9ea0',
KartYuse: '7fff00',
KocTate: 'd2691e',
cSO: 'ff7f50',
cSnflowerXe: '6495ed',
cSnsilk: 'fff8dc',
crimson: 'dc143c',
cyan: 'ffff',
xXe: '8b',
xcyan: '8b8b',
xgTMnPd: 'b8860b',
xWay: 'a9a9a9',
xgYF: '6400',
xgYy: 'a9a9a9',
xkhaki: 'bdb76b',
xmagFta: '8b008b',
xTivegYF: '556b2f',
xSange: 'ff8c00',
xScEd: '9932cc',
xYd: '8b0000',
xsOmon: 'e9967a',
xsHgYF: '8fbc8f',
xUXe: '483d8b',
xUWay: '2f4f4f',
xUgYy: '2f4f4f',
xQe: 'ced1',
xviTet: '9400d3',
dAppRk: 'ff1493',
dApskyXe: 'bfff',
dimWay: '696969',
dimgYy: '696969',
dodgerXe: '1e90ff',
fiYbrick: 'b22222',
flSOwEte: 'fffaf0',
foYstWAn: '228b22',
fuKsia: 'ff00ff',
gaRsbSo: 'dcdcdc',
ghostwEte: 'f8f8ff',
gTd: 'ffd700',
gTMnPd: 'daa520',
Way: '808080',
gYF: '8000',
gYFLw: 'adff2f',
gYy: '808080',
honeyMw: 'f0fff0',
hotpRk: 'ff69b4',
RdianYd: 'cd5c5c',
Rdigo: '4b0082',
ivSy: 'fffff0',
khaki: 'f0e68c',
lavFMr: 'e6e6fa',
lavFMrXsh: 'fff0f5',
lawngYF: '7cfc00',
NmoncEffon: 'fffacd',
ZXe: 'add8e6',
ZcSO: 'f08080',
Zcyan: 'e0ffff',
ZgTMnPdLw: 'fafad2',
ZWay: 'd3d3d3',
ZgYF: '90ee90',
ZgYy: 'd3d3d3',
ZpRk: 'ffb6c1',
ZsOmon: 'ffa07a',
ZsHgYF: '20b2aa',
ZskyXe: '87cefa',
ZUWay: '778899',
ZUgYy: '778899',
ZstAlXe: 'b0c4de',
ZLw: 'ffffe0',
lime: 'ff00',
limegYF: '32cd32',
lRF: 'faf0e6',
magFta: 'ff00ff',
maPon: '800000',
VaquamarRe: '66cdaa',
VXe: 'cd',
VScEd: 'ba55d3',
VpurpN: '9370db',
VsHgYF: '3cb371',
VUXe: '7b68ee',
VsprRggYF: 'fa9a',
VQe: '48d1cc',
VviTetYd: 'c71585',
midnightXe: '191970',
mRtcYam: 'f5fffa',
mistyPse: 'ffe4e1',
moccasR: 'ffe4b5',
navajowEte: 'ffdead',
navy: '80',
Tdlace: 'fdf5e6',
Tive: '808000',
TivedBb: '6b8e23',
Sange: 'ffa500',
SangeYd: 'ff4500',
ScEd: 'da70d6',
pOegTMnPd: 'eee8aa',
pOegYF: '98fb98',
pOeQe: 'afeeee',
pOeviTetYd: 'db7093',
papayawEp: 'ffefd5',
pHKpuff: 'ffdab9',
peru: 'cd853f',
pRk: 'ffc0cb',
plum: 'dda0dd',
powMrXe: 'b0e0e6',
purpN: '800080',
YbeccapurpN: '663399',
Yd: 'ff0000',
Psybrown: 'bc8f8f',
PyOXe: '4169e1',
saddNbPwn: '8b4513',
sOmon: 'fa8072',
sandybPwn: 'f4a460',
sHgYF: '2e8b57',
sHshell: 'fff5ee',
siFna: 'a0522d',
silver: 'c0c0c0',
skyXe: '87ceeb',
UXe: '6a5acd',
UWay: '708090',
UgYy: '708090',
snow: 'fffafa',
sprRggYF: 'ff7f',
stAlXe: '4682b4',
tan: 'd2b48c',
teO: '8080',
tEstN: 'd8bfd8',
tomato: 'ff6347',
Qe: '40e0d0',
viTet: 'ee82ee',
JHt: 'f5deb3',
wEte: 'ffffff',
wEtesmoke: 'f5f5f5',
Lw: 'ffff00',
LwgYF: '9acd32'
};
function unpack() {
const unpacked = {};
const keys = Object.keys(names$1);
const tkeys = Object.keys(map);
let i, j, k, ok, nk;
for (i = 0; i < keys.length; i++) {
ok = nk = keys[i];
for (j = 0; j < tkeys.length; j++) {
k = tkeys[j];
nk = nk.replace(k, map[k]);
}
k = parseInt(names$1[ok], 16);
unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
}
return unpacked;
}
let names;
function nameParse(str) {
if (!names) {
names = unpack();
names.transparent = [0, 0, 0, 0];
}
const a = names[str.toLowerCase()];
return a && {
r: a[0],
g: a[1],
b: a[2],
a: a.length === 4 ? a[3] : 255
};
}
const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
function rgbParse(str) {
const m = RGB_RE.exec(str);
let a = 255;
let r, g, b;
if (!m) {
return;
}
if (m[7] !== r) {
const v = +m[7];
a = m[8] ? p2b(v) : lim(v * 255, 0, 255);
}
r = +m[1];
g = +m[3];
b = +m[5];
r = 255 & (m[2] ? p2b(r) : lim(r, 0, 255));
g = 255 & (m[4] ? p2b(g) : lim(g, 0, 255));
b = 255 & (m[6] ? p2b(b) : lim(b, 0, 255));
return {
r: r,
g: g,
b: b,
a: a
};
}
function rgbString(v) {
return v && (
v.a < 255
? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
: `rgb(${v.r}, ${v.g}, ${v.b})`
);
}
const to = v => v <= 0.0031308 ? v * 12.92 : Math.pow(v, 1.0 / 2.4) * 1.055 - 0.055;
const from = v => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
function interpolate(rgb1, rgb2, t) {
const r = from(b2n(rgb1.r));
const g = from(b2n(rgb1.g));
const b = from(b2n(rgb1.b));
return {
r: n2b(to(r + t * (from(b2n(rgb2.r)) - r))),
g: n2b(to(g + t * (from(b2n(rgb2.g)) - g))),
b: n2b(to(b + t * (from(b2n(rgb2.b)) - b))),
a: rgb1.a + t * (rgb2.a - rgb1.a)
};
}
function modHSL(v, i, ratio) {
if (v) {
let tmp = rgb2hsl(v);
tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
tmp = hsl2rgb(tmp);
v.r = tmp[0];
v.g = tmp[1];
v.b = tmp[2];
}
}
function clone(v, proto) {
return v ? Object.assign(proto || {}, v) : v;
}
function fromObject(input) {
var v = {r: 0, g: 0, b: 0, a: 255};
if (Array.isArray(input)) {
if (input.length >= 3) {
v = {r: input[0], g: input[1], b: input[2], a: 255};
if (input.length > 3) {
v.a = n2b(input[3]);
}
}
} else {
v = clone(input, {r: 0, g: 0, b: 0, a: 1});
v.a = n2b(v.a);
}
return v;
}
function functionParse(str) {
if (str.charAt(0) === 'r') {
return rgbParse(str);
}
return hueParse(str);
}
class Color {
constructor(input) {
if (input instanceof Color) {
return input;
}
const type = typeof input;
let v;
if (type === 'object') {
v = fromObject(input);
} else if (type === 'string') {
v = hexParse(input) || nameParse(input) || functionParse(input);
}
this._rgb = v;
this._valid = !!v;
}
get valid() {
return this._valid;
}
get rgb() {
var v = clone(this._rgb);
if (v) {
v.a = b2n(v.a);
}
return v;
}
set rgb(obj) {
this._rgb = fromObject(obj);
}
rgbString() {
return this._valid ? rgbString(this._rgb) : undefined;
}
hexString() {
return this._valid ? hexString(this._rgb) : undefined;
}
hslString() {
return this._valid ? hslString(this._rgb) : undefined;
}
mix(color, weight) {
if (color) {
const c1 = this.rgb;
const c2 = color.rgb;
let w2;
const p = weight === w2 ? 0.5 : weight;
const w = 2 * p - 1;
const a = c1.a - c2.a;
const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
w2 = 1 - w1;
c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
c1.a = p * c1.a + (1 - p) * c2.a;
this.rgb = c1;
}
return this;
}
interpolate(color, t) {
if (color) {
this._rgb = interpolate(this._rgb, color._rgb, t);
}
return this;
}
clone() {
return new Color(this.rgb);
}
alpha(a) {
this._rgb.a = n2b(a);
return this;
}
clearer(ratio) {
const rgb = this._rgb;
rgb.a *= 1 - ratio;
return this;
}
greyscale() {
const rgb = this._rgb;
const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
rgb.r = rgb.g = rgb.b = val;
return this;
}
opaquer(ratio) {
const rgb = this._rgb;
rgb.a *= 1 + ratio;
return this;
}
negate() {
const v = this._rgb;
v.r = 255 - v.r;
v.g = 255 - v.g;
v.b = 255 - v.b;
return this;
}
lighten(ratio) {
modHSL(this._rgb, 2, ratio);
return this;
}
darken(ratio) {
modHSL(this._rgb, 2, -ratio);
return this;
}
saturate(ratio) {
modHSL(this._rgb, 1, ratio);
return this;
}
desaturate(ratio) {
modHSL(this._rgb, 1, -ratio);
return this;
}
rotate(deg) {
rotate(this._rgb, deg);
return this;
}
}
function index_esm(input) {
return new Color(input);
}
;// ../../node_modules/chart.js/dist/chunks/helpers.dataset.js
/*!
* Chart.js v4.5.1
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT License
*/
/**
* @namespace Chart.helpers
*/ /**
* An empty function that can be used, for example, for optional callback.
*/ function noop() {
/* noop */ }
/**
* Returns a unique id, sequentially generated from a global variable.
*/ const uid = (()=>{
let id = 0;
return ()=>id++;
})();
/**
* Returns true if `value` is neither null nor undefined, else returns false.
* @param value - The value to test.
* @since 2.7.0
*/ function isNullOrUndef(value) {
return value === null || value === undefined;
}
/**
* Returns true if `value` is an array (including typed arrays), else returns false.
* @param value - The value to test.
* @function
*/ function isArray(value) {
if (Array.isArray && Array.isArray(value)) {
return true;
}
const type = Object.prototype.toString.call(value);
if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {
return true;
}
return false;
}
/**
* Returns true if `value` is an object (excluding null), else returns false.
* @param value - The value to test.
* @since 2.7.0
*/ function isObject(value) {
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
}
/**
* Returns true if `value` is a finite number, else returns false
* @param value - The value to test.
*/ function isNumberFinite(value) {
return (typeof value === 'number' || value instanceof Number) && isFinite(+value);
}
/**
* Returns `value` if finite, else returns `defaultValue`.
* @param value - The value to return if defined.
* @param defaultValue - The value to return if `value` is not finite.
*/ function finiteOrDefault(value, defaultValue) {
return isNumberFinite(value) ? value : defaultValue;
}
/**
* Returns `value` if defined, else returns `defaultValue`.
* @param value - The value to return if defined.
* @param defaultValue - The value to return if `value` is undefined.
*/ function valueOrDefault(value, defaultValue) {
return typeof value === 'undefined' ? defaultValue : value;
}
const toPercentage = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 : +value / dimension;
const toDimension = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 * dimension : +value;
/**
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
* value returned by `fn`. If `fn` is not a function, this method returns undefined.
* @param fn - The function to call.
* @param args - The arguments with which `fn` should be called.
* @param [thisArg] - The value of `this` provided for the call to `fn`.
*/ function callback(fn, args, thisArg) {
if (fn && typeof fn.call === 'function') {
return fn.apply(thisArg, args);
}
}
function each(loopable, fn, thisArg, reverse) {
let i, len, keys;
if (isArray(loopable)) {
len = loopable.length;
if (reverse) {
for(i = len - 1; i >= 0; i--){
fn.call(thisArg, loopable[i], i);
}
} else {
for(i = 0; i < len; i++){
fn.call(thisArg, loopable[i], i);
}
}
} else if (isObject(loopable)) {
keys = Object.keys(loopable);
len = keys.length;
for(i = 0; i < len; i++){
fn.call(thisArg, loopable[keys[i]], keys[i]);
}
}
}
/**
* Returns true if the `a0` and `a1` arrays have the same content, else returns false.
* @param a0 - The array to compare
* @param a1 - The array to compare
* @private
*/ function _elementsEqual(a0, a1) {
let i, ilen, v0, v1;
if (!a0 || !a1 || a0.length !== a1.length) {
return false;
}
for(i = 0, ilen = a0.length; i < ilen; ++i){
v0 = a0[i];
v1 = a1[i];
if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
return false;
}
}
return true;
}
/**
* Returns a deep copy of `source` without keeping references on objects and arrays.
* @param source - The value to clone.
*/ function helpers_dataset_clone(source) {
if (isArray(source)) {
return source.map(helpers_dataset_clone);
}
if (isObject(source)) {
const target = Object.create(null);
const keys = Object.keys(source);
const klen = keys.length;
let k = 0;
for(; k < klen; ++k){
target[keys[k]] = helpers_dataset_clone(source[keys[k]]);
}
return target;
}
return source;
}
function isValidKey(key) {
return [
'__proto__',
'prototype',
'constructor'
].indexOf(key) === -1;
}
/**
* The default merger when Chart.helpers.merge is called without merger option.
* Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
* @private
*/ function _merger(key, target, source, options) {
if (!isValidKey(key)) {
return;
}
const tval = target[key];
const sval = source[key];
if (isObject(tval) && isObject(sval)) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
merge(tval, sval, options);
} else {
target[key] = helpers_dataset_clone(sval);
}
}
function merge(target, source, options) {
const sources = isArray(source) ? source : [
source
];
const ilen = sources.length;
if (!isObject(target)) {
return target;
}
options = options || {};
const merger = options.merger || _merger;
let current;
for(let i = 0; i < ilen; ++i){
current = sources[i];
if (!isObject(current)) {
continue;
}
const keys = Object.keys(current);
for(let k = 0, klen = keys.length; k < klen; ++k){
merger(keys[k], target, current, options);
}
}
return target;
}
function mergeIf(target, source) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return merge(target, source, {
merger: _mergerIf
});
}
/**
* Merges source[key] in target[key] only if target[key] is undefined.
* @private
*/ function _mergerIf(key, target, source) {
if (!isValidKey(key)) {
return;
}
const tval = target[key];
const sval = source[key];
if (isObject(tval) && isObject(sval)) {
mergeIf(tval, sval);
} else if (!Object.prototype.hasOwnProperty.call(target, key)) {
target[key] = helpers_dataset_clone(sval);
}
}
/**
* @private
*/ function _deprecated(scope, value, previous, current) {
if (value !== undefined) {
console.warn(scope + ': "' + previous + '" is deprecated. Please use "' + current + '" instead');
}
}
// resolveObjectKey resolver cache
const keyResolvers = {
// Chart.helpers.core resolveObjectKey should resolve empty key to root object
'': (v)=>v,
// default resolvers
x: (o)=>o.x,
y: (o)=>o.y
};
/**
* @private
*/ function _splitKey(key) {
const parts = key.split('.');
const keys = [];
let tmp = '';
for (const part of parts){
tmp += part;
if (tmp.endsWith('\\')) {
tmp = tmp.slice(0, -1) + '.';
} else {
keys.push(tmp);
tmp = '';
}
}
return keys;
}
function _getKeyResolver(key) {
const keys = _splitKey(key);
return (obj)=>{
for (const k of keys){
if (k === '') {
break;
}
obj = obj && obj[k];
}
return obj;
};
}
function resolveObjectKey(obj, key) {
const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
return resolver(obj);
}
/**
* @private
*/ function _capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
const defined = (value)=>typeof value !== 'undefined';
const isFunction = (value)=>typeof value === 'function';
// Adapted from https://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality#31129384
const setsEqual = (a, b)=>{
if (a.size !== b.size) {
return false;
}
for (const item of a){
if (!b.has(item)) {
return false;
}
}
return true;
};
/**
* @param e - The event
* @private
*/ function _isClickEvent(e) {
return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
}
/**
* @alias Chart.helpers.math
* @namespace
*/ const PI = Math.PI;
const TAU = 2 * PI;
const PITAU = TAU + PI;
const INFINITY = Number.POSITIVE_INFINITY;
const RAD_PER_DEG = PI / 180;
const HALF_PI = PI / 2;
const QUARTER_PI = PI / 4;
const TWO_THIRDS_PI = PI * 2 / 3;
const log10 = Math.log10;
const sign = Math.sign;
function almostEquals(x, y, epsilon) {
return Math.abs(x - y) < epsilon;
}
/**
* Implementation of the nice number algorithm used in determining where axis labels will go
*/ function niceNum(range) {
const roundedRange = Math.round(range);
range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
const niceRange = Math.pow(10, Math.floor(log10(range)));
const fraction = range / niceRange;
const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
return niceFraction * niceRange;
}
/**
* Returns an array of factors sorted from 1 to sqrt(value)
* @private
*/ function _factorize(value) {
const result = [];
const sqrt = Math.sqrt(value);
let i;
for(i = 1; i < sqrt; i++){
if (value % i === 0) {
result.push(i);
result.push(value / i);
}
}
if (sqrt === (sqrt | 0)) {
result.push(sqrt);
}
result.sort((a, b)=>a - b).pop();
return result;
}
/**
* Verifies that attempting to coerce n to string or number won't throw a TypeError.
*/ function isNonPrimitive(n) {
return typeof n === 'symbol' || typeof n === 'object' && n !== null && !(Symbol.toPrimitive in n || 'toString' in n || 'valueOf' in n);
}
function isNumber(n) {
return !isNonPrimitive(n) && !isNaN(parseFloat(n)) && isFinite(n);
}
function almostWhole(x, epsilon) {
const rounded = Math.round(x);
return rounded - epsilon <= x && rounded + epsilon >= x;
}
/**
* @private
*/ function _setMinAndMaxByKey(array, target, property) {
let i, ilen, value;
for(i = 0, ilen = array.length; i < ilen; i++){
value = array[i][property];
if (!isNaN(value)) {
target.min = Math.min(target.min, value);
target.max = Math.max(target.max, value);
}
}
}
function toRadians(degrees) {
return degrees * (PI / 180);
}
function toDegrees(radians) {
return radians * (180 / PI);
}
/**
* Returns the number of decimal places
* i.e. the number of digits after the decimal point, of the value of this Number.
* @param x - A number.
* @returns The number of decimal places.
* @private
*/ function _decimalPlaces(x) {
if (!isNumberFinite(x)) {
return;
}
let e = 1;
let p = 0;
while(Math.round(x * e) / e !== x){
e *= 10;
p++;
}
return p;
}
// Gets the angle from vertical upright to the point about a centre.
function getAngleFromPoint(centrePoint, anglePoint) {
const distanceFromXCenter = anglePoint.x - centrePoint.x;
const distanceFromYCenter = anglePoint.y - centrePoint.y;
const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
if (angle < -0.5 * PI) {
angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
}
return {
angle,
distance: radialDistanceFromCenter
};
}
function distanceBetweenPoints(pt1, pt2) {
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
}
/**
* Shortest distance between angles, in either direction.
* @private
*/ function _angleDiff(a, b) {
return (a - b + PITAU) % TAU - PI;
}
/**
* Normalize angle to be between 0 and 2*PI
* @private
*/ function _normalizeAngle(a) {
return (a % TAU + TAU) % TAU;
}
/**
* @private
*/ function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
const a = _normalizeAngle(angle);
const s = _normalizeAngle(start);
const e = _normalizeAngle(end);
const angleToStart = _normalizeAngle(s - a);
const angleToEnd = _normalizeAngle(e - a);
const startToAngle = _normalizeAngle(a - s);
const endToAngle = _normalizeAngle(a - e);
return a === s || a === e || sameAngleIsFullCircle && s === e || angleToStart > angleToEnd && startToAngle < endToAngle;
}
/**
* Limit `value` between `min` and `max`
* @param value
* @param min
* @param max
* @private
*/ function _limitValue(value, min, max) {
return Math.max(min, Math.min(max, value));
}
/**
* @param {number} value
* @private
*/ function _int16Range(value) {
return _limitValue(value, -32768, 32767);
}
/**
* @param value
* @param start
* @param end
* @param [epsilon]
* @private
*/ function _isBetween(value, start, end, epsilon = 1e-6) {
return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
}
function _lookup(table, value, cmp) {
cmp = cmp || ((index)=>table[index] < value);
let hi = table.length - 1;
let lo = 0;
let mid;
while(hi - lo > 1){
mid = lo + hi >> 1;
if (cmp(mid)) {
lo = mid;
} else {
hi = mid;
}
}
return {
lo,
hi
};
}
/**
* Binary search
* @param table - the table search. must be sorted!
* @param key - property name for the value in each entry
* @param value - value to find
* @param last - lookup last index
* @private
*/ const _lookupByKey = (table, key, value, last)=>_lookup(table, value, last ? (index)=>{
const ti = table[index][key];
return ti < value || ti === value && table[index + 1][key] === value;
} : (index)=>table[index][key] < value);
/**
* Reverse binary search
* @param table - the table search. must be sorted!
* @param key - property name for the value in each entry
* @param value - value to find
* @private
*/ const _rlookupByKey = (table, key, value)=>_lookup(table, value, (index)=>table[index][key] >= value);
/**
* Return subset of `values` between `min` and `max` inclusive.
* Values are assumed to be in sorted order.
* @param values - sorted array of values
* @param min - min value
* @param max - max value
*/ function _filterBetween(values, min, max) {
let start = 0;
let end = values.length;
while(start < end && values[start] < min){
start++;
}
while(end > start && values[end - 1] > max){
end--;
}
return start > 0 || end < values.length ? values.slice(start, end) : values;
}
const arrayEvents = [
'push',
'pop',
'shift',
'splice',
'unshift'
];
function listenArrayEvents(array, listener) {
if (array._chartjs) {
array._chartjs.listeners.push(listener);
return;
}
Object.defineProperty(array, '_chartjs', {
configurable: true,
enumerable: false,
value: {
listeners: [
listener
]
}
});
arrayEvents.forEach((key)=>{
const method = '_onData' + _capitalize(key);
const base = array[key];
Object.defineProperty(array, key, {
configurable: true,
enumerable: false,
value (...args) {
const res = base.apply(this, args);
array._chartjs.listeners.forEach((object)=>{
if (typeof object[method] === 'function') {
object[method](...args);
}
});
return res;
}
});
});
}
function unlistenArrayEvents(array, listener) {
const stub = array._chartjs;
if (!stub) {
return;
}
const listeners = stub.listeners;
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
if (listeners.length > 0) {
return;
}
arrayEvents.forEach((key)=>{
delete array[key];
});
delete array._chartjs;
}
/**
* @param items
*/ function _arrayUnique(items) {
const set = new Set(items);
if (set.size === items.length) {
return items;
}
return Array.from(set);
}
function fontString(pixelSize, fontStyle, fontFamily) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
}
/**
* Request animation polyfill
*/ const requestAnimFrame = function() {
if (typeof window === 'undefined') {
return function(callback) {
return callback();
};
}
return window.requestAnimationFrame;
}();
/**
* Throttles calling `fn` once per animation frame
* Latest arguments are used on the actual call
*/ function throttled(fn, thisArg) {
let argsToUse = [];
let ticking = false;
return function(...args) {
// Save the args for use later
argsToUse = args;
if (!ticking) {
ticking = true;
requestAnimFrame.call(window, ()=>{
ticking = false;
fn.apply(thisArg, argsToUse);
});
}
};
}
/**
* Debounces calling `fn` for `delay` ms
*/ function debounce(fn, delay) {
let timeout;
return function(...args) {
if (delay) {
clearTimeout(timeout);
timeout = setTimeout(fn, delay, args);
} else {
fn.apply(this, args);
}
return delay;
};
}
/**
* Converts 'start' to 'left', 'end' to 'right' and others to 'center'
* @private
*/ const _toLeftRightCenter = (align)=>align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
/**
* Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`
* @private
*/ const _alignStartEnd = (align, start, end)=>align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
/**
* Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`
* @private
*/ const _textX = (align, left, right, rtl)=>{
const check = rtl ? 'left' : 'right';
return align === check ? right : align === 'center' ? (left + right) / 2 : left;
};
/**
* Return start and count of visible points.
* @private
*/ function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
const pointCount = points.length;
let start = 0;
let count = pointCount;
if (meta._sorted) {
const { iScale , vScale , _parsed } = meta;
const spanGaps = meta.dataset ? meta.dataset.options ? meta.dataset.options.spanGaps : null : null;
const axis = iScale.axis;
const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
if (minDefined) {
start = Math.min(// @ts-expect-error Need to type _parsed
_lookupByKey(_parsed, axis, min).lo, // @ts-expect-error Need to fix types on _lookupByKey
animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo);
if (spanGaps) {
const distanceToDefinedLo = _parsed.slice(0, start + 1).reverse().findIndex((point)=>!isNullOrUndef(point[vScale.axis]));
start -= Math.max(0, distanceToDefinedLo);
}
start = _limitValue(start, 0, pointCount - 1);
}
if (maxDefined) {
let end = Math.max(// @ts-expect-error Need to type _parsed
_lookupByKey(_parsed, iScale.axis, max, true).hi + 1, // @ts-expect-error Need to fix types on _lookupByKey
animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1);
if (spanGaps) {
const distanceToDefinedHi = _parsed.slice(end - 1).findIndex((point)=>!isNullOrUndef(point[vScale.axis]));
end += Math.max(0, distanceToDefinedHi);
}
count = _limitValue(end, start, pointCount) - start;
} else {
count = pointCount - start;
}
}
return {
start,
count
};
}
/**
* Checks if the scale ranges have changed.
* @param {object} meta - dataset meta.
* @returns {boolean}
* @private
*/ function _scaleRangesChanged(meta) {
const { xScale , yScale , _scaleRanges } = meta;
const newRanges = {
xmin: xScale.min,
xmax: xScale.max,
ymin: yScale.min,
ymax: yScale.max
};
if (!_scaleRanges) {
meta._scaleRanges = newRanges;
return true;
}
const changed = _scaleRanges.xmin !== xScale.min || _scaleRanges.xmax !== xScale.max || _scaleRanges.ymin !== yScale.min || _scaleRanges.ymax !== yScale.max;
Object.assign(_scaleRanges, newRanges);
return changed;
}
const atEdge = (t)=>t === 0 || t === 1;
const elasticIn = (t, s, p)=>-(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
const elasticOut = (t, s, p)=>Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
/**
* Easing functions adapted from Robert Penner's easing equations.
* @namespace Chart.helpers.easing.effects
* @see http://www.robertpenner.com/easing/
*/ const effects = {
linear: (t)=>t,
easeInQuad: (t)=>t * t,
easeOutQuad: (t)=>-t * (t - 2),
easeInOutQuad: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t : -0.5 * (--t * (t - 2) - 1),
easeInCubic: (t)=>t * t * t,
easeOutCubic: (t)=>(t -= 1) * t * t + 1,
easeInOutCubic: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t : 0.5 * ((t -= 2) * t * t + 2),
easeInQuart: (t)=>t * t * t * t,
easeOutQuart: (t)=>-((t -= 1) * t * t * t - 1),
easeInOutQuart: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t : -0.5 * ((t -= 2) * t * t * t - 2),
easeInQuint: (t)=>t * t * t * t * t,
easeOutQuint: (t)=>(t -= 1) * t * t * t * t + 1,
easeInOutQuint: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t * t : 0.5 * ((t -= 2) * t * t * t * t + 2),
easeInSine: (t)=>-Math.cos(t * HALF_PI) + 1,
easeOutSine: (t)=>Math.sin(t * HALF_PI),
easeInOutSine: (t)=>-0.5 * (Math.cos(PI * t) - 1),
easeInExpo: (t)=>t === 0 ? 0 : Math.pow(2, 10 * (t - 1)),
easeOutExpo: (t)=>t === 1 ? 1 : -Math.pow(2, -10 * t) + 1,
easeInOutExpo: (t)=>atEdge(t) ? t : t < 0.5 ? 0.5 * Math.pow(2, 10 * (t * 2 - 1)) : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
easeInCirc: (t)=>t >= 1 ? t : -(Math.sqrt(1 - t * t) - 1),
easeOutCirc: (t)=>Math.sqrt(1 - (t -= 1) * t),
easeInOutCirc: (t)=>(t /= 0.5) < 1 ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
easeInElastic: (t)=>atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
easeOutElastic: (t)=>atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
easeInOutElastic (t) {
const s = 0.1125;
const p = 0.45;
return atEdge(t) ? t : t < 0.5 ? 0.5 * elasticIn(t * 2, s, p) : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
},
easeInBack (t) {
const s = 1.70158;
return t * t * ((s + 1) * t - s);
},
easeOutBack (t) {
const s = 1.70158;
return (t -= 1) * t * ((s + 1) * t + s) + 1;
},
easeInOutBack (t) {
let s = 1.70158;
if ((t /= 0.5) < 1) {
return 0.5 * (t * t * (((s *= 1.525) + 1) * t - s));
}
return 0.5 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);
},
easeInBounce: (t)=>1 - effects.easeOutBounce(1 - t),
easeOutBounce (t) {
const m = 7.5625;
const d = 2.75;
if (t < 1 / d) {
return m * t * t;
}
if (t < 2 / d) {
return m * (t -= 1.5 / d) * t + 0.75;
}
if (t < 2.5 / d) {
return m * (t -= 2.25 / d) * t + 0.9375;
}
return m * (t -= 2.625 / d) * t + 0.984375;
},
easeInOutBounce: (t)=>t < 0.5 ? effects.easeInBounce(t * 2) * 0.5 : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5
};
function isPatternOrGradient(value) {
if (value && typeof value === 'object') {
const type = value.toString();
return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
}
return false;
}
function color(value) {
return isPatternOrGradient(value) ? value : new Color(value);
}
function getHoverColor(value) {
return isPatternOrGradient(value) ? value : new Color(value).saturate(0.5).darken(0.1).hexString();
}
const numbers = [
'x',
'y',
'borderWidth',
'radius',
'tension'
];
const colors = [
'color',
'borderColor',
'backgroundColor'
];
function applyAnimationsDefaults(defaults) {
defaults.set('animation', {
delay: undefined,
duration: 1000,
easing: 'easeOutQuart',
fn: undefined,
from: undefined,
loop: undefined,
to: undefined,
type: undefined
});
defaults.describe('animation', {
_fallback: false,
_indexable: false,
_scriptable: (name)=>name !== 'onProgress' && name !== 'onComplete' && name !== 'fn'
});
defaults.set('animations', {
colors: {
type: 'color',
properties: colors
},
numbers: {
type: 'number',
properties: numbers
}
});
defaults.describe('animations', {
_fallback: 'animation'
});
defaults.set('transitions', {
active: {
animation: {
duration: 400
}
},
resize: {
animation: {
duration: 0
}
},
show: {
animations: {
colors: {
from: 'transparent'
},
visible: {
type: 'boolean',
duration: 0
}
}
},
hide: {
animations: {
colors: {
to: 'transparent'
},
visible: {
type: 'boolean',
easing: 'linear',
fn: (v)=>v | 0
}
}
}
});
}
function applyLayoutsDefaults(defaults) {
defaults.set('layout', {
autoPadding: true,
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
});
}
const intlCache = new Map();
function getNumberFormat(locale, options) {
options = options || {};
const cacheKey = locale + JSON.stringify(options);
let formatter = intlCache.get(cacheKey);
if (!formatter) {
formatter = new Intl.NumberFormat(locale, options);
intlCache.set(cacheKey, formatter);
}
return formatter;
}
function formatNumber(num, locale, options) {
return getNumberFormat(locale, options).format(num);
}
const formatters = {
values (value) {
return isArray(value) ? value : '' + value;
},
numeric (tickValue, index, ticks) {
if (tickValue === 0) {
return '0';
}
const locale = this.chart.options.locale;
let notation;
let delta = tickValue;
if (ticks.length > 1) {
const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
if (maxTick < 1e-4 || maxTick > 1e+15) {
notation = 'scientific';
}
delta = calculateDelta(tickValue, ticks);
}
const logDelta = log10(Math.abs(delta));
const numDecimal = isNaN(logDelta) ? 1 : Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
const options = {
notation,
minimumFractionDigits: numDecimal,
maximumFractionDigits: numDecimal
};
Object.assign(options, this.options.ticks.format);
return formatNumber(tickValue, locale, options);
},
logarithmic (tickValue, index, ticks) {
if (tickValue === 0) {
return '0';
}
const remain = ticks[index].significand || tickValue / Math.pow(10, Math.floor(log10(tickValue)));
if ([
1,
2,
3,
5,
10,
15
].includes(remain) || index > 0.8 * ticks.length) {
return formatters.numeric.call(this, tickValue, index, ticks);
}
return '';
}
};
function calculateDelta(tickValue, ticks) {
let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
delta = tickValue - Math.floor(tickValue);
}
return delta;
}
var Ticks = {
formatters
};
function applyScaleDefaults(defaults) {
defaults.set('scale', {
display: true,
offset: false,
reverse: false,
beginAtZero: false,
bounds: 'ticks',
clip: true,
grace: 0,
grid: {
display: true,
lineWidth: 1,
drawOnChartArea: true,
drawTicks: true,
tickLength: 8,
tickWidth: (_ctx, options)=>options.lineWidth,
tickColor: (_ctx, options)=>options.color,
offset: false
},
border: {
display: true,
dash: [],
dashOffset: 0.0,
width: 1
},
title: {
display: false,
text: '',
padding: {
top: 4,
bottom: 4
}
},
ticks: {
minRotation: 0,
maxRotation: 50,
mirror: false,
textStrokeWidth: 0,
textStrokeColor: '',
padding: 3,
display: true,
autoSkip: true,
autoSkipPadding: 3,
labelOffset: 0,
callback: Ticks.formatters.values,
minor: {},
major: {},
align: 'center',
crossAlign: 'near',
showLabelBackdrop: false,
backdropColor: 'rgba(255, 255, 255, 0.75)',
backdropPadding: 2
}
});
defaults.route('scale.ticks', 'color', '', 'color');
defaults.route('scale.grid', 'color', '', 'borderColor');
defaults.route('scale.border', 'color', '', 'borderColor');
defaults.route('scale.title', 'color', '', 'color');
defaults.describe('scale', {
_fallback: false,
_scriptable: (name)=>!name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
_indexable: (name)=>name !== 'borderDash' && name !== 'tickBorderDash' && name !== 'dash'
});
defaults.describe('scales', {
_fallback: 'scale'
});
defaults.describe('scale.ticks', {
_scriptable: (name)=>name !== 'backdropPadding' && name !== 'callback',
_indexable: (name)=>name !== 'backdropPadding'
});
}
const overrides = Object.create(null);
const descriptors = Object.create(null);
function getScope$1(node, key) {
if (!key) {
return node;
}
const keys = key.split('.');
for(let i = 0, n = keys.length; i < n; ++i){
const k = keys[i];
node = node[k] || (node[k] = Object.create(null));
}
return node;
}
function set(root, scope, values) {
if (typeof scope === 'string') {
return merge(getScope$1(root, scope), values);
}
return merge(getScope$1(root, ''), scope);
}
class Defaults {
constructor(_descriptors, _appliers){
this.animation = undefined;
this.backgroundColor = 'rgba(0,0,0,0.1)';
this.borderColor = 'rgba(0,0,0,0.1)';
this.color = '#666';
this.datasets = {};
this.devicePixelRatio = (context)=>context.chart.platform.getDevicePixelRatio();
this.elements = {};
this.events = [
'mousemove',
'mouseout',
'click',
'touchstart',
'touchmove'
];
this.font = {
family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
size: 12,
style: 'normal',
lineHeight: 1.2,
weight: null
};
this.hover = {};
this.hoverBackgroundColor = (ctx, options)=>getHoverColor(options.backgroundColor);
this.hoverBorderColor = (ctx, options)=>getHoverColor(options.borderColor);
this.hoverColor = (ctx, options)=>getHoverColor(options.color);
this.indexAxis = 'x';
this.interaction = {
mode: 'nearest',
intersect: true,
includeInvisible: false
};
this.maintainAspectRatio = true;
this.onHover = null;
this.onClick = null;
this.parsing = true;
this.plugins = {};
this.responsive = true;
this.scale = undefined;
this.scales = {};
this.showLine = true;
this.drawActiveElementsOnTop = true;
this.describe(_descriptors);
this.apply(_appliers);
}
set(scope, values) {
return set(this, scope, values);
}
get(scope) {
return getScope$1(this, scope);
}
describe(scope, values) {
return set(descriptors, scope, values);
}
override(scope, values) {
return set(overrides, scope, values);
}
route(scope, name, targetScope, targetName) {
const scopeObject = getScope$1(this, scope);
const targetScopeObject = getScope$1(this, targetScope);
const privateName = '_' + name;
Object.defineProperties(scopeObject, {
[privateName]: {
value: scopeObject[name],
writable: true
},
[name]: {
enumerable: true,
get () {
const local = this[privateName];
const target = targetScopeObject[targetName];
if (isObject(local)) {
return Object.assign({}, target, local);
}
return valueOrDefault(local, target);
},
set (value) {
this[privateName] = value;
}
}
});
}
apply(appliers) {
appliers.forEach((apply)=>apply(this));
}
}
var defaults = /* #__PURE__ */ new Defaults({
_scriptable: (name)=>!name.startsWith('on'),
_indexable: (name)=>name !== 'events',
hover: {
_fallback: 'interaction'
},
interaction: {
_scriptable: false,
_indexable: false
}
}, [
applyAnimationsDefaults,
applyLayoutsDefaults,
applyScaleDefaults
]);
/**
* Converts the given font object into a CSS font string.
* @param font - A font object.
* @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
* @private
*/ function toFontString(font) {
if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
return null;
}
return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family;
}
/**
* @private
*/ function _measureText(ctx, data, gc, longest, string) {
let textWidth = data[string];
if (!textWidth) {
textWidth = data[string] = ctx.measureText(string).width;
gc.push(string);
}
if (textWidth > longest) {
longest = textWidth;
}
return longest;
}
/**
* @private
*/ // eslint-disable-next-line complexity
function _longestText(ctx, font, arrayOfThings, cache) {
cache = cache || {};
let data = cache.data = cache.data || {};
let gc = cache.garbageCollect = cache.garbageCollect || [];
if (cache.font !== font) {
data = cache.data = {};
gc = cache.garbageCollect = [];
cache.font = font;
}
ctx.save();
ctx.font = font;
let longest = 0;
const ilen = arrayOfThings.length;
let i, j, jlen, thing, nestedThing;
for(i = 0; i < ilen; i++){
thing = arrayOfThings[i];
// Undefined strings and arrays should not be measured
if (thing !== undefined && thing !== null && !isArray(thing)) {
longest = _measureText(ctx, data, gc, longest, thing);
} else if (isArray(thing)) {
// if it is an array lets measure each element
// to do maybe simplify this function a bit so we can do this more recursively?
for(j = 0, jlen = thing.length; j < jlen; j++){
nestedThing = thing[j];
// Undefined strings and arrays should not be measured
if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
longest = _measureText(ctx, data, gc, longest, nestedThing);
}
}
}
}
ctx.restore();
const gcLen = gc.length / 2;
if (gcLen > arrayOfThings.length) {
for(i = 0; i < gcLen; i++){
delete data[gc[i]];
}
gc.splice(0, gcLen);
}
return longest;
}
/**
* Returns the aligned pixel value to avoid anti-aliasing blur
* @param chart - The chart instance.
* @param pixel - A pixel value.
* @param width - The width of the element.
* @returns The aligned pixel value.
* @private
*/ function _alignPixel(chart, pixel, width) {
const devicePixelRatio = chart.currentDevicePixelRatio;
const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
}
/**
* Clears the entire canvas.
*/ function clearCanvas(canvas, ctx) {
if (!ctx && !canvas) {
return;
}
ctx = ctx || canvas.getContext('2d');
ctx.save();
// canvas.width and canvas.height do not consider the canvas transform,
// while clearRect does
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
function drawPoint(ctx, options, x, y) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
drawPointLegend(ctx, options, x, y, null);
}
// eslint-disable-next-line complexity
function drawPointLegend(ctx, options, x, y, w) {
let type, xOffset, yOffset, size, cornerRadius, width, xOffsetW, yOffsetW;
const style = options.pointStyle;
const rotation = options.rotation;
const radius = options.radius;
let rad = (rotation || 0) * RAD_PER_DEG;
if (style && typeof style === 'object') {
type = style.toString();
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rad);
ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
ctx.restore();
return;
}
}
if (isNaN(radius) || radius <= 0) {
return;
}
ctx.beginPath();
switch(style){
// Default includes circle
default:
if (w) {
ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
} else {
ctx.arc(x, y, radius, 0, TAU);
}
ctx.closePath();
break;
case 'triangle':
width = w ? w / 2 : radius;
ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
rad += TWO_THIRDS_PI;
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
rad += TWO_THIRDS_PI;
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
ctx.closePath();
break;
case 'rectRounded':
// NOTE: the rounded rect implementation changed to use `arc` instead of
// `quadraticCurveTo` since it generates better results when rect is
// almost a circle. 0.516 (instead of 0.5) produces results with visually
// closer proportion to the previous impl and it is inscribed in the
// circle with `radius`. For more details, see the following PRs:
// https://github.com/chartjs/Chart.js/issues/5597
// https://github.com/chartjs/Chart.js/issues/5858
cornerRadius = radius * 0.516;
size = radius - cornerRadius;
xOffset = Math.cos(rad + QUARTER_PI) * size;
xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
yOffset = Math.sin(rad + QUARTER_PI) * size;
yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
ctx.closePath();
break;
case 'rect':
if (!rotation) {
size = Math.SQRT1_2 * radius;
width = w ? w / 2 : size;
ctx.rect(x - width, y - size, 2 * width, 2 * size);
break;
}
rad += QUARTER_PI;
/* falls through */ case 'rectRot':
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
ctx.moveTo(x - xOffsetW, y - yOffset);
ctx.lineTo(x + yOffsetW, y - xOffset);
ctx.lineTo(x + xOffsetW, y + yOffset);
ctx.lineTo(x - yOffsetW, y + xOffset);
ctx.closePath();
break;
case 'crossRot':
rad += QUARTER_PI;
/* falls through */ case 'cross':
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
ctx.moveTo(x - xOffsetW, y - yOffset);
ctx.lineTo(x + xOffsetW, y + yOffset);
ctx.moveTo(x + yOffsetW, y - xOffset);
ctx.lineTo(x - yOffsetW, y + xOffset);
break;
case 'star':
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
ctx.moveTo(x - xOffsetW, y - yOffset);
ctx.lineTo(x + xOffsetW, y + yOffset);
ctx.moveTo(x + yOffsetW, y - xOffset);
ctx.lineTo(x - yOffsetW, y + xOffset);
rad += QUARTER_PI;
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
ctx.moveTo(x - xOffsetW, y - yOffset);
ctx.lineTo(x + xOffsetW, y + yOffset);
ctx.moveTo(x + yOffsetW, y - xOffset);
ctx.lineTo(x - yOffsetW, y + xOffset);
break;
case 'line':
xOffset = w ? w / 2 : Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
break;
case 'dash':
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
break;
case false:
ctx.closePath();
break;
}
ctx.fill();
if (options.borderWidth > 0) {
ctx.stroke();
}
}
/**
* Returns true if the point is inside the rectangle
* @param point - The point to test
* @param area - The rectangle
* @param margin - allowed margin
* @private
*/ function _isPointInArea(point, area, margin) {
margin = margin || 0.5; // margin - default is to match rounded decimals
return !area || point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
}
function clipArea(ctx, area) {
ctx.save();
ctx.beginPath();
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
ctx.clip();
}
function unclipArea(ctx) {
ctx.restore();
}
/**
* @private
*/ function _steppedLineTo(ctx, previous, target, flip, mode) {
if (!previous) {
return ctx.lineTo(target.x, target.y);
}
if (mode === 'middle') {
const midpoint = (previous.x + target.x) / 2.0;
ctx.lineTo(midpoint, previous.y);
ctx.lineTo(midpoint, target.y);
} else if (mode === 'after' !== !!flip) {
ctx.lineTo(previous.x, target.y);
} else {
ctx.lineTo(target.x, previous.y);
}
ctx.lineTo(target.x, target.y);
}
/**
* @private
*/ function _bezierCurveTo(ctx, previous, target, flip) {
if (!previous) {
return ctx.lineTo(target.x, target.y);
}
ctx.bezierCurveTo(flip ? previous.cp1x : previous.cp2x, flip ? previous.cp1y : previous.cp2y, flip ? target.cp2x : target.cp1x, flip ? target.cp2y : target.cp1y, target.x, target.y);
}
function setRenderOpts(ctx, opts) {
if (opts.translation) {
ctx.translate(opts.translation[0], opts.translation[1]);
}
if (!isNullOrUndef(opts.rotation)) {
ctx.rotate(opts.rotation);
}
if (opts.color) {
ctx.fillStyle = opts.color;
}
if (opts.textAlign) {
ctx.textAlign = opts.textAlign;
}
if (opts.textBaseline) {
ctx.textBaseline = opts.textBaseline;
}
}
function decorateText(ctx, x, y, line, opts) {
if (opts.strikethrough || opts.underline) {
/**
* Now that IE11 support has been dropped, we can use more
* of the TextMetrics object. The actual bounding boxes
* are unflagged in Chrome, Firefox, Edge, and Safari so they
* can be safely used.
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
*/ const metrics = ctx.measureText(line);
const left = x - metrics.actualBoundingBoxLeft;
const right = x + metrics.actualBoundingBoxRight;
const top = y - metrics.actualBoundingBoxAscent;
const bottom = y + metrics.actualBoundingBoxDescent;
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
ctx.strokeStyle = ctx.fillStyle;
ctx.beginPath();
ctx.lineWidth = opts.decorationWidth || 2;
ctx.moveTo(left, yDecoration);
ctx.lineTo(right, yDecoration);
ctx.stroke();
}
}
function drawBackdrop(ctx, opts) {
const oldColor = ctx.fillStyle;
ctx.fillStyle = opts.color;
ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
ctx.fillStyle = oldColor;
}
/**
* Render text onto the canvas
*/ function renderText(ctx, text, x, y, font, opts = {}) {
const lines = isArray(text) ? text : [
text
];
const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
let i, line;
ctx.save();
ctx.font = font.string;
setRenderOpts(ctx, opts);
for(i = 0; i < lines.length; ++i){
line = lines[i];
if (opts.backdrop) {
drawBackdrop(ctx, opts.backdrop);
}
if (stroke) {
if (opts.strokeColor) {
ctx.strokeStyle = opts.strokeColor;
}
if (!isNullOrUndef(opts.strokeWidth)) {
ctx.lineWidth = opts.strokeWidth;
}
ctx.strokeText(line, x, y, opts.maxWidth);
}
ctx.fillText(line, x, y, opts.maxWidth);
decorateText(ctx, x, y, line, opts);
y += Number(font.lineHeight);
}
ctx.restore();
}
/**
* Add a path of a rectangle with rounded corners to the current sub-path
* @param ctx - Context
* @param rect - Bounding rect
*/ function addRoundedRectPath(ctx, rect) {
const { x , y , w , h , radius } = rect;
// top left arc
ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, 1.5 * PI, PI, true);
// line from top left to bottom left
ctx.lineTo(x, y + h - radius.bottomLeft);
// bottom left arc
ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
// line from bottom left to bottom right
ctx.lineTo(x + w - radius.bottomRight, y + h);
// bottom right arc
ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
// line from bottom right to top right
ctx.lineTo(x + w, y + radius.topRight);
// top right arc
ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
// line from top right to top left
ctx.lineTo(x + radius.topLeft, y);
}
const LINE_HEIGHT = /^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/;
const FONT_STYLE = /^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;
/**
* @alias Chart.helpers.options
* @namespace
*/ /**
* Converts the given line height `value` in pixels for a specific font `size`.
* @param value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
* @param size - The font size (in pixels) used to resolve relative `value`.
* @returns The effective line height in pixels (size * 1.2 if value is invalid).
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
* @since 2.7.0
*/ function toLineHeight(value, size) {
const matches = ('' + value).match(LINE_HEIGHT);
if (!matches || matches[1] === 'normal') {
return size * 1.2;
}
value = +matches[2];
switch(matches[3]){
case 'px':
return value;
case '%':
value /= 100;
break;
}
return size * value;
}
const numberOrZero = (v)=>+v || 0;
function _readValueToProps(value, props) {
const ret = {};
const objProps = isObject(props);
const keys = objProps ? Object.keys(props) : props;
const read = isObject(value) ? objProps ? (prop)=>valueOrDefault(value[prop], value[props[prop]]) : (prop)=>value[prop] : ()=>value;
for (const prop of keys){
ret[prop] = numberOrZero(read(prop));
}
return ret;
}
/**
* Converts the given value into a TRBL object.
* @param value - If a number, set the value to all TRBL component,
* else, if an object, use defined properties and sets undefined ones to 0.
* x / y are shorthands for same value for left/right and top/bottom.
* @returns The padding values (top, right, bottom, left)
* @since 3.0.0
*/ function toTRBL(value) {
return _readValueToProps(value, {
top: 'y',
right: 'x',
bottom: 'y',
left: 'x'
});
}
/**
* Converts the given value into a TRBL corners object (similar with css border-radius).
* @param value - If a number, set the value to all TRBL corner components,
* else, if an object, use defined properties and sets undefined ones to 0.
* @returns The TRBL corner values (topLeft, topRight, bottomLeft, bottomRight)
* @since 3.0.0
*/ function toTRBLCorners(value) {
return _readValueToProps(value, [
'topLeft',
'topRight',
'bottomLeft',
'bottomRight'
]);
}
/**
* Converts the given value into a padding object with pre-computed width/height.
* @param value - If a number, set the value to all TRBL component,
* else, if an object, use defined properties and sets undefined ones to 0.
* x / y are shorthands for same value for left/right and top/bottom.
* @returns The padding values (top, right, bottom, left, width, height)
* @since 2.7.0
*/ function toPadding(value) {
const obj = toTRBL(value);
obj.width = obj.left + obj.right;
obj.height = obj.top + obj.bottom;
return obj;
}
/**
* Parses font options and returns the font object.
* @param options - A object that contains font options to be parsed.
* @param fallback - A object that contains fallback font options.
* @return The font object.
* @private
*/ function toFont(options, fallback) {
options = options || {};
fallback = fallback || defaults.font;
let size = valueOrDefault(options.size, fallback.size);
if (typeof size === 'string') {
size = parseInt(size, 10);
}
let style = valueOrDefault(options.style, fallback.style);
if (style && !('' + style).match(FONT_STYLE)) {
console.warn('Invalid font style specified: "' + style + '"');
style = undefined;
}
const font = {
family: valueOrDefault(options.family, fallback.family),
lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
size,
style,
weight: valueOrDefault(options.weight, fallback.weight),
string: ''
};
font.string = toFontString(font);
return font;
}
/**
* Evaluates the given `inputs` sequentially and returns the first defined value.
* @param inputs - An array of values, falling back to the last value.
* @param context - If defined and the current value is a function, the value
* is called with `context` as first argument and the result becomes the new input.
* @param index - If defined and the current value is an array, the value
* at `index` become the new input.
* @param info - object to return information about resolution in
* @param info.cacheable - Will be set to `false` if option is not cacheable.
* @since 2.7.0
*/ function resolve(inputs, context, index, info) {
let cacheable = true;
let i, ilen, value;
for(i = 0, ilen = inputs.length; i < ilen; ++i){
value = inputs[i];
if (value === undefined) {
continue;
}
if (context !== undefined && typeof value === 'function') {
value = value(context);
cacheable = false;
}
if (index !== undefined && isArray(value)) {
value = value[index % value.length];
cacheable = false;
}
if (value !== undefined) {
if (info && !cacheable) {
info.cacheable = false;
}
return value;
}
}
}
/**
* @param minmax
* @param grace
* @param beginAtZero
* @private
*/ function _addGrace(minmax, grace, beginAtZero) {
const { min , max } = minmax;
const change = toDimension(grace, (max - min) / 2);
const keepZero = (value, add)=>beginAtZero && value === 0 ? 0 : value + add;
return {
min: keepZero(min, -Math.abs(change)),
max: keepZero(max, change)
};
}
function createContext(parentContext, context) {
return Object.assign(Object.create(parentContext), context);
}
/**
* Creates a Proxy for resolving raw values for options.
* @param scopes - The option scopes to look for values, in resolution order
* @param prefixes - The prefixes for values, in resolution order.
* @param rootScopes - The root option scopes
* @param fallback - Parent scopes fallback
* @param getTarget - callback for getting the target for changed values
* @returns Proxy
* @private
*/ function _createResolver(scopes, prefixes = [
''
], rootScopes, fallback, getTarget = ()=>scopes[0]) {
const finalRootScopes = rootScopes || scopes;
if (typeof fallback === 'undefined') {
fallback = _resolve('_fallback', scopes);
}
const cache = {
[Symbol.toStringTag]: 'Object',
_cacheable: true,
_scopes: scopes,
_rootScopes: finalRootScopes,
_fallback: fallback,
_getTarget: getTarget,
override: (scope)=>_createResolver([
scope,
...scopes
], prefixes, finalRootScopes, fallback)
};
return new Proxy(cache, {
/**
* A trap for the delete operator.
*/ deleteProperty (target, prop) {
delete target[prop]; // remove from cache
delete target._keys; // remove cached keys
delete scopes[0][prop]; // remove from top level scope
return true;
},
/**
* A trap for getting property values.
*/ get (target, prop) {
return _cached(target, prop, ()=>_resolveWithPrefixes(prop, prefixes, scopes, target));
},
/**
* A trap for Object.getOwnPropertyDescriptor.
* Also used by Object.hasOwnProperty.
*/ getOwnPropertyDescriptor (target, prop) {
return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
},
/**
* A trap for Object.getPrototypeOf.
*/ getPrototypeOf () {
return Reflect.getPrototypeOf(scopes[0]);
},
/**
* A trap for the in operator.
*/ has (target, prop) {
return getKeysFromAllScopes(target).includes(prop);
},
/**
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
*/ ownKeys (target) {
return getKeysFromAllScopes(target);
},
/**
* A trap for setting property values.
*/ set (target, prop, value) {
const storage = target._storage || (target._storage = getTarget());
target[prop] = storage[prop] = value; // set to top level scope + cache
delete target._keys; // remove cached keys
return true;
}
});
}
/**
* Returns an Proxy for resolving option values with context.
* @param proxy - The Proxy returned by `_createResolver`
* @param context - Context object for scriptable/indexable options
* @param subProxy - The proxy provided for scriptable options
* @param descriptorDefaults - Defaults for descriptors
* @private
*/ function _attachContext(proxy, context, subProxy, descriptorDefaults) {
const cache = {
_cacheable: false,
_proxy: proxy,
_context: context,
_subProxy: subProxy,
_stack: new Set(),
_descriptors: _descriptors(proxy, descriptorDefaults),
setContext: (ctx)=>_attachContext(proxy, ctx, subProxy, descriptorDefaults),
override: (scope)=>_attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
};
return new Proxy(cache, {
/**
* A trap for the delete operator.
*/ deleteProperty (target, prop) {
delete target[prop]; // remove from cache
delete proxy[prop]; // remove from proxy
return true;
},
/**
* A trap for getting property values.
*/ get (target, prop, receiver) {
return _cached(target, prop, ()=>_resolveWithContext(target, prop, receiver));
},
/**
* A trap for Object.getOwnPropertyDescriptor.
* Also used by Object.hasOwnProperty.
*/ getOwnPropertyDescriptor (target, prop) {
return target._descriptors.allKeys ? Reflect.has(proxy, prop) ? {
enumerable: true,
configurable: true
} : undefined : Reflect.getOwnPropertyDescriptor(proxy, prop);
},
/**
* A trap for Object.getPrototypeOf.
*/ getPrototypeOf () {
return Reflect.getPrototypeOf(proxy);
},
/**
* A trap for the in operator.
*/ has (target, prop) {
return Reflect.has(proxy, prop);
},
/**
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
*/ ownKeys () {
return Reflect.ownKeys(proxy);
},
/**
* A trap for setting property values.
*/ set (target, prop, value) {
proxy[prop] = value; // set to proxy
delete target[prop]; // remove from cache
return true;
}
});
}
/**
* @private
*/ function _descriptors(proxy, defaults = {
scriptable: true,
indexable: true
}) {
const { _scriptable =defaults.scriptable , _indexable =defaults.indexable , _allKeys =defaults.allKeys } = proxy;
return {
allKeys: _allKeys,
scriptable: _scriptable,
indexable: _indexable,
isScriptable: isFunction(_scriptable) ? _scriptable : ()=>_scriptable,
isIndexable: isFunction(_indexable) ? _indexable : ()=>_indexable
};
}
const readKey = (prefix, name)=>prefix ? prefix + _capitalize(name) : name;
const needsSubResolver = (prop, value)=>isObject(value) && prop !== 'adapters' && (Object.getPrototypeOf(value) === null || value.constructor === Object);
function _cached(target, prop, resolve) {
if (Object.prototype.hasOwnProperty.call(target, prop) || prop === 'constructor') {
return target[prop];
}
const value = resolve();
// cache the resolved value
target[prop] = value;
return value;
}
function _resolveWithContext(target, prop, receiver) {
const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
let value = _proxy[prop]; // resolve from proxy
// resolve with context
if (isFunction(value) && descriptors.isScriptable(prop)) {
value = _resolveScriptable(prop, value, target, receiver);
}
if (isArray(value) && value.length) {
value = _resolveArray(prop, value, target, descriptors.isIndexable);
}
if (needsSubResolver(prop, value)) {
// if the resolved value is an object, create a sub resolver for it
value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
}
return value;
}
function _resolveScriptable(prop, getValue, target, receiver) {
const { _proxy , _context , _subProxy , _stack } = target;
if (_stack.has(prop)) {
throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
}
_stack.add(prop);
let value = getValue(_context, _subProxy || receiver);
_stack.delete(prop);
if (needsSubResolver(prop, value)) {
// When scriptable option returns an object, create a resolver on that.
value = createSubResolver(_proxy._scopes, _proxy, prop, value);
}
return value;
}
function _resolveArray(prop, value, target, isIndexable) {
const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
if (typeof _context.index !== 'undefined' && isIndexable(prop)) {
return value[_context.index % value.length];
} else if (isObject(value[0])) {
// Array of objects, return array or resolvers
const arr = value;
const scopes = _proxy._scopes.filter((s)=>s !== arr);
value = [];
for (const item of arr){
const resolver = createSubResolver(scopes, _proxy, prop, item);
value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
}
}
return value;
}
function resolveFallback(fallback, prop, value) {
return isFunction(fallback) ? fallback(prop, value) : fallback;
}
const getScope = (key, parent)=>key === true ? parent : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
function addScopes(set, parentScopes, key, parentFallback, value) {
for (const parent of parentScopes){
const scope = getScope(key, parent);
if (scope) {
set.add(scope);
const fallback = resolveFallback(scope._fallback, key, value);
if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {
// When we reach the descriptor that defines a new _fallback, return that.
// The fallback will resume to that new scope.
return fallback;
}
} else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {
// Fallback to `false` results to `false`, when falling back to different key.
// For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`
return null;
}
}
return false;
}
function createSubResolver(parentScopes, resolver, prop, value) {
const rootScopes = resolver._rootScopes;
const fallback = resolveFallback(resolver._fallback, prop, value);
const allScopes = [
...parentScopes,
...rootScopes
];
const set = new Set();
set.add(value);
let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
if (key === null) {
return false;
}
if (typeof fallback !== 'undefined' && fallback !== prop) {
key = addScopesFromKey(set, allScopes, fallback, key, value);
if (key === null) {
return false;
}
}
return _createResolver(Array.from(set), [
''
], rootScopes, fallback, ()=>subGetTarget(resolver, prop, value));
}
function addScopesFromKey(set, allScopes, key, fallback, item) {
while(key){
key = addScopes(set, allScopes, key, fallback, item);
}
return key;
}
function subGetTarget(resolver, prop, value) {
const parent = resolver._getTarget();
if (!(prop in parent)) {
parent[prop] = {};
}
const target = parent[prop];
if (isArray(target) && isObject(value)) {
// For array of objects, the object is used to store updated values
return value;
}
return target || {};
}
function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
let value;
for (const prefix of prefixes){
value = _resolve(readKey(prefix, prop), scopes);
if (typeof value !== 'undefined') {
return needsSubResolver(prop, value) ? createSubResolver(scopes, proxy, prop, value) : value;
}
}
}
function _resolve(key, scopes) {
for (const scope of scopes){
if (!scope) {
continue;
}
const value = scope[key];
if (typeof value !== 'undefined') {
return value;
}
}
}
function getKeysFromAllScopes(target) {
let keys = target._keys;
if (!keys) {
keys = target._keys = resolveKeysFromAllScopes(target._scopes);
}
return keys;
}
function resolveKeysFromAllScopes(scopes) {
const set = new Set();
for (const scope of scopes){
for (const key of Object.keys(scope).filter((k)=>!k.startsWith('_'))){
set.add(key);
}
}
return Array.from(set);
}
function _parseObjectDataRadialScale(meta, data, start, count) {
const { iScale } = meta;
const { key ='r' } = this._parsing;
const parsed = new Array(count);
let i, ilen, index, item;
for(i = 0, ilen = count; i < ilen; ++i){
index = i + start;
item = data[index];
parsed[i] = {
r: iScale.parse(resolveObjectKey(item, key), index)
};
}
return parsed;
}
const EPSILON = Number.EPSILON || 1e-14;
const getPoint = (points, i)=>i < points.length && !points[i].skip && points[i];
const getValueAxis = (indexAxis)=>indexAxis === 'x' ? 'y' : 'x';
function splineCurve(firstPoint, middlePoint, afterPoint, t) {
// Props to Rob Spencer at scaled innovation for his post on splining between points
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
// This function must also respect "skipped" points
const previous = firstPoint.skip ? middlePoint : firstPoint;
const current = middlePoint;
const next = afterPoint.skip ? middlePoint : afterPoint;
const d01 = distanceBetweenPoints(current, previous);
const d12 = distanceBetweenPoints(next, current);
let s01 = d01 / (d01 + d12);
let s12 = d12 / (d01 + d12);
// If all points are the same, s01 & s02 will be inf
s01 = isNaN(s01) ? 0 : s01;
s12 = isNaN(s12) ? 0 : s12;
const fa = t * s01; // scaling factor for triangle Ta
const fb = t * s12;
return {
previous: {
x: current.x - fa * (next.x - previous.x),
y: current.y - fa * (next.y - previous.y)
},
next: {
x: current.x + fb * (next.x - previous.x),
y: current.y + fb * (next.y - previous.y)
}
};
}
/**
* Adjust tangents to ensure monotonic properties
*/ function monotoneAdjust(points, deltaK, mK) {
const pointsLen = points.length;
let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
let pointAfter = getPoint(points, 0);
for(let i = 0; i < pointsLen - 1; ++i){
pointCurrent = pointAfter;
pointAfter = getPoint(points, i + 1);
if (!pointCurrent || !pointAfter) {
continue;
}
if (almostEquals(deltaK[i], 0, EPSILON)) {
mK[i] = mK[i + 1] = 0;
continue;
}
alphaK = mK[i] / deltaK[i];
betaK = mK[i + 1] / deltaK[i];
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
if (squaredMagnitude <= 9) {
continue;
}
tauK = 3 / Math.sqrt(squaredMagnitude);
mK[i] = alphaK * tauK * deltaK[i];
mK[i + 1] = betaK * tauK * deltaK[i];
}
}
function monotoneCompute(points, mK, indexAxis = 'x') {
const valueAxis = getValueAxis(indexAxis);
const pointsLen = points.length;
let delta, pointBefore, pointCurrent;
let pointAfter = getPoint(points, 0);
for(let i = 0; i < pointsLen; ++i){
pointBefore = pointCurrent;
pointCurrent = pointAfter;
pointAfter = getPoint(points, i + 1);
if (!pointCurrent) {
continue;
}
const iPixel = pointCurrent[indexAxis];
const vPixel = pointCurrent[valueAxis];
if (pointBefore) {
delta = (iPixel - pointBefore[indexAxis]) / 3;
pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
}
if (pointAfter) {
delta = (pointAfter[indexAxis] - iPixel) / 3;
pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
}
}
}
/**
* This function calculates Bézier control points in a similar way than |splineCurve|,
* but preserves monotonicity of the provided data and ensures no local extremums are added
* between the dataset discrete points due to the interpolation.
* See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
*/ function splineCurveMonotone(points, indexAxis = 'x') {
const valueAxis = getValueAxis(indexAxis);
const pointsLen = points.length;
const deltaK = Array(pointsLen).fill(0);
const mK = Array(pointsLen);
// Calculate slopes (deltaK) and initialize tangents (mK)
let i, pointBefore, pointCurrent;
let pointAfter = getPoint(points, 0);
for(i = 0; i < pointsLen; ++i){
pointBefore = pointCurrent;
pointCurrent = pointAfter;
pointAfter = getPoint(points, i + 1);
if (!pointCurrent) {
continue;
}
if (pointAfter) {
const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
}
mK[i] = !pointBefore ? deltaK[i] : !pointAfter ? deltaK[i - 1] : sign(deltaK[i - 1]) !== sign(deltaK[i]) ? 0 : (deltaK[i - 1] + deltaK[i]) / 2;
}
monotoneAdjust(points, deltaK, mK);
monotoneCompute(points, mK, indexAxis);
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
function capBezierPoints(points, area) {
let i, ilen, point, inArea, inAreaPrev;
let inAreaNext = _isPointInArea(points[0], area);
for(i = 0, ilen = points.length; i < ilen; ++i){
inAreaPrev = inArea;
inArea = inAreaNext;
inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
if (!inArea) {
continue;
}
point = points[i];
if (inAreaPrev) {
point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
}
if (inAreaNext) {
point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
}
}
}
/**
* @private
*/ function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
let i, ilen, point, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (options.spanGaps) {
points = points.filter((pt)=>!pt.skip);
}
if (options.cubicInterpolationMode === 'monotone') {
splineCurveMonotone(points, indexAxis);
} else {
let prev = loop ? points[points.length - 1] : points[0];
for(i = 0, ilen = points.length; i < ilen; ++i){
point = points[i];
controlPoints = splineCurve(prev, point, points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], options.tension);
point.cp1x = controlPoints.previous.x;
point.cp1y = controlPoints.previous.y;
point.cp2x = controlPoints.next.x;
point.cp2y = controlPoints.next.y;
prev = point;
}
}
if (options.capBezierPoints) {
capBezierPoints(points, area);
}
}
/**
* @private
*/ function _isDomSupported() {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
/**
* @private
*/ function _getParentNode(domNode) {
let parent = domNode.parentNode;
if (parent && parent.toString() === '[object ShadowRoot]') {
parent = parent.host;
}
return parent;
}
/**
* convert max-width/max-height values that may be percentages into a number
* @private
*/ function parseMaxStyle(styleValue, node, parentProperty) {
let valueInPixels;
if (typeof styleValue === 'string') {
valueInPixels = parseInt(styleValue, 10);
if (styleValue.indexOf('%') !== -1) {
// percentage * size in dimension
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
}
} else {
valueInPixels = styleValue;
}
return valueInPixels;
}
const helpers_dataset_getComputedStyle = (element)=>element.ownerDocument.defaultView.getComputedStyle(element, null);
function getStyle(el, property) {
return helpers_dataset_getComputedStyle(el).getPropertyValue(property);
}
const positions = [
'top',
'right',
'bottom',
'left'
];
function getPositionedStyle(styles, style, suffix) {
const result = {};
suffix = suffix ? '-' + suffix : '';
for(let i = 0; i < 4; i++){
const pos = positions[i];
result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
}
result.width = result.left + result.right;
result.height = result.top + result.bottom;
return result;
}
const useOffsetPos = (x, y, target)=>(x > 0 || y > 0) && (!target || !target.shadowRoot);
/**
* @param e
* @param canvas
* @returns Canvas position
*/ function getCanvasPosition(e, canvas) {
const touches = e.touches;
const source = touches && touches.length ? touches[0] : e;
const { offsetX , offsetY } = source;
let box = false;
let x, y;
if (useOffsetPos(offsetX, offsetY, e.target)) {
x = offsetX;
y = offsetY;
} else {
const rect = canvas.getBoundingClientRect();
x = source.clientX - rect.left;
y = source.clientY - rect.top;
box = true;
}
return {
x,
y,
box
};
}
/**
* Gets an event's x, y coordinates, relative to the chart area
* @param event
* @param chart
* @returns x and y coordinates of the event
*/ function getRelativePosition(event, chart) {
if ('native' in event) {
return event;
}
const { canvas , currentDevicePixelRatio } = chart;
const style = helpers_dataset_getComputedStyle(canvas);
const borderBox = style.boxSizing === 'border-box';
const paddings = getPositionedStyle(style, 'padding');
const borders = getPositionedStyle(style, 'border', 'width');
const { x , y , box } = getCanvasPosition(event, canvas);
const xOffset = paddings.left + (box && borders.left);
const yOffset = paddings.top + (box && borders.top);
let { width , height } = chart;
if (borderBox) {
width -= paddings.width + borders.width;
height -= paddings.height + borders.height;
}
return {
x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
};
}
function getContainerSize(canvas, width, height) {
let maxWidth, maxHeight;
if (width === undefined || height === undefined) {
const container = canvas && _getParentNode(canvas);
if (!container) {
width = canvas.clientWidth;
height = canvas.clientHeight;
} else {
const rect = container.getBoundingClientRect(); // this is the border box of the container
const containerStyle = helpers_dataset_getComputedStyle(container);
const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
const containerPadding = getPositionedStyle(containerStyle, 'padding');
width = rect.width - containerPadding.width - containerBorder.width;
height = rect.height - containerPadding.height - containerBorder.height;
maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
}
}
return {
width,
height,
maxWidth: maxWidth || INFINITY,
maxHeight: maxHeight || INFINITY
};
}
const round1 = (v)=>Math.round(v * 10) / 10;
// eslint-disable-next-line complexity
function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
const style = helpers_dataset_getComputedStyle(canvas);
const margins = getPositionedStyle(style, 'margin');
const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
let { width , height } = containerSize;
if (style.boxSizing === 'content-box') {
const borders = getPositionedStyle(style, 'border', 'width');
const paddings = getPositionedStyle(style, 'padding');
width -= paddings.width + borders.width;
height -= paddings.height + borders.height;
}
width = Math.max(0, width - margins.width);
height = Math.max(0, aspectRatio ? width / aspectRatio : height - margins.height);
width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
if (width && !height) {
// https://github.com/chartjs/Chart.js/issues/4659
// If the canvas has width, but no height, default to aspectRatio of 2 (canvas default)
height = round1(width / 2);
}
const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;
if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
height = containerSize.height;
width = round1(Math.floor(height * aspectRatio));
}
return {
width,
height
};
}
/**
* @param chart
* @param forceRatio
* @param forceStyle
* @returns True if the canvas context size or transformation has changed.
*/ function retinaScale(chart, forceRatio, forceStyle) {
const pixelRatio = forceRatio || 1;
const deviceHeight = round1(chart.height * pixelRatio);
const deviceWidth = round1(chart.width * pixelRatio);
chart.height = round1(chart.height);
chart.width = round1(chart.width);
const canvas = chart.canvas;
// If no style has been set on the canvas, the render size is used as display size,
// making the chart visually bigger, so let's enforce it to the "correct" values.
// See https://github.com/chartjs/Chart.js/issues/3575
if (canvas.style && (forceStyle || !canvas.style.height && !canvas.style.width)) {
canvas.style.height = `${chart.height}px`;
canvas.style.width = `${chart.width}px`;
}
if (chart.currentDevicePixelRatio !== pixelRatio || canvas.height !== deviceHeight || canvas.width !== deviceWidth) {
chart.currentDevicePixelRatio = pixelRatio;
canvas.height = deviceHeight;
canvas.width = deviceWidth;
chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
return true;
}
return false;
}
/**
* Detects support for options object argument in addEventListener.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
* @private
*/ const supportsEventListenerOptions = function() {
let passiveSupported = false;
try {
const options = {
get passive () {
passiveSupported = true;
return false;
}
};
if (_isDomSupported()) {
window.addEventListener('test', null, options);
window.removeEventListener('test', null, options);
}
} catch (e) {
// continue regardless of error
}
return passiveSupported;
}();
/**
* The "used" size is the final value of a dimension property after all calculations have
* been performed. This method uses the computed style of `element` but returns undefined
* if the computed style is not expressed in pixels. That can happen in some cases where
* `element` has a size relative to its parent and this last one is not yet displayed,
* for example because of `display: none` on a parent node.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
* @returns Size in pixels or undefined if unknown.
*/ function readUsedSize(element, property) {
const value = getStyle(element, property);
const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
return matches ? +matches[1] : undefined;
}
/**
* @private
*/ function _pointInLine(p1, p2, t, mode) {
return {
x: p1.x + t * (p2.x - p1.x),
y: p1.y + t * (p2.y - p1.y)
};
}
/**
* @private
*/ function _steppedInterpolation(p1, p2, t, mode) {
return {
x: p1.x + t * (p2.x - p1.x),
y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y : mode === 'after' ? t < 1 ? p1.y : p2.y : t > 0 ? p2.y : p1.y
};
}
/**
* @private
*/ function _bezierInterpolation(p1, p2, t, mode) {
const cp1 = {
x: p1.cp2x,
y: p1.cp2y
};
const cp2 = {
x: p2.cp1x,
y: p2.cp1y
};
const a = _pointInLine(p1, cp1, t);
const b = _pointInLine(cp1, cp2, t);
const c = _pointInLine(cp2, p2, t);
const d = _pointInLine(a, b, t);
const e = _pointInLine(b, c, t);
return _pointInLine(d, e, t);
}
const getRightToLeftAdapter = function(rectX, width) {
return {
x (x) {
return rectX + rectX + width - x;
},
setWidth (w) {
width = w;
},
textAlign (align) {
if (align === 'center') {
return align;
}
return align === 'right' ? 'left' : 'right';
},
xPlus (x, value) {
return x - value;
},
leftForLtr (x, itemWidth) {
return x - itemWidth;
}
};
};
const getLeftToRightAdapter = function() {
return {
x (x) {
return x;
},
setWidth (w) {},
textAlign (align) {
return align;
},
xPlus (x, value) {
return x + value;
},
leftForLtr (x, _itemWidth) {
return x;
}
};
};
function getRtlAdapter(rtl, rectX, width) {
return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
}
function overrideTextDirection(ctx, direction) {
let style, original;
if (direction === 'ltr' || direction === 'rtl') {
style = ctx.canvas.style;
original = [
style.getPropertyValue('direction'),
style.getPropertyPriority('direction')
];
style.setProperty('direction', direction, 'important');
ctx.prevTextDirection = original;
}
}
function restoreTextDirection(ctx, original) {
if (original !== undefined) {
delete ctx.prevTextDirection;
ctx.canvas.style.setProperty('direction', original[0], original[1]);
}
}
function propertyFn(property) {
if (property === 'angle') {
return {
between: _angleBetween,
compare: _angleDiff,
normalize: _normalizeAngle
};
}
return {
between: _isBetween,
compare: (a, b)=>a - b,
normalize: (x)=>x
};
}
function normalizeSegment({ start , end , count , loop , style }) {
return {
start: start % count,
end: end % count,
loop: loop && (end - start + 1) % count === 0,
style
};
}
function getSegment(segment, points, bounds) {
const { property , start: startBound , end: endBound } = bounds;
const { between , normalize } = propertyFn(property);
const count = points.length;
let { start , end , loop } = segment;
let i, ilen;
if (loop) {
start += count;
end += count;
for(i = 0, ilen = count; i < ilen; ++i){
if (!between(normalize(points[start % count][property]), startBound, endBound)) {
break;
}
start--;
end--;
}
start %= count;
end %= count;
}
if (end < start) {
end += count;
}
return {
start,
end,
loop,
style: segment.style
};
}
function _boundSegment(segment, points, bounds) {
if (!bounds) {
return [
segment
];
}
const { property , start: startBound , end: endBound } = bounds;
const count = points.length;
const { compare , between , normalize } = propertyFn(property);
const { start , end , loop , style } = getSegment(segment, points, bounds);
const result = [];
let inside = false;
let subStart = null;
let value, point, prevValue;
const startIsBefore = ()=>between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
const endIsBefore = ()=>compare(endBound, value) === 0 || between(endBound, prevValue, value);
const shouldStart = ()=>inside || startIsBefore();
const shouldStop = ()=>!inside || endIsBefore();
for(let i = start, prev = start; i <= end; ++i){
point = points[i % count];
if (point.skip) {
continue;
}
value = normalize(point[property]);
if (value === prevValue) {
continue;
}
inside = between(value, startBound, endBound);
if (subStart === null && shouldStart()) {
subStart = compare(value, startBound) === 0 ? i : prev;
}
if (subStart !== null && shouldStop()) {
result.push(normalizeSegment({
start: subStart,
end: i,
loop,
count,
style
}));
subStart = null;
}
prev = i;
prevValue = value;
}
if (subStart !== null) {
result.push(normalizeSegment({
start: subStart,
end,
loop,
count,
style
}));
}
return result;
}
function _boundSegments(line, bounds) {
const result = [];
const segments = line.segments;
for(let i = 0; i < segments.length; i++){
const sub = _boundSegment(segments[i], line.points, bounds);
if (sub.length) {
result.push(...sub);
}
}
return result;
}
function findStartAndEnd(points, count, loop, spanGaps) {
let start = 0;
let end = count - 1;
if (loop && !spanGaps) {
while(start < count && !points[start].skip){
start++;
}
}
while(start < count && points[start].skip){
start++;
}
start %= count;
if (loop) {
end += start;
}
while(end > start && points[end % count].skip){
end--;
}
end %= count;
return {
start,
end
};
}
function solidSegments(points, start, max, loop) {
const count = points.length;
const result = [];
let last = start;
let prev = points[start];
let end;
for(end = start + 1; end <= max; ++end){
const cur = points[end % count];
if (cur.skip || cur.stop) {
if (!prev.skip) {
loop = false;
result.push({
start: start % count,
end: (end - 1) % count,
loop
});
start = last = cur.stop ? end : null;
}
} else {
last = end;
if (prev.skip) {
start = end;
}
}
prev = cur;
}
if (last !== null) {
result.push({
start: start % count,
end: last % count,
loop
});
}
return result;
}
function _computeSegments(line, segmentOptions) {
const points = line.points;
const spanGaps = line.options.spanGaps;
const count = points.length;
if (!count) {
return [];
}
const loop = !!line._loop;
const { start , end } = findStartAndEnd(points, count, loop, spanGaps);
if (spanGaps === true) {
return splitByStyles(line, [
{
start,
end,
loop
}
], points, segmentOptions);
}
const max = end < start ? end + count : end;
const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
}
function splitByStyles(line, segments, points, segmentOptions) {
if (!segmentOptions || !segmentOptions.setContext || !points) {
return segments;
}
return doSplitByStyles(line, segments, points, segmentOptions);
}
function doSplitByStyles(line, segments, points, segmentOptions) {
const chartContext = line._chart.getContext();
const baseStyle = readStyle(line.options);
const { _datasetIndex: datasetIndex , options: { spanGaps } } = line;
const count = points.length;
const result = [];
let prevStyle = baseStyle;
let start = segments[0].start;
let i = start;
function addStyle(s, e, l, st) {
const dir = spanGaps ? -1 : 1;
if (s === e) {
return;
}
s += count;
while(points[s % count].skip){
s -= dir;
}
while(points[e % count].skip){
e += dir;
}
if (s % count !== e % count) {
result.push({
start: s % count,
end: e % count,
loop: l,
style: st
});
prevStyle = st;
start = e % count;
}
}
for (const segment of segments){
start = spanGaps ? start : segment.start;
let prev = points[start % count];
let style;
for(i = start + 1; i <= segment.end; i++){
const pt = points[i % count];
style = readStyle(segmentOptions.setContext(createContext(chartContext, {
type: 'segment',
p0: prev,
p1: pt,
p0DataIndex: (i - 1) % count,
p1DataIndex: i % count,
datasetIndex
})));
if (styleChanged(style, prevStyle)) {
addStyle(start, i - 1, segment.loop, prevStyle);
}
prev = pt;
prevStyle = style;
}
if (start < i - 1) {
addStyle(start, i - 1, segment.loop, prevStyle);
}
}
return result;
}
function readStyle(options) {
return {
backgroundColor: options.backgroundColor,
borderCapStyle: options.borderCapStyle,
borderDash: options.borderDash,
borderDashOffset: options.borderDashOffset,
borderJoinStyle: options.borderJoinStyle,
borderWidth: options.borderWidth,
borderColor: options.borderColor
};
}
function styleChanged(style, prevStyle) {
if (!prevStyle) {
return false;
}
const cache = [];
const replacer = function(key, value) {
if (!isPatternOrGradient(value)) {
return value;
}
if (!cache.includes(value)) {
cache.push(value);
}
return cache.indexOf(value);
};
return JSON.stringify(style, replacer) !== JSON.stringify(prevStyle, replacer);
}
function getSizeForArea(scale, chartArea, field) {
return scale.options.clip ? scale[field] : chartArea[field];
}
function getDatasetArea(meta, chartArea) {
const { xScale , yScale } = meta;
if (xScale && yScale) {
return {
left: getSizeForArea(xScale, chartArea, 'left'),
right: getSizeForArea(xScale, chartArea, 'right'),
top: getSizeForArea(yScale, chartArea, 'top'),
bottom: getSizeForArea(yScale, chartArea, 'bottom')
};
}
return chartArea;
}
function getDatasetClipArea(chart, meta) {
const clip = meta._clip;
if (clip.disabled) {
return false;
}
const area = getDatasetArea(meta, chart.chartArea);
return {
left: clip.left === false ? 0 : area.left - (clip.left === true ? 0 : clip.left),
right: clip.right === false ? chart.width : area.right + (clip.right === true ? 0 : clip.right),
top: clip.top === false ? 0 : area.top - (clip.top === true ? 0 : clip.top),
bottom: clip.bottom === false ? chart.height : area.bottom + (clip.bottom === true ? 0 : clip.bottom)
};
}
//# sourceMappingURL=helpers.dataset.js.map
;// ../../node_modules/chart.js/dist/chart.js
/*!
* Chart.js v4.5.1
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT License
*/
class Animator {
constructor(){
this._request = null;
this._charts = new Map();
this._running = false;
this._lastDate = undefined;
}
_notify(chart, anims, date, type) {
const callbacks = anims.listeners[type];
const numSteps = anims.duration;
callbacks.forEach((fn)=>fn({
chart,
initial: anims.initial,
numSteps,
currentStep: Math.min(date - anims.start, numSteps)
}));
}
_refresh() {
if (this._request) {
return;
}
this._running = true;
this._request = requestAnimFrame.call(window, ()=>{
this._update();
this._request = null;
if (this._running) {
this._refresh();
}
});
}
_update(date = Date.now()) {
let remaining = 0;
this._charts.forEach((anims, chart)=>{
if (!anims.running || !anims.items.length) {
return;
}
const items = anims.items;
let i = items.length - 1;
let draw = false;
let item;
for(; i >= 0; --i){
item = items[i];
if (item._active) {
if (item._total > anims.duration) {
anims.duration = item._total;
}
item.tick(date);
draw = true;
} else {
items[i] = items[items.length - 1];
items.pop();
}
}
if (draw) {
chart.draw();
this._notify(chart, anims, date, 'progress');
}
if (!items.length) {
anims.running = false;
this._notify(chart, anims, date, 'complete');
anims.initial = false;
}
remaining += items.length;
});
this._lastDate = date;
if (remaining === 0) {
this._running = false;
}
}
_getAnims(chart) {
const charts = this._charts;
let anims = charts.get(chart);
if (!anims) {
anims = {
running: false,
initial: true,
items: [],
listeners: {
complete: [],
progress: []
}
};
charts.set(chart, anims);
}
return anims;
}
listen(chart, event, cb) {
this._getAnims(chart).listeners[event].push(cb);
}
add(chart, items) {
if (!items || !items.length) {
return;
}
this._getAnims(chart).items.push(...items);
}
has(chart) {
return this._getAnims(chart).items.length > 0;
}
start(chart) {
const anims = this._charts.get(chart);
if (!anims) {
return;
}
anims.running = true;
anims.start = Date.now();
anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
this._refresh();
}
running(chart) {
if (!this._running) {
return false;
}
const anims = this._charts.get(chart);
if (!anims || !anims.running || !anims.items.length) {
return false;
}
return true;
}
stop(chart) {
const anims = this._charts.get(chart);
if (!anims || !anims.items.length) {
return;
}
const items = anims.items;
let i = items.length - 1;
for(; i >= 0; --i){
items[i].cancel();
}
anims.items = [];
this._notify(chart, anims, Date.now(), 'complete');
}
remove(chart) {
return this._charts.delete(chart);
}
}
var animator = /* #__PURE__ */ new Animator();
const transparent = 'transparent';
const interpolators = {
boolean (from, to, factor) {
return factor > 0.5 ? to : from;
},
color (from, to, factor) {
const c0 = color(from || transparent);
const c1 = c0.valid && color(to || transparent);
return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
},
number (from, to, factor) {
return from + (to - from) * factor;
}
};
class Animation {
constructor(cfg, target, prop, to){
const currentValue = target[prop];
to = resolve([
cfg.to,
to,
currentValue,
cfg.from
]);
const from = resolve([
cfg.from,
currentValue,
to
]);
this._active = true;
this._fn = cfg.fn || interpolators[cfg.type || typeof from];
this._easing = effects[cfg.easing] || effects.linear;
this._start = Math.floor(Date.now() + (cfg.delay || 0));
this._duration = this._total = Math.floor(cfg.duration);
this._loop = !!cfg.loop;
this._target = target;
this._prop = prop;
this._from = from;
this._to = to;
this._promises = undefined;
}
active() {
return this._active;
}
update(cfg, to, date) {
if (this._active) {
this._notify(false);
const currentValue = this._target[this._prop];
const elapsed = date - this._start;
const remain = this._duration - elapsed;
this._start = date;
this._duration = Math.floor(Math.max(remain, cfg.duration));
this._total += elapsed;
this._loop = !!cfg.loop;
this._to = resolve([
cfg.to,
to,
currentValue,
cfg.from
]);
this._from = resolve([
cfg.from,
currentValue,
to
]);
}
}
cancel() {
if (this._active) {
this.tick(Date.now());
this._active = false;
this._notify(false);
}
}
tick(date) {
const elapsed = date - this._start;
const duration = this._duration;
const prop = this._prop;
const from = this._from;
const loop = this._loop;
const to = this._to;
let factor;
this._active = from !== to && (loop || elapsed < duration);
if (!this._active) {
this._target[prop] = to;
this._notify(true);
return;
}
if (elapsed < 0) {
this._target[prop] = from;
return;
}
factor = elapsed / duration % 2;
factor = loop && factor > 1 ? 2 - factor : factor;
factor = this._easing(Math.min(1, Math.max(0, factor)));
this._target[prop] = this._fn(from, to, factor);
}
wait() {
const promises = this._promises || (this._promises = []);
return new Promise((res, rej)=>{
promises.push({
res,
rej
});
});
}
_notify(resolved) {
const method = resolved ? 'res' : 'rej';
const promises = this._promises || [];
for(let i = 0; i < promises.length; i++){
promises[i][method]();
}
}
}
class Animations {
constructor(chart, config){
this._chart = chart;
this._properties = new Map();
this.configure(config);
}
configure(config) {
if (!isObject(config)) {
return;
}
const animationOptions = Object.keys(defaults.animation);
const animatedProps = this._properties;
Object.getOwnPropertyNames(config).forEach((key)=>{
const cfg = config[key];
if (!isObject(cfg)) {
return;
}
const resolved = {};
for (const option of animationOptions){
resolved[option] = cfg[option];
}
(isArray(cfg.properties) && cfg.properties || [
key
]).forEach((prop)=>{
if (prop === key || !animatedProps.has(prop)) {
animatedProps.set(prop, resolved);
}
});
});
}
_animateOptions(target, values) {
const newOptions = values.options;
const options = resolveTargetOptions(target, newOptions);
if (!options) {
return [];
}
const animations = this._createAnimations(options, newOptions);
if (newOptions.$shared) {
awaitAll(target.options.$animations, newOptions).then(()=>{
target.options = newOptions;
}, ()=>{
});
}
return animations;
}
_createAnimations(target, values) {
const animatedProps = this._properties;
const animations = [];
const running = target.$animations || (target.$animations = {});
const props = Object.keys(values);
const date = Date.now();
let i;
for(i = props.length - 1; i >= 0; --i){
const prop = props[i];
if (prop.charAt(0) === '$') {
continue;
}
if (prop === 'options') {
animations.push(...this._animateOptions(target, values));
continue;
}
const value = values[prop];
let animation = running[prop];
const cfg = animatedProps.get(prop);
if (animation) {
if (cfg && animation.active()) {
animation.update(cfg, value, date);
continue;
} else {
animation.cancel();
}
}
if (!cfg || !cfg.duration) {
target[prop] = value;
continue;
}
running[prop] = animation = new Animation(cfg, target, prop, value);
animations.push(animation);
}
return animations;
}
update(target, values) {
if (this._properties.size === 0) {
Object.assign(target, values);
return;
}
const animations = this._createAnimations(target, values);
if (animations.length) {
animator.add(this._chart, animations);
return true;
}
}
}
function awaitAll(animations, properties) {
const running = [];
const keys = Object.keys(properties);
for(let i = 0; i < keys.length; i++){
const anim = animations[keys[i]];
if (anim && anim.active()) {
running.push(anim.wait());
}
}
return Promise.all(running);
}
function resolveTargetOptions(target, newOptions) {
if (!newOptions) {
return;
}
let options = target.options;
if (!options) {
target.options = newOptions;
return;
}
if (options.$shared) {
target.options = options = Object.assign({}, options, {
$shared: false,
$animations: {}
});
}
return options;
}
function scaleClip(scale, allowedOverflow) {
const opts = scale && scale.options || {};
const reverse = opts.reverse;
const min = opts.min === undefined ? allowedOverflow : 0;
const max = opts.max === undefined ? allowedOverflow : 0;
return {
start: reverse ? max : min,
end: reverse ? min : max
};
}
function defaultClip(xScale, yScale, allowedOverflow) {
if (allowedOverflow === false) {
return false;
}
const x = scaleClip(xScale, allowedOverflow);
const y = scaleClip(yScale, allowedOverflow);
return {
top: y.end,
right: x.end,
bottom: y.start,
left: x.start
};
}
function toClip(value) {
let t, r, b, l;
if (isObject(value)) {
t = value.top;
r = value.right;
b = value.bottom;
l = value.left;
} else {
t = r = b = l = value;
}
return {
top: t,
right: r,
bottom: b,
left: l,
disabled: value === false
};
}
function getSortedDatasetIndices(chart, filterVisible) {
const keys = [];
const metasets = chart._getSortedDatasetMetas(filterVisible);
let i, ilen;
for(i = 0, ilen = metasets.length; i < ilen; ++i){
keys.push(metasets[i].index);
}
return keys;
}
function applyStack(stack, value, dsIndex, options = {}) {
const keys = stack.keys;
const singleMode = options.mode === 'single';
let i, ilen, datasetIndex, otherValue;
if (value === null) {
return;
}
let found = false;
for(i = 0, ilen = keys.length; i < ilen; ++i){
datasetIndex = +keys[i];
if (datasetIndex === dsIndex) {
found = true;
if (options.all) {
continue;
}
break;
}
otherValue = stack.values[datasetIndex];
if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) {
value += otherValue;
}
}
if (!found && !options.all) {
return 0;
}
return value;
}
function convertObjectDataToArray(data, meta) {
const { iScale , vScale } = meta;
const iAxisKey = iScale.axis === 'x' ? 'x' : 'y';
const vAxisKey = vScale.axis === 'x' ? 'x' : 'y';
const keys = Object.keys(data);
const adata = new Array(keys.length);
let i, ilen, key;
for(i = 0, ilen = keys.length; i < ilen; ++i){
key = keys[i];
adata[i] = {
[iAxisKey]: key,
[vAxisKey]: data[key]
};
}
return adata;
}
function isStacked(scale, meta) {
const stacked = scale && scale.options.stacked;
return stacked || stacked === undefined && meta.stack !== undefined;
}
function getStackKey(indexScale, valueScale, meta) {
return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
}
function getUserBounds(scale) {
const { min , max , minDefined , maxDefined } = scale.getUserBounds();
return {
min: minDefined ? min : Number.NEGATIVE_INFINITY,
max: maxDefined ? max : Number.POSITIVE_INFINITY
};
}
function getOrCreateStack(stacks, stackKey, indexValue) {
const subStack = stacks[stackKey] || (stacks[stackKey] = {});
return subStack[indexValue] || (subStack[indexValue] = {});
}
function getLastIndexInStack(stack, vScale, positive, type) {
for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
const value = stack[meta.index];
if (positive && value > 0 || !positive && value < 0) {
return meta.index;
}
}
return null;
}
function updateStacks(controller, parsed) {
const { chart , _cachedMeta: meta } = controller;
const stacks = chart._stacks || (chart._stacks = {});
const { iScale , vScale , index: datasetIndex } = meta;
const iAxis = iScale.axis;
const vAxis = vScale.axis;
const key = getStackKey(iScale, vScale, meta);
const ilen = parsed.length;
let stack;
for(let i = 0; i < ilen; ++i){
const item = parsed[i];
const { [iAxis]: index , [vAxis]: value } = item;
const itemStacks = item._stacks || (item._stacks = {});
stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
stack[datasetIndex] = value;
stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
const visualValues = stack._visualValues || (stack._visualValues = {});
visualValues[datasetIndex] = value;
}
}
function getFirstScaleId(chart, axis) {
const scales = chart.scales;
return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
}
function createDatasetContext(parent, index) {
return createContext(parent, {
active: false,
dataset: undefined,
datasetIndex: index,
index,
mode: 'default',
type: 'dataset'
});
}
function createDataContext(parent, index, element) {
return createContext(parent, {
active: false,
dataIndex: index,
parsed: undefined,
raw: undefined,
element,
index,
mode: 'default',
type: 'data'
});
}
function clearStacks(meta, items) {
const datasetIndex = meta.controller.index;
const axis = meta.vScale && meta.vScale.axis;
if (!axis) {
return;
}
items = items || meta._parsed;
for (const parsed of items){
const stacks = parsed._stacks;
if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
return;
}
delete stacks[axis][datasetIndex];
if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
delete stacks[axis]._visualValues[datasetIndex];
}
}
}
const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
keys: getSortedDatasetIndices(chart, true),
values: null
};
class DatasetController {
static defaults = {};
static datasetElementType = null;
static dataElementType = null;
constructor(chart, datasetIndex){
this.chart = chart;
this._ctx = chart.ctx;
this.index = datasetIndex;
this._cachedDataOpts = {};
this._cachedMeta = this.getMeta();
this._type = this._cachedMeta.type;
this.options = undefined;
this._parsing = false;
this._data = undefined;
this._objectData = undefined;
this._sharedOptions = undefined;
this._drawStart = undefined;
this._drawCount = undefined;
this.enableOptionSharing = false;
this.supportsDecimation = false;
this.$context = undefined;
this._syncList = [];
this.datasetElementType = new.target.datasetElementType;
this.dataElementType = new.target.dataElementType;
this.initialize();
}
initialize() {
const meta = this._cachedMeta;
this.configure();
this.linkScales();
meta._stacked = isStacked(meta.vScale, meta);
this.addElements();
if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
}
}
updateIndex(datasetIndex) {
if (this.index !== datasetIndex) {
clearStacks(this._cachedMeta);
}
this.index = datasetIndex;
}
linkScales() {
const chart = this.chart;
const meta = this._cachedMeta;
const dataset = this.getDataset();
const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
const indexAxis = meta.indexAxis;
const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
meta.xScale = this.getScaleForId(xid);
meta.yScale = this.getScaleForId(yid);
meta.rScale = this.getScaleForId(rid);
meta.iScale = this.getScaleForId(iid);
meta.vScale = this.getScaleForId(vid);
}
getDataset() {
return this.chart.data.datasets[this.index];
}
getMeta() {
return this.chart.getDatasetMeta(this.index);
}
getScaleForId(scaleID) {
return this.chart.scales[scaleID];
}
_getOtherScale(scale) {
const meta = this._cachedMeta;
return scale === meta.iScale ? meta.vScale : meta.iScale;
}
reset() {
this._update('reset');
}
_destroy() {
const meta = this._cachedMeta;
if (this._data) {
unlistenArrayEvents(this._data, this);
}
if (meta._stacked) {
clearStacks(meta);
}
}
_dataCheck() {
const dataset = this.getDataset();
const data = dataset.data || (dataset.data = []);
const _data = this._data;
if (isObject(data)) {
const meta = this._cachedMeta;
this._data = convertObjectDataToArray(data, meta);
} else if (_data !== data) {
if (_data) {
unlistenArrayEvents(_data, this);
const meta = this._cachedMeta;
clearStacks(meta);
meta._parsed = [];
}
if (data && Object.isExtensible(data)) {
listenArrayEvents(data, this);
}
this._syncList = [];
this._data = data;
}
}
addElements() {
const meta = this._cachedMeta;
this._dataCheck();
if (this.datasetElementType) {
meta.dataset = new this.datasetElementType();
}
}
buildOrUpdateElements(resetNewElements) {
const meta = this._cachedMeta;
const dataset = this.getDataset();
let stackChanged = false;
this._dataCheck();
const oldStacked = meta._stacked;
meta._stacked = isStacked(meta.vScale, meta);
if (meta.stack !== dataset.stack) {
stackChanged = true;
clearStacks(meta);
meta.stack = dataset.stack;
}
this._resyncElements(resetNewElements);
if (stackChanged || oldStacked !== meta._stacked) {
updateStacks(this, meta._parsed);
meta._stacked = isStacked(meta.vScale, meta);
}
}
configure() {
const config = this.chart.config;
const scopeKeys = config.datasetScopeKeys(this._type);
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
this.options = config.createResolver(scopes, this.getContext());
this._parsing = this.options.parsing;
this._cachedDataOpts = {};
}
parse(start, count) {
const { _cachedMeta: meta , _data: data } = this;
const { iScale , _stacked } = meta;
const iAxis = iScale.axis;
let sorted = start === 0 && count === data.length ? true : meta._sorted;
let prev = start > 0 && meta._parsed[start - 1];
let i, cur, parsed;
if (this._parsing === false) {
meta._parsed = data;
meta._sorted = true;
parsed = data;
} else {
if (isArray(data[start])) {
parsed = this.parseArrayData(meta, data, start, count);
} else if (isObject(data[start])) {
parsed = this.parseObjectData(meta, data, start, count);
} else {
parsed = this.parsePrimitiveData(meta, data, start, count);
}
const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
for(i = 0; i < count; ++i){
meta._parsed[i + start] = cur = parsed[i];
if (sorted) {
if (isNotInOrderComparedToPrev()) {
sorted = false;
}
prev = cur;
}
}
meta._sorted = sorted;
}
if (_stacked) {
updateStacks(this, parsed);
}
}
parsePrimitiveData(meta, data, start, count) {
const { iScale , vScale } = meta;
const iAxis = iScale.axis;
const vAxis = vScale.axis;
const labels = iScale.getLabels();
const singleScale = iScale === vScale;
const parsed = new Array(count);
let i, ilen, index;
for(i = 0, ilen = count; i < ilen; ++i){
index = i + start;
parsed[i] = {
[iAxis]: singleScale || iScale.parse(labels[index], index),
[vAxis]: vScale.parse(data[index], index)
};
}
return parsed;
}
parseArrayData(meta, data, start, count) {
const { xScale , yScale } = meta;
const parsed = new Array(count);
let i, ilen, index, item;
for(i = 0, ilen = count; i < ilen; ++i){
index = i + start;
item = data[index];
parsed[i] = {
x: xScale.parse(item[0], index),
y: yScale.parse(item[1], index)
};
}
return parsed;
}
parseObjectData(meta, data, start, count) {
const { xScale , yScale } = meta;
const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
const parsed = new Array(count);
let i, ilen, index, item;
for(i = 0, ilen = count; i < ilen; ++i){
index = i + start;
item = data[index];
parsed[i] = {
x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
};
}
return parsed;
}
getParsed(index) {
return this._cachedMeta._parsed[index];
}
getDataElement(index) {
return this._cachedMeta.data[index];
}
applyStack(scale, parsed, mode) {
const chart = this.chart;
const meta = this._cachedMeta;
const value = parsed[scale.axis];
const stack = {
keys: getSortedDatasetIndices(chart, true),
values: parsed._stacks[scale.axis]._visualValues
};
return applyStack(stack, value, meta.index, {
mode
});
}
updateRangeFromParsed(range, scale, parsed, stack) {
const parsedValue = parsed[scale.axis];
let value = parsedValue === null ? NaN : parsedValue;
const values = stack && parsed._stacks[scale.axis];
if (stack && values) {
stack.values = values;
value = applyStack(stack, parsedValue, this._cachedMeta.index);
}
range.min = Math.min(range.min, value);
range.max = Math.max(range.max, value);
}
getMinMax(scale, canStack) {
const meta = this._cachedMeta;
const _parsed = meta._parsed;
const sorted = meta._sorted && scale === meta.iScale;
const ilen = _parsed.length;
const otherScale = this._getOtherScale(scale);
const stack = createStack(canStack, meta, this.chart);
const range = {
min: Number.POSITIVE_INFINITY,
max: Number.NEGATIVE_INFINITY
};
const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
let i, parsed;
function _skip() {
parsed = _parsed[i];
const otherValue = parsed[otherScale.axis];
return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
}
for(i = 0; i < ilen; ++i){
if (_skip()) {
continue;
}
this.updateRangeFromParsed(range, scale, parsed, stack);
if (sorted) {
break;
}
}
if (sorted) {
for(i = ilen - 1; i >= 0; --i){
if (_skip()) {
continue;
}
this.updateRangeFromParsed(range, scale, parsed, stack);
break;
}
}
return range;
}
getAllParsedValues(scale) {
const parsed = this._cachedMeta._parsed;
const values = [];
let i, ilen, value;
for(i = 0, ilen = parsed.length; i < ilen; ++i){
value = parsed[i][scale.axis];
if (isNumberFinite(value)) {
values.push(value);
}
}
return values;
}
getMaxOverflow() {
return false;
}
getLabelAndValue(index) {
const meta = this._cachedMeta;
const iScale = meta.iScale;
const vScale = meta.vScale;
const parsed = this.getParsed(index);
return {
label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
};
}
_update(mode) {
const meta = this._cachedMeta;
this.update(mode || 'default');
meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
}
update(mode) {}
draw() {
const ctx = this._ctx;
const chart = this.chart;
const meta = this._cachedMeta;
const elements = meta.data || [];
const area = chart.chartArea;
const active = [];
const start = this._drawStart || 0;
const count = this._drawCount || elements.length - start;
const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
let i;
if (meta.dataset) {
meta.dataset.draw(ctx, area, start, count);
}
for(i = start; i < start + count; ++i){
const element = elements[i];
if (element.hidden) {
continue;
}
if (element.active && drawActiveElementsOnTop) {
active.push(element);
} else {
element.draw(ctx, area);
}
}
for(i = 0; i < active.length; ++i){
active[i].draw(ctx, area);
}
}
getStyle(index, active) {
const mode = active ? 'active' : 'default';
return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
}
getContext(index, active, mode) {
const dataset = this.getDataset();
let context;
if (index >= 0 && index < this._cachedMeta.data.length) {
const element = this._cachedMeta.data[index];
context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
context.parsed = this.getParsed(index);
context.raw = dataset.data[index];
context.index = context.dataIndex = index;
} else {
context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
context.dataset = dataset;
context.index = context.datasetIndex = this.index;
}
context.active = !!active;
context.mode = mode;
return context;
}
resolveDatasetElementOptions(mode) {
return this._resolveElementOptions(this.datasetElementType.id, mode);
}
resolveDataElementOptions(index, mode) {
return this._resolveElementOptions(this.dataElementType.id, mode, index);
}
_resolveElementOptions(elementType, mode = 'default', index) {
const active = mode === 'active';
const cache = this._cachedDataOpts;
const cacheKey = elementType + '-' + mode;
const cached = cache[cacheKey];
const sharing = this.enableOptionSharing && defined(index);
if (cached) {
return cloneIfNotShared(cached, sharing);
}
const config = this.chart.config;
const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
const prefixes = active ? [
`${elementType}Hover`,
'hover',
elementType,
''
] : [
elementType,
''
];
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
const names = Object.keys(defaults.elements[elementType]);
const context = ()=>this.getContext(index, active, mode);
const values = config.resolveNamedOptions(scopes, names, context, prefixes);
if (values.$shared) {
values.$shared = sharing;
cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
}
return values;
}
_resolveAnimations(index, transition, active) {
const chart = this.chart;
const cache = this._cachedDataOpts;
const cacheKey = `animation-${transition}`;
const cached = cache[cacheKey];
if (cached) {
return cached;
}
let options;
if (chart.options.animation !== false) {
const config = this.chart.config;
const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
options = config.createResolver(scopes, this.getContext(index, active, transition));
}
const animations = new Animations(chart, options && options.animations);
if (options && options._cacheable) {
cache[cacheKey] = Object.freeze(animations);
}
return animations;
}
getSharedOptions(options) {
if (!options.$shared) {
return;
}
return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
}
includeOptions(mode, sharedOptions) {
return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
}
_getSharedOptions(start, mode) {
const firstOpts = this.resolveDataElementOptions(start, mode);
const previouslySharedOptions = this._sharedOptions;
const sharedOptions = this.getSharedOptions(firstOpts);
const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
this.updateSharedOptions(sharedOptions, mode, firstOpts);
return {
sharedOptions,
includeOptions
};
}
updateElement(element, index, properties, mode) {
if (isDirectUpdateMode(mode)) {
Object.assign(element, properties);
} else {
this._resolveAnimations(index, mode).update(element, properties);
}
}
updateSharedOptions(sharedOptions, mode, newOptions) {
if (sharedOptions && !isDirectUpdateMode(mode)) {
this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
}
}
_setStyle(element, index, mode, active) {
element.active = active;
const options = this.getStyle(index, active);
this._resolveAnimations(index, mode, active).update(element, {
options: !active && this.getSharedOptions(options) || options
});
}
removeHoverStyle(element, datasetIndex, index) {
this._setStyle(element, index, 'active', false);
}
setHoverStyle(element, datasetIndex, index) {
this._setStyle(element, index, 'active', true);
}
_removeDatasetHoverStyle() {
const element = this._cachedMeta.dataset;
if (element) {
this._setStyle(element, undefined, 'active', false);
}
}
_setDatasetHoverStyle() {
const element = this._cachedMeta.dataset;
if (element) {
this._setStyle(element, undefined, 'active', true);
}
}
_resyncElements(resetNewElements) {
const data = this._data;
const elements = this._cachedMeta.data;
for (const [method, arg1, arg2] of this._syncList){
this[method](arg1, arg2);
}
this._syncList = [];
const numMeta = elements.length;
const numData = data.length;
const count = Math.min(numData, numMeta);
if (count) {
this.parse(0, count);
}
if (numData > numMeta) {
this._insertElements(numMeta, numData - numMeta, resetNewElements);
} else if (numData < numMeta) {
this._removeElements(numData, numMeta - numData);
}
}
_insertElements(start, count, resetNewElements = true) {
const meta = this._cachedMeta;
const data = meta.data;
const end = start + count;
let i;
const move = (arr)=>{
arr.length += count;
for(i = arr.length - 1; i >= end; i--){
arr[i] = arr[i - count];
}
};
move(data);
for(i = start; i < end; ++i){
data[i] = new this.dataElementType();
}
if (this._parsing) {
move(meta._parsed);
}
this.parse(start, count);
if (resetNewElements) {
this.updateElements(data, start, count, 'reset');
}
}
updateElements(element, start, count, mode) {}
_removeElements(start, count) {
const meta = this._cachedMeta;
if (this._parsing) {
const removed = meta._parsed.splice(start, count);
if (meta._stacked) {
clearStacks(meta, removed);
}
}
meta.data.splice(start, count);
}
_sync(args) {
if (this._parsing) {
this._syncList.push(args);
} else {
const [method, arg1, arg2] = args;
this[method](arg1, arg2);
}
this.chart._dataChanges.push([
this.index,
...args
]);
}
_onDataPush() {
const count = arguments.length;
this._sync([
'_insertElements',
this.getDataset().data.length - count,
count
]);
}
_onDataPop() {
this._sync([
'_removeElements',
this._cachedMeta.data.length - 1,
1
]);
}
_onDataShift() {
this._sync([
'_removeElements',
0,
1
]);
}
_onDataSplice(start, count) {
if (count) {
this._sync([
'_removeElements',
start,
count
]);
}
const newCount = arguments.length - 2;
if (newCount) {
this._sync([
'_insertElements',
start,
newCount
]);
}
}
_onDataUnshift() {
this._sync([
'_insertElements',
0,
arguments.length
]);
}
}
function getAllScaleValues(scale, type) {
if (!scale._cache.$bar) {
const visibleMetas = scale.getMatchingVisibleMetas(type);
let values = [];
for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
}
scale._cache.$bar = _arrayUnique(values.sort((a, b)=>a - b));
}
return scale._cache.$bar;
}
function computeMinSampleSize(meta) {
const scale = meta.iScale;
const values = getAllScaleValues(scale, meta.type);
let min = scale._length;
let i, ilen, curr, prev;
const updateMinAndPrev = ()=>{
if (curr === 32767 || curr === -32768) {
return;
}
if (defined(prev)) {
min = Math.min(min, Math.abs(curr - prev) || min);
}
prev = curr;
};
for(i = 0, ilen = values.length; i < ilen; ++i){
curr = scale.getPixelForValue(values[i]);
updateMinAndPrev();
}
prev = undefined;
for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
curr = scale.getPixelForTick(i);
updateMinAndPrev();
}
return min;
}
function computeFitCategoryTraits(index, ruler, options, stackCount) {
const thickness = options.barThickness;
let size, ratio;
if (isNullOrUndef(thickness)) {
size = ruler.min * options.categoryPercentage;
ratio = options.barPercentage;
} else {
size = thickness * stackCount;
ratio = 1;
}
return {
chunk: size / stackCount,
ratio,
start: ruler.pixels[index] - size / 2
};
}
function computeFlexCategoryTraits(index, ruler, options, stackCount) {
const pixels = ruler.pixels;
const curr = pixels[index];
let prev = index > 0 ? pixels[index - 1] : null;
let next = index < pixels.length - 1 ? pixels[index + 1] : null;
const percent = options.categoryPercentage;
if (prev === null) {
prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
}
if (next === null) {
next = curr + curr - prev;
}
const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
const size = Math.abs(next - prev) / 2 * percent;
return {
chunk: size / stackCount,
ratio: options.barPercentage,
start
};
}
function parseFloatBar(entry, item, vScale, i) {
const startValue = vScale.parse(entry[0], i);
const endValue = vScale.parse(entry[1], i);
const min = Math.min(startValue, endValue);
const max = Math.max(startValue, endValue);
let barStart = min;
let barEnd = max;
if (Math.abs(min) > Math.abs(max)) {
barStart = max;
barEnd = min;
}
item[vScale.axis] = barEnd;
item._custom = {
barStart,
barEnd,
start: startValue,
end: endValue,
min,
max
};
}
function parseValue(entry, item, vScale, i) {
if (isArray(entry)) {
parseFloatBar(entry, item, vScale, i);
} else {
item[vScale.axis] = vScale.parse(entry, i);
}
return item;
}
function parseArrayOrPrimitive(meta, data, start, count) {
const iScale = meta.iScale;
const vScale = meta.vScale;
const labels = iScale.getLabels();
const singleScale = iScale === vScale;
const parsed = [];
let i, ilen, item, entry;
for(i = start, ilen = start + count; i < ilen; ++i){
entry = data[i];
item = {};
item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
parsed.push(parseValue(entry, item, vScale, i));
}
return parsed;
}
function isFloatBar(custom) {
return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
}
function barSign(size, vScale, actualBase) {
if (size !== 0) {
return sign(size);
}
return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
}
function borderProps(properties) {
let reverse, start, end, top, bottom;
if (properties.horizontal) {
reverse = properties.base > properties.x;
start = 'left';
end = 'right';
} else {
reverse = properties.base < properties.y;
start = 'bottom';
end = 'top';
}
if (reverse) {
top = 'end';
bottom = 'start';
} else {
top = 'start';
bottom = 'end';
}
return {
start,
end,
reverse,
top,
bottom
};
}
function setBorderSkipped(properties, options, stack, index) {
let edge = options.borderSkipped;
const res = {};
if (!edge) {
properties.borderSkipped = res;
return;
}
if (edge === true) {
properties.borderSkipped = {
top: true,
right: true,
bottom: true,
left: true
};
return;
}
const { start , end , reverse , top , bottom } = borderProps(properties);
if (edge === 'middle' && stack) {
properties.enableBorderRadius = true;
if ((stack._top || 0) === index) {
edge = top;
} else if ((stack._bottom || 0) === index) {
edge = bottom;
} else {
res[parseEdge(bottom, start, end, reverse)] = true;
edge = top;
}
}
res[parseEdge(edge, start, end, reverse)] = true;
properties.borderSkipped = res;
}
function parseEdge(edge, a, b, reverse) {
if (reverse) {
edge = swap(edge, a, b);
edge = startEnd(edge, b, a);
} else {
edge = startEnd(edge, a, b);
}
return edge;
}
function swap(orig, v1, v2) {
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
}
function startEnd(v, start, end) {
return v === 'start' ? start : v === 'end' ? end : v;
}
function setInflateAmount(properties, { inflateAmount }, ratio) {
properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
}
class BarController extends DatasetController {
static id = 'bar';
static defaults = {
datasetElementType: false,
dataElementType: 'bar',
categoryPercentage: 0.8,
barPercentage: 0.9,
grouped: true,
animations: {
numbers: {
type: 'number',
properties: [
'x',
'y',
'base',
'width',
'height'
]
}
}
};
static overrides = {
scales: {
_index_: {
type: 'category',
offset: true,
grid: {
offset: true
}
},
_value_: {
type: 'linear',
beginAtZero: true
}
}
};
parsePrimitiveData(meta, data, start, count) {
return parseArrayOrPrimitive(meta, data, start, count);
}
parseArrayData(meta, data, start, count) {
return parseArrayOrPrimitive(meta, data, start, count);
}
parseObjectData(meta, data, start, count) {
const { iScale , vScale } = meta;
const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
const parsed = [];
let i, ilen, item, obj;
for(i = start, ilen = start + count; i < ilen; ++i){
obj = data[i];
item = {};
item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
}
return parsed;
}
updateRangeFromParsed(range, scale, parsed, stack) {
super.updateRangeFromParsed(range, scale, parsed, stack);
const custom = parsed._custom;
if (custom && scale === this._cachedMeta.vScale) {
range.min = Math.min(range.min, custom.min);
range.max = Math.max(range.max, custom.max);
}
}
getMaxOverflow() {
return 0;
}
getLabelAndValue(index) {
const meta = this._cachedMeta;
const { iScale , vScale } = meta;
const parsed = this.getParsed(index);
const custom = parsed._custom;
const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
return {
label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
value
};
}
initialize() {
this.enableOptionSharing = true;
super.initialize();
const meta = this._cachedMeta;
meta.stack = this.getDataset().stack;
}
update(mode) {
const meta = this._cachedMeta;
this.updateElements(meta.data, 0, meta.data.length, mode);
}
updateElements(bars, start, count, mode) {
const reset = mode === 'reset';
const { index , _cachedMeta: { vScale } } = this;
const base = vScale.getBasePixel();
const horizontal = vScale.isHorizontal();
const ruler = this._getRuler();
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
for(let i = start; i < start + count; i++){
const parsed = this.getParsed(i);
const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {
base,
head: base
} : this._calculateBarValuePixels(i);
const ipixels = this._calculateBarIndexPixels(i, ruler);
const stack = (parsed._stacks || {})[vScale.axis];
const properties = {
horizontal,
base: vpixels.base,
enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
x: horizontal ? vpixels.head : ipixels.center,
y: horizontal ? ipixels.center : vpixels.head,
height: horizontal ? ipixels.size : Math.abs(vpixels.size),
width: horizontal ? Math.abs(vpixels.size) : ipixels.size
};
if (includeOptions) {
properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
}
const options = properties.options || bars[i].options;
setBorderSkipped(properties, options, stack, index);
setInflateAmount(properties, options, ruler.ratio);
this.updateElement(bars[i], i, properties, mode);
}
}
_getStacks(last, dataIndex) {
const { iScale } = this._cachedMeta;
const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
const stacked = iScale.options.stacked;
const stacks = [];
const currentParsed = this._cachedMeta.controller.getParsed(dataIndex);
const iScaleValue = currentParsed && currentParsed[iScale.axis];
const skipNull = (meta)=>{
const parsed = meta._parsed.find((item)=>item[iScale.axis] === iScaleValue);
const val = parsed && parsed[meta.vScale.axis];
if (isNullOrUndef(val) || isNaN(val)) {
return true;
}
};
for (const meta of metasets){
if (dataIndex !== undefined && skipNull(meta)) {
continue;
}
if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
stacks.push(meta.stack);
}
if (meta.index === last) {
break;
}
}
if (!stacks.length) {
stacks.push(undefined);
}
return stacks;
}
_getStackCount(index) {
return this._getStacks(undefined, index).length;
}
_getAxisCount() {
return this._getAxis().length;
}
getFirstScaleIdForIndexAxis() {
const scales = this.chart.scales;
const indexScaleId = this.chart.options.indexAxis;
return Object.keys(scales).filter((key)=>scales[key].axis === indexScaleId).shift();
}
_getAxis() {
const axis = {};
const firstScaleAxisId = this.getFirstScaleIdForIndexAxis();
for (const dataset of this.chart.data.datasets){
axis[valueOrDefault(this.chart.options.indexAxis === 'x' ? dataset.xAxisID : dataset.yAxisID, firstScaleAxisId)] = true;
}
return Object.keys(axis);
}
_getStackIndex(datasetIndex, name, dataIndex) {
const stacks = this._getStacks(datasetIndex, dataIndex);
const index = name !== undefined ? stacks.indexOf(name) : -1;
return index === -1 ? stacks.length - 1 : index;
}
_getRuler() {
const opts = this.options;
const meta = this._cachedMeta;
const iScale = meta.iScale;
const pixels = [];
let i, ilen;
for(i = 0, ilen = meta.data.length; i < ilen; ++i){
pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
}
const barThickness = opts.barThickness;
const min = barThickness || computeMinSampleSize(meta);
return {
min,
pixels,
start: iScale._startPixel,
end: iScale._endPixel,
stackCount: this._getStackCount(),
scale: iScale,
grouped: opts.grouped,
ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
};
}
_calculateBarValuePixels(index) {
const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
const actualBase = baseValue || 0;
const parsed = this.getParsed(index);
const custom = parsed._custom;
const floating = isFloatBar(custom);
let value = parsed[vScale.axis];
let start = 0;
let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
let head, size;
if (length !== value) {
start = length - value;
length = value;
}
if (floating) {
value = custom.barStart;
length = custom.barEnd - custom.barStart;
if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
start = 0;
}
start += value;
}
const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
let base = vScale.getPixelForValue(startValue);
if (this.chart.getDataVisibility(index)) {
head = vScale.getPixelForValue(start + length);
} else {
head = base;
}
size = head - base;
if (Math.abs(size) < minBarLength) {
size = barSign(size, vScale, actualBase) * minBarLength;
if (value === actualBase) {
base -= size / 2;
}
const startPixel = vScale.getPixelForDecimal(0);
const endPixel = vScale.getPixelForDecimal(1);
const min = Math.min(startPixel, endPixel);
const max = Math.max(startPixel, endPixel);
base = Math.max(Math.min(base, max), min);
head = base + size;
if (_stacked && !floating) {
parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
}
}
if (base === vScale.getPixelForValue(actualBase)) {
const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
base += halfGrid;
size -= halfGrid;
}
return {
size,
base,
head,
center: head + size / 2
};
}
_calculateBarIndexPixels(index, ruler) {
const scale = ruler.scale;
const options = this.options;
const skipNull = options.skipNull;
const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
let center, size;
const axisCount = this._getAxisCount();
if (ruler.grouped) {
const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount * axisCount) : computeFitCategoryTraits(index, ruler, options, stackCount * axisCount);
const axisID = this.chart.options.indexAxis === 'x' ? this.getDataset().xAxisID : this.getDataset().yAxisID;
const axisNumber = this._getAxis().indexOf(valueOrDefault(axisID, this.getFirstScaleIdForIndexAxis()));
const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined) + axisNumber;
center = range.start + range.chunk * stackIndex + range.chunk / 2;
size = Math.min(maxBarThickness, range.chunk * range.ratio);
} else {
center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
}
return {
base: center - size / 2,
head: center + size / 2,
center,
size
};
}
draw() {
const meta = this._cachedMeta;
const vScale = meta.vScale;
const rects = meta.data;
const ilen = rects.length;
let i = 0;
for(; i < ilen; ++i){
if (this.getParsed(i)[vScale.axis] !== null && !rects[i].hidden) {
rects[i].draw(this._ctx);
}
}
}
}
class BubbleController extends DatasetController {
static id = 'bubble';
static defaults = {
datasetElementType: false,
dataElementType: 'point',
animations: {
numbers: {
type: 'number',
properties: [
'x',
'y',
'borderWidth',
'radius'
]
}
}
};
static overrides = {
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
}
};
initialize() {
this.enableOptionSharing = true;
super.initialize();
}
parsePrimitiveData(meta, data, start, count) {
const parsed = super.parsePrimitiveData(meta, data, start, count);
for(let i = 0; i < parsed.length; i++){
parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
}
return parsed;
}
parseArrayData(meta, data, start, count) {
const parsed = super.parseArrayData(meta, data, start, count);
for(let i = 0; i < parsed.length; i++){
const item = data[start + i];
parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
}
return parsed;
}
parseObjectData(meta, data, start, count) {
const parsed = super.parseObjectData(meta, data, start, count);
for(let i = 0; i < parsed.length; i++){
const item = data[start + i];
parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
}
return parsed;
}
getMaxOverflow() {
const data = this._cachedMeta.data;
let max = 0;
for(let i = data.length - 1; i >= 0; --i){
max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
}
return max > 0 && max;
}
getLabelAndValue(index) {
const meta = this._cachedMeta;
const labels = this.chart.data.labels || [];
const { xScale , yScale } = meta;
const parsed = this.getParsed(index);
const x = xScale.getLabelForValue(parsed.x);
const y = yScale.getLabelForValue(parsed.y);
const r = parsed._custom;
return {
label: labels[index] || '',
value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
};
}
update(mode) {
const points = this._cachedMeta.data;
this.updateElements(points, 0, points.length, mode);
}
updateElements(points, start, count, mode) {
const reset = mode === 'reset';
const { iScale , vScale } = this._cachedMeta;
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
const iAxis = iScale.axis;
const vAxis = vScale.axis;
for(let i = start; i < start + count; i++){
const point = points[i];
const parsed = !reset && this.getParsed(i);
const properties = {};
const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
properties.skip = isNaN(iPixel) || isNaN(vPixel);
if (includeOptions) {
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
if (reset) {
properties.options.radius = 0;
}
}
this.updateElement(point, i, properties, mode);
}
}
resolveDataElementOptions(index, mode) {
const parsed = this.getParsed(index);
let values = super.resolveDataElementOptions(index, mode);
if (values.$shared) {
values = Object.assign({}, values, {
$shared: false
});
}
const radius = values.radius;
if (mode !== 'active') {
values.radius = 0;
}
values.radius += valueOrDefault(parsed && parsed._custom, radius);
return values;
}
}
function getRatioAndOffset(rotation, circumference, cutout) {
let ratioX = 1;
let ratioY = 1;
let offsetX = 0;
let offsetY = 0;
if (circumference < TAU) {
const startAngle = rotation;
const endAngle = startAngle + circumference;
const startX = Math.cos(startAngle);
const startY = Math.sin(startAngle);
const endX = Math.cos(endAngle);
const endY = Math.sin(endAngle);
const calcMax = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
const calcMin = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
const maxX = calcMax(0, startX, endX);
const maxY = calcMax(HALF_PI, startY, endY);
const minX = calcMin(PI, startX, endX);
const minY = calcMin(PI + HALF_PI, startY, endY);
ratioX = (maxX - minX) / 2;
ratioY = (maxY - minY) / 2;
offsetX = -(maxX + minX) / 2;
offsetY = -(maxY + minY) / 2;
}
return {
ratioX,
ratioY,
offsetX,
offsetY
};
}
class DoughnutController extends DatasetController {
static id = 'doughnut';
static defaults = {
datasetElementType: false,
dataElementType: 'arc',
animation: {
animateRotate: true,
animateScale: false
},
animations: {
numbers: {
type: 'number',
properties: [
'circumference',
'endAngle',
'innerRadius',
'outerRadius',
'startAngle',
'x',
'y',
'offset',
'borderWidth',
'spacing'
]
}
},
cutout: '50%',
rotation: 0,
circumference: 360,
radius: '100%',
spacing: 0,
indexAxis: 'r'
};
static descriptors = {
_scriptable: (name)=>name !== 'spacing',
_indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash')
};
static overrides = {
aspectRatio: 1,
plugins: {
legend: {
labels: {
generateLabels (chart) {
const data = chart.data;
const { labels: { pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i)=>{
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
fontColor: color,
hidden: !chart.getDataVisibility(i),
lineDash: style.borderDash,
lineDashOffset: style.borderDashOffset,
lineJoin: style.borderJoinStyle,
lineWidth: style.borderWidth,
strokeStyle: style.borderColor,
textAlign: textAlign,
pointStyle: pointStyle,
borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
index: i
};
});
}
return [];
}
},
onClick (e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
}
}
};
constructor(chart, datasetIndex){
super(chart, datasetIndex);
this.enableOptionSharing = true;
this.innerRadius = undefined;
this.outerRadius = undefined;
this.offsetX = undefined;
this.offsetY = undefined;
}
linkScales() {}
parse(start, count) {
const data = this.getDataset().data;
const meta = this._cachedMeta;
if (this._parsing === false) {
meta._parsed = data;
} else {
let getter = (i)=>+data[i];
if (isObject(data[start])) {
const { key ='value' } = this._parsing;
getter = (i)=>+resolveObjectKey(data[i], key);
}
let i, ilen;
for(i = start, ilen = start + count; i < ilen; ++i){
meta._parsed[i] = getter(i);
}
}
}
_getRotation() {
return toRadians(this.options.rotation - 90);
}
_getCircumference() {
return toRadians(this.options.circumference);
}
_getRotationExtents() {
let min = TAU;
let max = -TAU;
for(let i = 0; i < this.chart.data.datasets.length; ++i){
if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
const controller = this.chart.getDatasetMeta(i).controller;
const rotation = controller._getRotation();
const circumference = controller._getCircumference();
min = Math.min(min, rotation);
max = Math.max(max, rotation + circumference);
}
}
return {
rotation: min,
circumference: max - min
};
}
update(mode) {
const chart = this.chart;
const { chartArea } = chart;
const meta = this._cachedMeta;
const arcs = meta.data;
const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
const chartWeight = this._getRingWeight(this.index);
const { circumference , rotation } = this._getRotationExtents();
const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
const maxWidth = (chartArea.width - spacing) / ratioX;
const maxHeight = (chartArea.height - spacing) / ratioY;
const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
const outerRadius = toDimension(this.options.radius, maxRadius);
const innerRadius = Math.max(outerRadius * cutout, 0);
const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
this.offsetX = offsetX * outerRadius;
this.offsetY = offsetY * outerRadius;
meta.total = this.calculateTotal();
this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
this.updateElements(arcs, 0, arcs.length, mode);
}
_circumference(i, reset) {
const opts = this.options;
const meta = this._cachedMeta;
const circumference = this._getCircumference();
if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
return 0;
}
return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
}
updateElements(arcs, start, count, mode) {
const reset = mode === 'reset';
const chart = this.chart;
const chartArea = chart.chartArea;
const opts = chart.options;
const animationOpts = opts.animation;
const centerX = (chartArea.left + chartArea.right) / 2;
const centerY = (chartArea.top + chartArea.bottom) / 2;
const animateScale = reset && animationOpts.animateScale;
const innerRadius = animateScale ? 0 : this.innerRadius;
const outerRadius = animateScale ? 0 : this.outerRadius;
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
let startAngle = this._getRotation();
let i;
for(i = 0; i < start; ++i){
startAngle += this._circumference(i, reset);
}
for(i = start; i < start + count; ++i){
const circumference = this._circumference(i, reset);
const arc = arcs[i];
const properties = {
x: centerX + this.offsetX,
y: centerY + this.offsetY,
startAngle,
endAngle: startAngle + circumference,
circumference,
outerRadius,
innerRadius
};
if (includeOptions) {
properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
}
startAngle += circumference;
this.updateElement(arc, i, properties, mode);
}
}
calculateTotal() {
const meta = this._cachedMeta;
const metaData = meta.data;
let total = 0;
let i;
for(i = 0; i < metaData.length; i++){
const value = meta._parsed[i];
if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
total += Math.abs(value);
}
}
return total;
}
calculateCircumference(value) {
const total = this._cachedMeta.total;
if (total > 0 && !isNaN(value)) {
return TAU * (Math.abs(value) / total);
}
return 0;
}
getLabelAndValue(index) {
const meta = this._cachedMeta;
const chart = this.chart;
const labels = chart.data.labels || [];
const value = formatNumber(meta._parsed[index], chart.options.locale);
return {
label: labels[index] || '',
value
};
}
getMaxBorderWidth(arcs) {
let max = 0;
const chart = this.chart;
let i, ilen, meta, controller, options;
if (!arcs) {
for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
if (chart.isDatasetVisible(i)) {
meta = chart.getDatasetMeta(i);
arcs = meta.data;
controller = meta.controller;
break;
}
}
}
if (!arcs) {
return 0;
}
for(i = 0, ilen = arcs.length; i < ilen; ++i){
options = controller.resolveDataElementOptions(i);
if (options.borderAlign !== 'inner') {
max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
}
}
return max;
}
getMaxOffset(arcs) {
let max = 0;
for(let i = 0, ilen = arcs.length; i < ilen; ++i){
const options = this.resolveDataElementOptions(i);
max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
}
return max;
}
_getRingWeightOffset(datasetIndex) {
let ringWeightOffset = 0;
for(let i = 0; i < datasetIndex; ++i){
if (this.chart.isDatasetVisible(i)) {
ringWeightOffset += this._getRingWeight(i);
}
}
return ringWeightOffset;
}
_getRingWeight(datasetIndex) {
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
}
_getVisibleDatasetWeightTotal() {
return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
}
}
class LineController extends DatasetController {
static id = 'line';
static defaults = {
datasetElementType: 'line',
dataElementType: 'point',
showLine: true,
spanGaps: false
};
static overrides = {
scales: {
_index_: {
type: 'category'
},
_value_: {
type: 'linear'
}
}
};
initialize() {
this.enableOptionSharing = true;
this.supportsDecimation = true;
super.initialize();
}
update(mode) {
const meta = this._cachedMeta;
const { dataset: line , data: points = [] , _dataset } = meta;
const animationsDisabled = this.chart._animationsDisabled;
let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
this._drawStart = start;
this._drawCount = count;
if (_scaleRangesChanged(meta)) {
start = 0;
count = points.length;
}
line._chart = this.chart;
line._datasetIndex = this.index;
line._decimated = !!_dataset._decimated;
line.points = points;
const options = this.resolveDatasetElementOptions(mode);
if (!this.options.showLine) {
options.borderWidth = 0;
}
options.segment = this.options.segment;
this.updateElement(line, undefined, {
animated: !animationsDisabled,
options
}, mode);
this.updateElements(points, start, count, mode);
}
updateElements(points, start, count, mode) {
const reset = mode === 'reset';
const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
const iAxis = iScale.axis;
const vAxis = vScale.axis;
const { spanGaps , segment } = this.options;
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
const end = start + count;
const pointsCount = points.length;
let prevParsed = start > 0 && this.getParsed(start - 1);
for(let i = 0; i < pointsCount; ++i){
const point = points[i];
const properties = directUpdate ? point : {};
if (i < start || i >= end) {
properties.skip = true;
continue;
}
const parsed = this.getParsed(i);
const nullData = isNullOrUndef(parsed[vAxis]);
const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
if (segment) {
properties.parsed = parsed;
properties.raw = _dataset.data[i];
}
if (includeOptions) {
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
}
if (!directUpdate) {
this.updateElement(point, i, properties, mode);
}
prevParsed = parsed;
}
}
getMaxOverflow() {
const meta = this._cachedMeta;
const dataset = meta.dataset;
const border = dataset.options && dataset.options.borderWidth || 0;
const data = meta.data || [];
if (!data.length) {
return border;
}
const firstPoint = data[0].size(this.resolveDataElementOptions(0));
const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
return Math.max(border, firstPoint, lastPoint) / 2;
}
draw() {
const meta = this._cachedMeta;
meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
super.draw();
}
}
class PolarAreaController extends DatasetController {
static id = 'polarArea';
static defaults = {
dataElementType: 'arc',
animation: {
animateRotate: true,
animateScale: true
},
animations: {
numbers: {
type: 'number',
properties: [
'x',
'y',
'startAngle',
'endAngle',
'innerRadius',
'outerRadius'
]
}
},
indexAxis: 'r',
startAngle: 0
};
static overrides = {
aspectRatio: 1,
plugins: {
legend: {
labels: {
generateLabels (chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
const { labels: { pointStyle , color } } = chart.legend.options;
return data.labels.map((label, i)=>{
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
strokeStyle: style.borderColor,
fontColor: color,
lineWidth: style.borderWidth,
pointStyle: pointStyle,
hidden: !chart.getDataVisibility(i),
index: i
};
});
}
return [];
}
},
onClick (e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
}
},
scales: {
r: {
type: 'radialLinear',
angleLines: {
display: false
},
beginAtZero: true,
grid: {
circular: true
},
pointLabels: {
display: false
},
startAngle: 0
}
}
};
constructor(chart, datasetIndex){
super(chart, datasetIndex);
this.innerRadius = undefined;
this.outerRadius = undefined;
}
getLabelAndValue(index) {
const meta = this._cachedMeta;
const chart = this.chart;
const labels = chart.data.labels || [];
const value = formatNumber(meta._parsed[index].r, chart.options.locale);
return {
label: labels[index] || '',
value
};
}
parseObjectData(meta, data, start, count) {
return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
}
update(mode) {
const arcs = this._cachedMeta.data;
this._updateRadius();
this.updateElements(arcs, 0, arcs.length, mode);
}
getMinMax() {
const meta = this._cachedMeta;
const range = {
min: Number.POSITIVE_INFINITY,
max: Number.NEGATIVE_INFINITY
};
meta.data.forEach((element, index)=>{
const parsed = this.getParsed(index).r;
if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
if (parsed < range.min) {
range.min = parsed;
}
if (parsed > range.max) {
range.max = parsed;
}
}
});
return range;
}
_updateRadius() {
const chart = this.chart;
const chartArea = chart.chartArea;
const opts = chart.options;
const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
const outerRadius = Math.max(minSize / 2, 0);
const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
this.outerRadius = outerRadius - radiusLength * this.index;
this.innerRadius = this.outerRadius - radiusLength;
}
updateElements(arcs, start, count, mode) {
const reset = mode === 'reset';
const chart = this.chart;
const opts = chart.options;
const animationOpts = opts.animation;
const scale = this._cachedMeta.rScale;
const centerX = scale.xCenter;
const centerY = scale.yCenter;
const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
let angle = datasetStartAngle;
let i;
const defaultAngle = 360 / this.countVisibleElements();
for(i = 0; i < start; ++i){
angle += this._computeAngle(i, mode, defaultAngle);
}
for(i = start; i < start + count; i++){
const arc = arcs[i];
let startAngle = angle;
let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
angle = endAngle;
if (reset) {
if (animationOpts.animateScale) {
outerRadius = 0;
}
if (animationOpts.animateRotate) {
startAngle = endAngle = datasetStartAngle;
}
}
const properties = {
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius,
startAngle,
endAngle,
options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
};
this.updateElement(arc, i, properties, mode);
}
}
countVisibleElements() {
const meta = this._cachedMeta;
let count = 0;
meta.data.forEach((element, index)=>{
if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
count++;
}
});
return count;
}
_computeAngle(index, mode, defaultAngle) {
return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
}
}
class PieController extends DoughnutController {
static id = 'pie';
static defaults = {
cutout: 0,
rotation: 0,
circumference: 360,
radius: '100%'
};
}
class RadarController extends DatasetController {
static id = 'radar';
static defaults = {
datasetElementType: 'line',
dataElementType: 'point',
indexAxis: 'r',
showLine: true,
elements: {
line: {
fill: 'start'
}
}
};
static overrides = {
aspectRatio: 1,
scales: {
r: {
type: 'radialLinear'
}
}
};
getLabelAndValue(index) {
const vScale = this._cachedMeta.vScale;
const parsed = this.getParsed(index);
return {
label: vScale.getLabels()[index],
value: '' + vScale.getLabelForValue(parsed[vScale.axis])
};
}
parseObjectData(meta, data, start, count) {
return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
}
update(mode) {
const meta = this._cachedMeta;
const line = meta.dataset;
const points = meta.data || [];
const labels = meta.iScale.getLabels();
line.points = points;
if (mode !== 'resize') {
const options = this.resolveDatasetElementOptions(mode);
if (!this.options.showLine) {
options.borderWidth = 0;
}
const properties = {
_loop: true,
_fullLoop: labels.length === points.length,
options
};
this.updateElement(line, undefined, properties, mode);
}
this.updateElements(points, 0, points.length, mode);
}
updateElements(points, start, count, mode) {
const scale = this._cachedMeta.rScale;
const reset = mode === 'reset';
for(let i = start; i < start + count; i++){
const point = points[i];
const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
const x = reset ? scale.xCenter : pointPosition.x;
const y = reset ? scale.yCenter : pointPosition.y;
const properties = {
x,
y,
angle: pointPosition.angle,
skip: isNaN(x) || isNaN(y),
options
};
this.updateElement(point, i, properties, mode);
}
}
}
class ScatterController extends DatasetController {
static id = 'scatter';
static defaults = {
datasetElementType: false,
dataElementType: 'point',
showLine: false,
fill: false
};
static overrides = {
interaction: {
mode: 'point'
},
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
}
};
getLabelAndValue(index) {
const meta = this._cachedMeta;
const labels = this.chart.data.labels || [];
const { xScale , yScale } = meta;
const parsed = this.getParsed(index);
const x = xScale.getLabelForValue(parsed.x);
const y = yScale.getLabelForValue(parsed.y);
return {
label: labels[index] || '',
value: '(' + x + ', ' + y + ')'
};
}
update(mode) {
const meta = this._cachedMeta;
const { data: points = [] } = meta;
const animationsDisabled = this.chart._animationsDisabled;
let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
this._drawStart = start;
this._drawCount = count;
if (_scaleRangesChanged(meta)) {
start = 0;
count = points.length;
}
if (this.options.showLine) {
if (!this.datasetElementType) {
this.addElements();
}
const { dataset: line , _dataset } = meta;
line._chart = this.chart;
line._datasetIndex = this.index;
line._decimated = !!_dataset._decimated;
line.points = points;
const options = this.resolveDatasetElementOptions(mode);
options.segment = this.options.segment;
this.updateElement(line, undefined, {
animated: !animationsDisabled,
options
}, mode);
} else if (this.datasetElementType) {
delete meta.dataset;
this.datasetElementType = false;
}
this.updateElements(points, start, count, mode);
}
addElements() {
const { showLine } = this.options;
if (!this.datasetElementType && showLine) {
this.datasetElementType = this.chart.registry.getElement('line');
}
super.addElements();
}
updateElements(points, start, count, mode) {
const reset = mode === 'reset';
const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
const firstOpts = this.resolveDataElementOptions(start, mode);
const sharedOptions = this.getSharedOptions(firstOpts);
const includeOptions = this.includeOptions(mode, sharedOptions);
const iAxis = iScale.axis;
const vAxis = vScale.axis;
const { spanGaps , segment } = this.options;
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
let prevParsed = start > 0 && this.getParsed(start - 1);
for(let i = start; i < start + count; ++i){
const point = points[i];
const parsed = this.getParsed(i);
const properties = directUpdate ? point : {};
const nullData = isNullOrUndef(parsed[vAxis]);
const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
if (segment) {
properties.parsed = parsed;
properties.raw = _dataset.data[i];
}
if (includeOptions) {
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
}
if (!directUpdate) {
this.updateElement(point, i, properties, mode);
}
prevParsed = parsed;
}
this.updateSharedOptions(sharedOptions, mode, firstOpts);
}
getMaxOverflow() {
const meta = this._cachedMeta;
const data = meta.data || [];
if (!this.options.showLine) {
let max = 0;
for(let i = data.length - 1; i >= 0; --i){
max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
}
return max > 0 && max;
}
const dataset = meta.dataset;
const border = dataset.options && dataset.options.borderWidth || 0;
if (!data.length) {
return border;
}
const firstPoint = data[0].size(this.resolveDataElementOptions(0));
const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
return Math.max(border, firstPoint, lastPoint) / 2;
}
}
var controllers = /*#__PURE__*/Object.freeze({
__proto__: null,
BarController: BarController,
BubbleController: BubbleController,
DoughnutController: DoughnutController,
LineController: LineController,
PieController: PieController,
PolarAreaController: PolarAreaController,
RadarController: RadarController,
ScatterController: ScatterController
});
/**
* @namespace Chart._adapters
* @since 2.8.0
* @private
*/ function chart_abstract() {
throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
}
/**
* Date adapter (current used by the time scale)
* @namespace Chart._adapters._date
* @memberof Chart._adapters
* @private
*/ class DateAdapterBase {
/**
* Override default date adapter methods.
* Accepts type parameter to define options type.
* @example
* Chart._adapters._date.override<{myAdapterOption: string}>({
* init() {
* console.log(this.options.myAdapterOption);
* }
* })
*/ static override(members) {
Object.assign(DateAdapterBase.prototype, members);
}
options;
constructor(options){
this.options = options || {};
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
init() {}
formats() {
return chart_abstract();
}
parse() {
return chart_abstract();
}
format() {
return chart_abstract();
}
add() {
return chart_abstract();
}
diff() {
return chart_abstract();
}
startOf() {
return chart_abstract();
}
endOf() {
return chart_abstract();
}
}
var adapters = {
_date: DateAdapterBase
};
function binarySearch(metaset, axis, value, intersect) {
const { controller , data , _sorted } = metaset;
const iScale = controller._cachedMeta.iScale;
const spanGaps = metaset.dataset ? metaset.dataset.options ? metaset.dataset.options.spanGaps : null : null;
if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
if (!intersect) {
const result = lookupMethod(data, axis, value);
if (spanGaps) {
const { vScale } = controller._cachedMeta;
const { _parsed } = metaset;
const distanceToDefinedLo = _parsed.slice(0, result.lo + 1).reverse().findIndex((point)=>!isNullOrUndef(point[vScale.axis]));
result.lo -= Math.max(0, distanceToDefinedLo);
const distanceToDefinedHi = _parsed.slice(result.hi).findIndex((point)=>!isNullOrUndef(point[vScale.axis]));
result.hi += Math.max(0, distanceToDefinedHi);
}
return result;
} else if (controller._sharedOptions) {
const el = data[0];
const range = typeof el.getRange === 'function' && el.getRange(axis);
if (range) {
const start = lookupMethod(data, axis, value - range);
const end = lookupMethod(data, axis, value + range);
return {
lo: start.lo,
hi: end.hi
};
}
}
}
return {
lo: 0,
hi: data.length - 1
};
}
function evaluateInteractionItems(chart, axis, position, handler, intersect) {
const metasets = chart.getSortedVisibleDatasetMetas();
const value = position[axis];
for(let i = 0, ilen = metasets.length; i < ilen; ++i){
const { index , data } = metasets[i];
const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
for(let j = lo; j <= hi; ++j){
const element = data[j];
if (!element.skip) {
handler(element, index, j);
}
}
}
}
function getDistanceMetricForAxis(axis) {
const useX = axis.indexOf('x') !== -1;
const useY = axis.indexOf('y') !== -1;
return function(pt1, pt2) {
const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
};
}
function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
const items = [];
if (!includeInvisible && !chart.isPointInArea(position)) {
return items;
}
const evaluationFunc = function(element, datasetIndex, index) {
if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
return;
}
if (element.inRange(position.x, position.y, useFinalPosition)) {
items.push({
element,
datasetIndex,
index
});
}
};
evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
return items;
}
function getNearestRadialItems(chart, position, axis, useFinalPosition) {
let items = [];
function evaluationFunc(element, datasetIndex, index) {
const { startAngle , endAngle } = element.getProps([
'startAngle',
'endAngle'
], useFinalPosition);
const { angle } = getAngleFromPoint(element, {
x: position.x,
y: position.y
});
if (_angleBetween(angle, startAngle, endAngle)) {
items.push({
element,
datasetIndex,
index
});
}
}
evaluateInteractionItems(chart, axis, position, evaluationFunc);
return items;
}
function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
let items = [];
const distanceMetric = getDistanceMetricForAxis(axis);
let minDistance = Number.POSITIVE_INFINITY;
function evaluationFunc(element, datasetIndex, index) {
const inRange = element.inRange(position.x, position.y, useFinalPosition);
if (intersect && !inRange) {
return;
}
const center = element.getCenterPoint(useFinalPosition);
const pointInArea = !!includeInvisible || chart.isPointInArea(center);
if (!pointInArea && !inRange) {
return;
}
const distance = distanceMetric(position, center);
if (distance < minDistance) {
items = [
{
element,
datasetIndex,
index
}
];
minDistance = distance;
} else if (distance === minDistance) {
items.push({
element,
datasetIndex,
index
});
}
}
evaluateInteractionItems(chart, axis, position, evaluationFunc);
return items;
}
function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
if (!includeInvisible && !chart.isPointInArea(position)) {
return [];
}
return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
}
function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
const items = [];
const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
let intersectsItem = false;
evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
if (element[rangeMethod] && element[rangeMethod](position[axis], useFinalPosition)) {
items.push({
element,
datasetIndex,
index
});
intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
}
});
if (intersect && !intersectsItem) {
return [];
}
return items;
}
var Interaction = {
evaluateInteractionItems,
modes: {
index (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
const axis = options.axis || 'x';
const includeInvisible = options.includeInvisible || false;
const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
const elements = [];
if (!items.length) {
return [];
}
chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
const index = items[0].index;
const element = meta.data[index];
if (element && !element.skip) {
elements.push({
element,
datasetIndex: meta.index,
index
});
}
});
return elements;
},
dataset (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
const axis = options.axis || 'xy';
const includeInvisible = options.includeInvisible || false;
let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
if (items.length > 0) {
const datasetIndex = items[0].datasetIndex;
const data = chart.getDatasetMeta(datasetIndex).data;
items = [];
for(let i = 0; i < data.length; ++i){
items.push({
element: data[i],
datasetIndex,
index: i
});
}
}
return items;
},
point (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
const axis = options.axis || 'xy';
const includeInvisible = options.includeInvisible || false;
return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
},
nearest (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
const axis = options.axis || 'xy';
const includeInvisible = options.includeInvisible || false;
return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
},
x (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
},
y (chart, e, options, useFinalPosition) {
const position = getRelativePosition(e, chart);
return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
}
}
};
const STATIC_POSITIONS = [
'left',
'top',
'right',
'bottom'
];
function filterByPosition(array, position) {
return array.filter((v)=>v.pos === position);
}
function filterDynamicPositionByAxis(array, axis) {
return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
}
function sortByWeight(array, reverse) {
return array.sort((a, b)=>{
const v0 = reverse ? b : a;
const v1 = reverse ? a : b;
return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
});
}
function wrapBoxes(boxes) {
const layoutBoxes = [];
let i, ilen, box, pos, stack, stackWeight;
for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
box = boxes[i];
({ position: pos , options: { stack , stackWeight =1 } } = box);
layoutBoxes.push({
index: i,
box,
pos,
horizontal: box.isHorizontal(),
weight: box.weight,
stack: stack && pos + stack,
stackWeight
});
}
return layoutBoxes;
}
function buildStacks(layouts) {
const stacks = {};
for (const wrap of layouts){
const { stack , pos , stackWeight } = wrap;
if (!stack || !STATIC_POSITIONS.includes(pos)) {
continue;
}
const _stack = stacks[stack] || (stacks[stack] = {
count: 0,
placed: 0,
weight: 0,
size: 0
});
_stack.count++;
_stack.weight += stackWeight;
}
return stacks;
}
function setLayoutDims(layouts, params) {
const stacks = buildStacks(layouts);
const { vBoxMaxWidth , hBoxMaxHeight } = params;
let i, ilen, layout;
for(i = 0, ilen = layouts.length; i < ilen; ++i){
layout = layouts[i];
const { fullSize } = layout.box;
const stack = stacks[layout.stack];
const factor = stack && layout.stackWeight / stack.weight;
if (layout.horizontal) {
layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
layout.height = hBoxMaxHeight;
} else {
layout.width = vBoxMaxWidth;
layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
}
}
return stacks;
}
function buildLayoutBoxes(boxes) {
const layoutBoxes = wrapBoxes(boxes);
const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
return {
fullSize,
leftAndTop: left.concat(top),
rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
chartArea: filterByPosition(layoutBoxes, 'chartArea'),
vertical: left.concat(right).concat(centerVertical),
horizontal: top.concat(bottom).concat(centerHorizontal)
};
}
function getCombinedMax(maxPadding, chartArea, a, b) {
return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
}
function updateMaxPadding(maxPadding, boxPadding) {
maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
}
function updateDims(chartArea, params, layout, stacks) {
const { pos , box } = layout;
const maxPadding = chartArea.maxPadding;
if (!isObject(pos)) {
if (layout.size) {
chartArea[pos] -= layout.size;
}
const stack = stacks[layout.stack] || {
size: 0,
count: 1
};
stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
layout.size = stack.size / stack.count;
chartArea[pos] += layout.size;
}
if (box.getPadding) {
updateMaxPadding(maxPadding, box.getPadding());
}
const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
const widthChanged = newWidth !== chartArea.w;
const heightChanged = newHeight !== chartArea.h;
chartArea.w = newWidth;
chartArea.h = newHeight;
return layout.horizontal ? {
same: widthChanged,
other: heightChanged
} : {
same: heightChanged,
other: widthChanged
};
}
function handleMaxPadding(chartArea) {
const maxPadding = chartArea.maxPadding;
function updatePos(pos) {
const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
chartArea[pos] += change;
return change;
}
chartArea.y += updatePos('top');
chartArea.x += updatePos('left');
updatePos('right');
updatePos('bottom');
}
function getMargins(horizontal, chartArea) {
const maxPadding = chartArea.maxPadding;
function marginForPositions(positions) {
const margin = {
left: 0,
top: 0,
right: 0,
bottom: 0
};
positions.forEach((pos)=>{
margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
});
return margin;
}
return horizontal ? marginForPositions([
'left',
'right'
]) : marginForPositions([
'top',
'bottom'
]);
}
function fitBoxes(boxes, chartArea, params, stacks) {
const refitBoxes = [];
let i, ilen, layout, box, refit, changed;
for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
layout = boxes[i];
box = layout.box;
box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
const { same , other } = updateDims(chartArea, params, layout, stacks);
refit |= same && refitBoxes.length;
changed = changed || other;
if (!box.fullSize) {
refitBoxes.push(layout);
}
}
return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
}
function setBoxDims(box, left, top, width, height) {
box.top = top;
box.left = left;
box.right = left + width;
box.bottom = top + height;
box.width = width;
box.height = height;
}
function placeBoxes(boxes, chartArea, params, stacks) {
const userPadding = params.padding;
let { x , y } = chartArea;
for (const layout of boxes){
const box = layout.box;
const stack = stacks[layout.stack] || {
count: 1,
placed: 0,
weight: 1
};
const weight = layout.stackWeight / stack.weight || 1;
if (layout.horizontal) {
const width = chartArea.w * weight;
const height = stack.size || box.height;
if (defined(stack.start)) {
y = stack.start;
}
if (box.fullSize) {
setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
} else {
setBoxDims(box, chartArea.left + stack.placed, y, width, height);
}
stack.start = y;
stack.placed += width;
y = box.bottom;
} else {
const height = chartArea.h * weight;
const width = stack.size || box.width;
if (defined(stack.start)) {
x = stack.start;
}
if (box.fullSize) {
setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
} else {
setBoxDims(box, x, chartArea.top + stack.placed, width, height);
}
stack.start = x;
stack.placed += height;
x = box.right;
}
}
chartArea.x = x;
chartArea.y = y;
}
var layouts = {
addBox (chart, item) {
if (!chart.boxes) {
chart.boxes = [];
}
item.fullSize = item.fullSize || false;
item.position = item.position || 'top';
item.weight = item.weight || 0;
item._layers = item._layers || function() {
return [
{
z: 0,
draw (chartArea) {
item.draw(chartArea);
}
}
];
};
chart.boxes.push(item);
},
removeBox (chart, layoutItem) {
const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
if (index !== -1) {
chart.boxes.splice(index, 1);
}
},
configure (chart, item, options) {
item.fullSize = options.fullSize;
item.position = options.position;
item.weight = options.weight;
},
update (chart, width, height, minPadding) {
if (!chart) {
return;
}
const padding = toPadding(chart.options.layout.padding);
const availableWidth = Math.max(width - padding.width, 0);
const availableHeight = Math.max(height - padding.height, 0);
const boxes = buildLayoutBoxes(chart.boxes);
const verticalBoxes = boxes.vertical;
const horizontalBoxes = boxes.horizontal;
each(chart.boxes, (box)=>{
if (typeof box.beforeLayout === 'function') {
box.beforeLayout();
}
});
const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
const params = Object.freeze({
outerWidth: width,
outerHeight: height,
padding,
availableWidth,
availableHeight,
vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
hBoxMaxHeight: availableHeight / 2
});
const maxPadding = Object.assign({}, padding);
updateMaxPadding(maxPadding, toPadding(minPadding));
const chartArea = Object.assign({
maxPadding,
w: availableWidth,
h: availableHeight,
x: padding.left,
y: padding.top
}, padding);
const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
fitBoxes(boxes.fullSize, chartArea, params, stacks);
fitBoxes(verticalBoxes, chartArea, params, stacks);
if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
fitBoxes(verticalBoxes, chartArea, params, stacks);
}
handleMaxPadding(chartArea);
placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
chartArea.x += chartArea.w;
chartArea.y += chartArea.h;
placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
chart.chartArea = {
left: chartArea.left,
top: chartArea.top,
right: chartArea.left + chartArea.w,
bottom: chartArea.top + chartArea.h,
height: chartArea.h,
width: chartArea.w
};
each(boxes.chartArea, (layout)=>{
const box = layout.box;
Object.assign(box, chart.chartArea);
box.update(chartArea.w, chartArea.h, {
left: 0,
top: 0,
right: 0,
bottom: 0
});
});
}
};
class BasePlatform {
acquireContext(canvas, aspectRatio) {}
releaseContext(context) {
return false;
}
addEventListener(chart, type, listener) {}
removeEventListener(chart, type, listener) {}
getDevicePixelRatio() {
return 1;
}
getMaximumSize(element, width, height, aspectRatio) {
width = Math.max(0, width || element.width);
height = height || element.height;
return {
width,
height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
};
}
isAttached(canvas) {
return true;
}
updateConfig(config) {
}
}
class BasicPlatform extends BasePlatform {
acquireContext(item) {
return item && item.getContext && item.getContext('2d') || null;
}
updateConfig(config) {
config.options.animation = false;
}
}
const EXPANDO_KEY = '$chartjs';
const EVENT_TYPES = {
touchstart: 'mousedown',
touchmove: 'mousemove',
touchend: 'mouseup',
pointerenter: 'mouseenter',
pointerdown: 'mousedown',
pointermove: 'mousemove',
pointerup: 'mouseup',
pointerleave: 'mouseout',
pointerout: 'mouseout'
};
const isNullOrEmpty = (value)=>value === null || value === '';
function initCanvas(canvas, aspectRatio) {
const style = canvas.style;
const renderHeight = canvas.getAttribute('height');
const renderWidth = canvas.getAttribute('width');
canvas[EXPANDO_KEY] = {
initial: {
height: renderHeight,
width: renderWidth,
style: {
display: style.display,
height: style.height,
width: style.width
}
}
};
style.display = style.display || 'block';
style.boxSizing = style.boxSizing || 'border-box';
if (isNullOrEmpty(renderWidth)) {
const displayWidth = readUsedSize(canvas, 'width');
if (displayWidth !== undefined) {
canvas.width = displayWidth;
}
}
if (isNullOrEmpty(renderHeight)) {
if (canvas.style.height === '') {
canvas.height = canvas.width / (aspectRatio || 2);
} else {
const displayHeight = readUsedSize(canvas, 'height');
if (displayHeight !== undefined) {
canvas.height = displayHeight;
}
}
}
return canvas;
}
const eventListenerOptions = supportsEventListenerOptions ? {
passive: true
} : false;
function addListener(node, type, listener) {
if (node) {
node.addEventListener(type, listener, eventListenerOptions);
}
}
function removeListener(chart, type, listener) {
if (chart && chart.canvas) {
chart.canvas.removeEventListener(type, listener, eventListenerOptions);
}
}
function fromNativeEvent(event, chart) {
const type = EVENT_TYPES[event.type] || event.type;
const { x , y } = getRelativePosition(event, chart);
return {
type,
chart,
native: event,
x: x !== undefined ? x : null,
y: y !== undefined ? y : null
};
}
function nodeListContains(nodeList, canvas) {
for (const node of nodeList){
if (node === canvas || node.contains(canvas)) {
return true;
}
}
}
function createAttachObserver(chart, type, listener) {
const canvas = chart.canvas;
const observer = new MutationObserver((entries)=>{
let trigger = false;
for (const entry of entries){
trigger = trigger || nodeListContains(entry.addedNodes, canvas);
trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
}
if (trigger) {
listener();
}
});
observer.observe(document, {
childList: true,
subtree: true
});
return observer;
}
function createDetachObserver(chart, type, listener) {
const canvas = chart.canvas;
const observer = new MutationObserver((entries)=>{
let trigger = false;
for (const entry of entries){
trigger = trigger || nodeListContains(entry.removedNodes, canvas);
trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
}
if (trigger) {
listener();
}
});
observer.observe(document, {
childList: true,
subtree: true
});
return observer;
}
const drpListeningCharts = new Map();
let oldDevicePixelRatio = 0;
function onWindowResize() {
const dpr = window.devicePixelRatio;
if (dpr === oldDevicePixelRatio) {
return;
}
oldDevicePixelRatio = dpr;
drpListeningCharts.forEach((resize, chart)=>{
if (chart.currentDevicePixelRatio !== dpr) {
resize();
}
});
}
function listenDevicePixelRatioChanges(chart, resize) {
if (!drpListeningCharts.size) {
window.addEventListener('resize', onWindowResize);
}
drpListeningCharts.set(chart, resize);
}
function unlistenDevicePixelRatioChanges(chart) {
drpListeningCharts.delete(chart);
if (!drpListeningCharts.size) {
window.removeEventListener('resize', onWindowResize);
}
}
function createResizeObserver(chart, type, listener) {
const canvas = chart.canvas;
const container = canvas && _getParentNode(canvas);
if (!container) {
return;
}
const resize = throttled((width, height)=>{
const w = container.clientWidth;
listener(width, height);
if (w < container.clientWidth) {
listener();
}
}, window);
const observer = new ResizeObserver((entries)=>{
const entry = entries[0];
const width = entry.contentRect.width;
const height = entry.contentRect.height;
if (width === 0 && height === 0) {
return;
}
resize(width, height);
});
observer.observe(container);
listenDevicePixelRatioChanges(chart, resize);
return observer;
}
function releaseObserver(chart, type, observer) {
if (observer) {
observer.disconnect();
}
if (type === 'resize') {
unlistenDevicePixelRatioChanges(chart);
}
}
function createProxyAndListen(chart, type, listener) {
const canvas = chart.canvas;
const proxy = throttled((event)=>{
if (chart.ctx !== null) {
listener(fromNativeEvent(event, chart));
}
}, chart);
addListener(canvas, type, proxy);
return proxy;
}
class DomPlatform extends BasePlatform {
acquireContext(canvas, aspectRatio) {
const context = canvas && canvas.getContext && canvas.getContext('2d');
if (context && context.canvas === canvas) {
initCanvas(canvas, aspectRatio);
return context;
}
return null;
}
releaseContext(context) {
const canvas = context.canvas;
if (!canvas[EXPANDO_KEY]) {
return false;
}
const initial = canvas[EXPANDO_KEY].initial;
[
'height',
'width'
].forEach((prop)=>{
const value = initial[prop];
if (isNullOrUndef(value)) {
canvas.removeAttribute(prop);
} else {
canvas.setAttribute(prop, value);
}
});
const style = initial.style || {};
Object.keys(style).forEach((key)=>{
canvas.style[key] = style[key];
});
canvas.width = canvas.width;
delete canvas[EXPANDO_KEY];
return true;
}
addEventListener(chart, type, listener) {
this.removeEventListener(chart, type);
const proxies = chart.$proxies || (chart.$proxies = {});
const handlers = {
attach: createAttachObserver,
detach: createDetachObserver,
resize: createResizeObserver
};
const handler = handlers[type] || createProxyAndListen;
proxies[type] = handler(chart, type, listener);
}
removeEventListener(chart, type) {
const proxies = chart.$proxies || (chart.$proxies = {});
const proxy = proxies[type];
if (!proxy) {
return;
}
const handlers = {
attach: releaseObserver,
detach: releaseObserver,
resize: releaseObserver
};
const handler = handlers[type] || removeListener;
handler(chart, type, proxy);
proxies[type] = undefined;
}
getDevicePixelRatio() {
return window.devicePixelRatio;
}
getMaximumSize(canvas, width, height, aspectRatio) {
return getMaximumSize(canvas, width, height, aspectRatio);
}
isAttached(canvas) {
const container = canvas && _getParentNode(canvas);
return !!(container && container.isConnected);
}
}
function _detectPlatform(canvas) {
if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
return BasicPlatform;
}
return DomPlatform;
}
class chart_Element {
static defaults = {};
static defaultRoutes = undefined;
x;
y;
active = false;
options;
$animations;
tooltipPosition(useFinalPosition) {
const { x , y } = this.getProps([
'x',
'y'
], useFinalPosition);
return {
x,
y
};
}
hasValue() {
return isNumber(this.x) && isNumber(this.y);
}
getProps(props, final) {
const anims = this.$animations;
if (!final || !anims) {
// let's not create an object, if not needed
return this;
}
const ret = {};
props.forEach((prop)=>{
ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
});
return ret;
}
}
function autoSkip(scale, ticks) {
const tickOpts = scale.options.ticks;
const determinedMaxTicks = determineMaxTicks(scale);
const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
const numMajorIndices = majorIndices.length;
const first = majorIndices[0];
const last = majorIndices[numMajorIndices - 1];
const newTicks = [];
if (numMajorIndices > ticksLimit) {
skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
return newTicks;
}
const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
if (numMajorIndices > 0) {
let i, ilen;
const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
}
skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
return newTicks;
}
skip(ticks, newTicks, spacing);
return newTicks;
}
function determineMaxTicks(scale) {
const offset = scale.options.offset;
const tickLength = scale._tickSize();
const maxScale = scale._length / tickLength + (offset ? 0 : 1);
const maxChart = scale._maxLength / tickLength;
return Math.floor(Math.min(maxScale, maxChart));
}
function calculateSpacing(majorIndices, ticks, ticksLimit) {
const evenMajorSpacing = getEvenSpacing(majorIndices);
const spacing = ticks.length / ticksLimit;
if (!evenMajorSpacing) {
return Math.max(spacing, 1);
}
const factors = _factorize(evenMajorSpacing);
for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
const factor = factors[i];
if (factor > spacing) {
return factor;
}
}
return Math.max(spacing, 1);
}
function getMajorIndices(ticks) {
const result = [];
let i, ilen;
for(i = 0, ilen = ticks.length; i < ilen; i++){
if (ticks[i].major) {
result.push(i);
}
}
return result;
}
function skipMajors(ticks, newTicks, majorIndices, spacing) {
let count = 0;
let next = majorIndices[0];
let i;
spacing = Math.ceil(spacing);
for(i = 0; i < ticks.length; i++){
if (i === next) {
newTicks.push(ticks[i]);
count++;
next = majorIndices[count * spacing];
}
}
}
function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
const start = valueOrDefault(majorStart, 0);
const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
let count = 0;
let length, i, next;
spacing = Math.ceil(spacing);
if (majorEnd) {
length = majorEnd - majorStart;
spacing = length / Math.floor(length / spacing);
}
next = start;
while(next < 0){
count++;
next = Math.round(start + count * spacing);
}
for(i = Math.max(start, 0); i < end; i++){
if (i === next) {
newTicks.push(ticks[i]);
count++;
next = Math.round(start + count * spacing);
}
}
}
function getEvenSpacing(arr) {
const len = arr.length;
let i, diff;
if (len < 2) {
return false;
}
for(diff = arr[0], i = 1; i < len; ++i){
if (arr[i] - arr[i - 1] !== diff) {
return false;
}
}
return diff;
}
const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
function sample(arr, numItems) {
const result = [];
const increment = arr.length / numItems;
const len = arr.length;
let i = 0;
for(; i < len; i += increment){
result.push(arr[Math.floor(i)]);
}
return result;
}
function getPixelForGridLine(scale, index, offsetGridLines) {
const length = scale.ticks.length;
const validIndex = Math.min(index, length - 1);
const start = scale._startPixel;
const end = scale._endPixel;
const epsilon = 1e-6;
let lineValue = scale.getPixelForTick(validIndex);
let offset;
if (offsetGridLines) {
if (length === 1) {
offset = Math.max(lineValue - start, end - lineValue);
} else if (index === 0) {
offset = (scale.getPixelForTick(1) - lineValue) / 2;
} else {
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
}
lineValue += validIndex < index ? offset : -offset;
if (lineValue < start - epsilon || lineValue > end + epsilon) {
return;
}
}
return lineValue;
}
function garbageCollect(caches, length) {
each(caches, (cache)=>{
const gc = cache.gc;
const gcLen = gc.length / 2;
let i;
if (gcLen > length) {
for(i = 0; i < gcLen; ++i){
delete cache.data[gc[i]];
}
gc.splice(0, gcLen);
}
});
}
function getTickMarkLength(options) {
return options.drawTicks ? options.tickLength : 0;
}
function getTitleHeight(options, fallback) {
if (!options.display) {
return 0;
}
const font = toFont(options.font, fallback);
const padding = toPadding(options.padding);
const lines = isArray(options.text) ? options.text.length : 1;
return lines * font.lineHeight + padding.height;
}
function createScaleContext(parent, scale) {
return createContext(parent, {
scale,
type: 'scale'
});
}
function createTickContext(parent, index, tick) {
return createContext(parent, {
tick,
index,
type: 'tick'
});
}
function titleAlign(align, position, reverse) {
let ret = _toLeftRightCenter(align);
if (reverse && position !== 'right' || !reverse && position === 'right') {
ret = reverseAlign(ret);
}
return ret;
}
function titleArgs(scale, offset, position, align) {
const { top , left , bottom , right , chart } = scale;
const { chartArea , scales } = chart;
let rotation = 0;
let maxWidth, titleX, titleY;
const height = bottom - top;
const width = right - left;
if (scale.isHorizontal()) {
titleX = _alignStartEnd(align, left, right);
if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
} else if (position === 'center') {
titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
} else {
titleY = offsetFromEdge(scale, position, offset);
}
maxWidth = right - left;
} else {
if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
} else if (position === 'center') {
titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
} else {
titleX = offsetFromEdge(scale, position, offset);
}
titleY = _alignStartEnd(align, bottom, top);
rotation = position === 'left' ? -HALF_PI : HALF_PI;
}
return {
titleX,
titleY,
maxWidth,
rotation
};
}
class Scale extends chart_Element {
constructor(cfg){
super();
this.id = cfg.id;
this.type = cfg.type;
this.options = undefined;
this.ctx = cfg.ctx;
this.chart = cfg.chart;
this.top = undefined;
this.bottom = undefined;
this.left = undefined;
this.right = undefined;
this.width = undefined;
this.height = undefined;
this._margins = {
left: 0,
right: 0,
top: 0,
bottom: 0
};
this.maxWidth = undefined;
this.maxHeight = undefined;
this.paddingTop = undefined;
this.paddingBottom = undefined;
this.paddingLeft = undefined;
this.paddingRight = undefined;
this.axis = undefined;
this.labelRotation = undefined;
this.min = undefined;
this.max = undefined;
this._range = undefined;
this.ticks = [];
this._gridLineItems = null;
this._labelItems = null;
this._labelSizes = null;
this._length = 0;
this._maxLength = 0;
this._longestTextCache = {};
this._startPixel = undefined;
this._endPixel = undefined;
this._reversePixels = false;
this._userMax = undefined;
this._userMin = undefined;
this._suggestedMax = undefined;
this._suggestedMin = undefined;
this._ticksLength = 0;
this._borderValue = 0;
this._cache = {};
this._dataLimitsCached = false;
this.$context = undefined;
}
init(options) {
this.options = options.setContext(this.getContext());
this.axis = options.axis;
this._userMin = this.parse(options.min);
this._userMax = this.parse(options.max);
this._suggestedMin = this.parse(options.suggestedMin);
this._suggestedMax = this.parse(options.suggestedMax);
}
parse(raw, index) {
return raw;
}
getUserBounds() {
let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
_userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
_userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
_suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
_suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
return {
min: finiteOrDefault(_userMin, _suggestedMin),
max: finiteOrDefault(_userMax, _suggestedMax),
minDefined: isNumberFinite(_userMin),
maxDefined: isNumberFinite(_userMax)
};
}
getMinMax(canStack) {
let { min , max , minDefined , maxDefined } = this.getUserBounds();
let range;
if (minDefined && maxDefined) {
return {
min,
max
};
}
const metas = this.getMatchingVisibleMetas();
for(let i = 0, ilen = metas.length; i < ilen; ++i){
range = metas[i].controller.getMinMax(this, canStack);
if (!minDefined) {
min = Math.min(min, range.min);
}
if (!maxDefined) {
max = Math.max(max, range.max);
}
}
min = maxDefined && min > max ? max : min;
max = minDefined && min > max ? min : max;
return {
min: finiteOrDefault(min, finiteOrDefault(max, min)),
max: finiteOrDefault(max, finiteOrDefault(min, max))
};
}
getPadding() {
return {
left: this.paddingLeft || 0,
top: this.paddingTop || 0,
right: this.paddingRight || 0,
bottom: this.paddingBottom || 0
};
}
getTicks() {
return this.ticks;
}
getLabels() {
const data = this.chart.data;
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
}
getLabelItems(chartArea = this.chart.chartArea) {
const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
return items;
}
beforeLayout() {
this._cache = {};
this._dataLimitsCached = false;
}
beforeUpdate() {
callback(this.options.beforeUpdate, [
this
]);
}
update(maxWidth, maxHeight, margins) {
const { beginAtZero , grace , ticks: tickOpts } = this.options;
const sampleSize = tickOpts.sampleSize;
this.beforeUpdate();
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this._margins = margins = Object.assign({
left: 0,
right: 0,
top: 0,
bottom: 0
}, margins);
this.ticks = null;
this._labelSizes = null;
this._gridLineItems = null;
this._labelItems = null;
this.beforeSetDimensions();
this.setDimensions();
this.afterSetDimensions();
this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
if (!this._dataLimitsCached) {
this.beforeDataLimits();
this.determineDataLimits();
this.afterDataLimits();
this._range = _addGrace(this, grace, beginAtZero);
this._dataLimitsCached = true;
}
this.beforeBuildTicks();
this.ticks = this.buildTicks() || [];
this.afterBuildTicks();
const samplingEnabled = sampleSize < this.ticks.length;
this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
this.configure();
this.beforeCalculateLabelRotation();
this.calculateLabelRotation();
this.afterCalculateLabelRotation();
if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
this.ticks = autoSkip(this, this.ticks);
this._labelSizes = null;
this.afterAutoSkip();
}
if (samplingEnabled) {
this._convertTicksToLabels(this.ticks);
}
this.beforeFit();
this.fit();
this.afterFit();
this.afterUpdate();
}
configure() {
let reversePixels = this.options.reverse;
let startPixel, endPixel;
if (this.isHorizontal()) {
startPixel = this.left;
endPixel = this.right;
} else {
startPixel = this.top;
endPixel = this.bottom;
reversePixels = !reversePixels;
}
this._startPixel = startPixel;
this._endPixel = endPixel;
this._reversePixels = reversePixels;
this._length = endPixel - startPixel;
this._alignToPixels = this.options.alignToPixels;
}
afterUpdate() {
callback(this.options.afterUpdate, [
this
]);
}
beforeSetDimensions() {
callback(this.options.beforeSetDimensions, [
this
]);
}
setDimensions() {
if (this.isHorizontal()) {
this.width = this.maxWidth;
this.left = 0;
this.right = this.width;
} else {
this.height = this.maxHeight;
this.top = 0;
this.bottom = this.height;
}
this.paddingLeft = 0;
this.paddingTop = 0;
this.paddingRight = 0;
this.paddingBottom = 0;
}
afterSetDimensions() {
callback(this.options.afterSetDimensions, [
this
]);
}
_callHooks(name) {
this.chart.notifyPlugins(name, this.getContext());
callback(this.options[name], [
this
]);
}
beforeDataLimits() {
this._callHooks('beforeDataLimits');
}
determineDataLimits() {}
afterDataLimits() {
this._callHooks('afterDataLimits');
}
beforeBuildTicks() {
this._callHooks('beforeBuildTicks');
}
buildTicks() {
return [];
}
afterBuildTicks() {
this._callHooks('afterBuildTicks');
}
beforeTickToLabelConversion() {
callback(this.options.beforeTickToLabelConversion, [
this
]);
}
generateTickLabels(ticks) {
const tickOpts = this.options.ticks;
let i, ilen, tick;
for(i = 0, ilen = ticks.length; i < ilen; i++){
tick = ticks[i];
tick.label = callback(tickOpts.callback, [
tick.value,
i,
ticks
], this);
}
}
afterTickToLabelConversion() {
callback(this.options.afterTickToLabelConversion, [
this
]);
}
beforeCalculateLabelRotation() {
callback(this.options.beforeCalculateLabelRotation, [
this
]);
}
calculateLabelRotation() {
const options = this.options;
const tickOpts = options.ticks;
const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
const minRotation = tickOpts.minRotation || 0;
const maxRotation = tickOpts.maxRotation;
let labelRotation = minRotation;
let tickWidth, maxHeight, maxLabelDiagonal;
if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
this.labelRotation = minRotation;
return;
}
const labelSizes = this._getLabelSizes();
const maxLabelWidth = labelSizes.widest.width;
const maxLabelHeight = labelSizes.highest.height;
const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
if (maxLabelWidth + 6 > tickWidth) {
tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
}
this.labelRotation = labelRotation;
}
afterCalculateLabelRotation() {
callback(this.options.afterCalculateLabelRotation, [
this
]);
}
afterAutoSkip() {}
beforeFit() {
callback(this.options.beforeFit, [
this
]);
}
fit() {
const minSize = {
width: 0,
height: 0
};
const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
const display = this._isVisible();
const isHorizontal = this.isHorizontal();
if (display) {
const titleHeight = getTitleHeight(titleOpts, chart.options.font);
if (isHorizontal) {
minSize.width = this.maxWidth;
minSize.height = getTickMarkLength(gridOpts) + titleHeight;
} else {
minSize.height = this.maxHeight;
minSize.width = getTickMarkLength(gridOpts) + titleHeight;
}
if (tickOpts.display && this.ticks.length) {
const { first , last , widest , highest } = this._getLabelSizes();
const tickPadding = tickOpts.padding * 2;
const angleRadians = toRadians(this.labelRotation);
const cos = Math.cos(angleRadians);
const sin = Math.sin(angleRadians);
if (isHorizontal) {
const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
} else {
const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
}
this._calculatePadding(first, last, sin, cos);
}
}
this._handleMargins();
if (isHorizontal) {
this.width = this._length = chart.width - this._margins.left - this._margins.right;
this.height = minSize.height;
} else {
this.width = minSize.width;
this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
}
}
_calculatePadding(first, last, sin, cos) {
const { ticks: { align , padding } , position } = this.options;
const isRotated = this.labelRotation !== 0;
const labelsBelowTicks = position !== 'top' && this.axis === 'x';
if (this.isHorizontal()) {
const offsetLeft = this.getPixelForTick(0) - this.left;
const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
let paddingLeft = 0;
let paddingRight = 0;
if (isRotated) {
if (labelsBelowTicks) {
paddingLeft = cos * first.width;
paddingRight = sin * last.height;
} else {
paddingLeft = sin * first.height;
paddingRight = cos * last.width;
}
} else if (align === 'start') {
paddingRight = last.width;
} else if (align === 'end') {
paddingLeft = first.width;
} else if (align !== 'inner') {
paddingLeft = first.width / 2;
paddingRight = last.width / 2;
}
this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
} else {
let paddingTop = last.height / 2;
let paddingBottom = first.height / 2;
if (align === 'start') {
paddingTop = 0;
paddingBottom = first.height;
} else if (align === 'end') {
paddingTop = last.height;
paddingBottom = 0;
}
this.paddingTop = paddingTop + padding;
this.paddingBottom = paddingBottom + padding;
}
}
_handleMargins() {
if (this._margins) {
this._margins.left = Math.max(this.paddingLeft, this._margins.left);
this._margins.top = Math.max(this.paddingTop, this._margins.top);
this._margins.right = Math.max(this.paddingRight, this._margins.right);
this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
}
}
afterFit() {
callback(this.options.afterFit, [
this
]);
}
isHorizontal() {
const { axis , position } = this.options;
return position === 'top' || position === 'bottom' || axis === 'x';
}
isFullSize() {
return this.options.fullSize;
}
_convertTicksToLabels(ticks) {
this.beforeTickToLabelConversion();
this.generateTickLabels(ticks);
let i, ilen;
for(i = 0, ilen = ticks.length; i < ilen; i++){
if (isNullOrUndef(ticks[i].label)) {
ticks.splice(i, 1);
ilen--;
i--;
}
}
this.afterTickToLabelConversion();
}
_getLabelSizes() {
let labelSizes = this._labelSizes;
if (!labelSizes) {
const sampleSize = this.options.ticks.sampleSize;
let ticks = this.ticks;
if (sampleSize < ticks.length) {
ticks = sample(ticks, sampleSize);
}
this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
}
return labelSizes;
}
_computeLabelSizes(ticks, length, maxTicksLimit) {
const { ctx , _longestTextCache: caches } = this;
const widths = [];
const heights = [];
const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
let widestLabelSize = 0;
let highestLabelSize = 0;
let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
for(i = 0; i < length; i += increment){
label = ticks[i].label;
tickFont = this._resolveTickFontOptions(i);
ctx.font = fontString = tickFont.string;
cache = caches[fontString] = caches[fontString] || {
data: {},
gc: []
};
lineHeight = tickFont.lineHeight;
width = height = 0;
if (!isNullOrUndef(label) && !isArray(label)) {
width = _measureText(ctx, cache.data, cache.gc, width, label);
height = lineHeight;
} else if (isArray(label)) {
for(j = 0, jlen = label.length; j < jlen; ++j){
nestedLabel = label[j];
if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
height += lineHeight;
}
}
}
widths.push(width);
heights.push(height);
widestLabelSize = Math.max(width, widestLabelSize);
highestLabelSize = Math.max(height, highestLabelSize);
}
garbageCollect(caches, length);
const widest = widths.indexOf(widestLabelSize);
const highest = heights.indexOf(highestLabelSize);
const valueAt = (idx)=>({
width: widths[idx] || 0,
height: heights[idx] || 0
});
return {
first: valueAt(0),
last: valueAt(length - 1),
widest: valueAt(widest),
highest: valueAt(highest),
widths,
heights
};
}
getLabelForValue(value) {
return value;
}
getPixelForValue(value, index) {
return NaN;
}
getValueForPixel(pixel) {}
getPixelForTick(index) {
const ticks = this.ticks;
if (index < 0 || index > ticks.length - 1) {
return null;
}
return this.getPixelForValue(ticks[index].value);
}
getPixelForDecimal(decimal) {
if (this._reversePixels) {
decimal = 1 - decimal;
}
const pixel = this._startPixel + decimal * this._length;
return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
}
getDecimalForPixel(pixel) {
const decimal = (pixel - this._startPixel) / this._length;
return this._reversePixels ? 1 - decimal : decimal;
}
getBasePixel() {
return this.getPixelForValue(this.getBaseValue());
}
getBaseValue() {
const { min , max } = this;
return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
}
getContext(index) {
const ticks = this.ticks || [];
if (index >= 0 && index < ticks.length) {
const tick = ticks[index];
return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
}
return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
}
_tickSize() {
const optionTicks = this.options.ticks;
const rot = toRadians(this.labelRotation);
const cos = Math.abs(Math.cos(rot));
const sin = Math.abs(Math.sin(rot));
const labelSizes = this._getLabelSizes();
const padding = optionTicks.autoSkipPadding || 0;
const w = labelSizes ? labelSizes.widest.width + padding : 0;
const h = labelSizes ? labelSizes.highest.height + padding : 0;
return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
}
_isVisible() {
const display = this.options.display;
if (display !== 'auto') {
return !!display;
}
return this.getMatchingVisibleMetas().length > 0;
}
_computeGridLineItems(chartArea) {
const axis = this.axis;
const chart = this.chart;
const options = this.options;
const { grid , position , border } = options;
const offset = grid.offset;
const isHorizontal = this.isHorizontal();
const ticks = this.ticks;
const ticksLength = ticks.length + (offset ? 1 : 0);
const tl = getTickMarkLength(grid);
const items = [];
const borderOpts = border.setContext(this.getContext());
const axisWidth = borderOpts.display ? borderOpts.width : 0;
const axisHalfWidth = axisWidth / 2;
const alignBorderValue = function(pixel) {
return _alignPixel(chart, pixel, axisWidth);
};
let borderValue, i, lineValue, alignedLineValue;
let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
if (position === 'top') {
borderValue = alignBorderValue(this.bottom);
ty1 = this.bottom - tl;
ty2 = borderValue - axisHalfWidth;
y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
y2 = chartArea.bottom;
} else if (position === 'bottom') {
borderValue = alignBorderValue(this.top);
y1 = chartArea.top;
y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
ty1 = borderValue + axisHalfWidth;
ty2 = this.top + tl;
} else if (position === 'left') {
borderValue = alignBorderValue(this.right);
tx1 = this.right - tl;
tx2 = borderValue - axisHalfWidth;
x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
x2 = chartArea.right;
} else if (position === 'right') {
borderValue = alignBorderValue(this.left);
x1 = chartArea.left;
x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
tx1 = borderValue + axisHalfWidth;
tx2 = this.left + tl;
} else if (axis === 'x') {
if (position === 'center') {
borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
}
y1 = chartArea.top;
y2 = chartArea.bottom;
ty1 = borderValue + axisHalfWidth;
ty2 = ty1 + tl;
} else if (axis === 'y') {
if (position === 'center') {
borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
}
tx1 = borderValue - axisHalfWidth;
tx2 = tx1 - tl;
x1 = chartArea.left;
x2 = chartArea.right;
}
const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
const step = Math.max(1, Math.ceil(ticksLength / limit));
for(i = 0; i < ticksLength; i += step){
const context = this.getContext(i);
const optsAtIndex = grid.setContext(context);
const optsAtIndexBorder = border.setContext(context);
const lineWidth = optsAtIndex.lineWidth;
const lineColor = optsAtIndex.color;
const borderDash = optsAtIndexBorder.dash || [];
const borderDashOffset = optsAtIndexBorder.dashOffset;
const tickWidth = optsAtIndex.tickWidth;
const tickColor = optsAtIndex.tickColor;
const tickBorderDash = optsAtIndex.tickBorderDash || [];
const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
lineValue = getPixelForGridLine(this, i, offset);
if (lineValue === undefined) {
continue;
}
alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
if (isHorizontal) {
tx1 = tx2 = x1 = x2 = alignedLineValue;
} else {
ty1 = ty2 = y1 = y2 = alignedLineValue;
}
items.push({
tx1,
ty1,
tx2,
ty2,
x1,
y1,
x2,
y2,
width: lineWidth,
color: lineColor,
borderDash,
borderDashOffset,
tickWidth,
tickColor,
tickBorderDash,
tickBorderDashOffset
});
}
this._ticksLength = ticksLength;
this._borderValue = borderValue;
return items;
}
_computeLabelItems(chartArea) {
const axis = this.axis;
const options = this.options;
const { position , ticks: optionTicks } = options;
const isHorizontal = this.isHorizontal();
const ticks = this.ticks;
const { align , crossAlign , padding , mirror } = optionTicks;
const tl = getTickMarkLength(options.grid);
const tickAndPadding = tl + padding;
const hTickAndPadding = mirror ? -padding : tickAndPadding;
const rotation = -toRadians(this.labelRotation);
const items = [];
let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
let textBaseline = 'middle';
if (position === 'top') {
y = this.bottom - hTickAndPadding;
textAlign = this._getXAxisLabelAlignment();
} else if (position === 'bottom') {
y = this.top + hTickAndPadding;
textAlign = this._getXAxisLabelAlignment();
} else if (position === 'left') {
const ret = this._getYAxisLabelAlignment(tl);
textAlign = ret.textAlign;
x = ret.x;
} else if (position === 'right') {
const ret = this._getYAxisLabelAlignment(tl);
textAlign = ret.textAlign;
x = ret.x;
} else if (axis === 'x') {
if (position === 'center') {
y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
}
textAlign = this._getXAxisLabelAlignment();
} else if (axis === 'y') {
if (position === 'center') {
x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
} else if (isObject(position)) {
const positionAxisID = Object.keys(position)[0];
const value = position[positionAxisID];
x = this.chart.scales[positionAxisID].getPixelForValue(value);
}
textAlign = this._getYAxisLabelAlignment(tl).textAlign;
}
if (axis === 'y') {
if (align === 'start') {
textBaseline = 'top';
} else if (align === 'end') {
textBaseline = 'bottom';
}
}
const labelSizes = this._getLabelSizes();
for(i = 0, ilen = ticks.length; i < ilen; ++i){
tick = ticks[i];
label = tick.label;
const optsAtIndex = optionTicks.setContext(this.getContext(i));
pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
font = this._resolveTickFontOptions(i);
lineHeight = font.lineHeight;
lineCount = isArray(label) ? label.length : 1;
const halfCount = lineCount / 2;
const color = optsAtIndex.color;
const strokeColor = optsAtIndex.textStrokeColor;
const strokeWidth = optsAtIndex.textStrokeWidth;
let tickTextAlign = textAlign;
if (isHorizontal) {
x = pixel;
if (textAlign === 'inner') {
if (i === ilen - 1) {
tickTextAlign = !this.options.reverse ? 'right' : 'left';
} else if (i === 0) {
tickTextAlign = !this.options.reverse ? 'left' : 'right';
} else {
tickTextAlign = 'center';
}
}
if (position === 'top') {
if (crossAlign === 'near' || rotation !== 0) {
textOffset = -lineCount * lineHeight + lineHeight / 2;
} else if (crossAlign === 'center') {
textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
} else {
textOffset = -labelSizes.highest.height + lineHeight / 2;
}
} else {
if (crossAlign === 'near' || rotation !== 0) {
textOffset = lineHeight / 2;
} else if (crossAlign === 'center') {
textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
} else {
textOffset = labelSizes.highest.height - lineCount * lineHeight;
}
}
if (mirror) {
textOffset *= -1;
}
if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
x += lineHeight / 2 * Math.sin(rotation);
}
} else {
y = pixel;
textOffset = (1 - lineCount) * lineHeight / 2;
}
let backdrop;
if (optsAtIndex.showLabelBackdrop) {
const labelPadding = toPadding(optsAtIndex.backdropPadding);
const height = labelSizes.heights[i];
const width = labelSizes.widths[i];
let top = textOffset - labelPadding.top;
let left = 0 - labelPadding.left;
switch(textBaseline){
case 'middle':
top -= height / 2;
break;
case 'bottom':
top -= height;
break;
}
switch(textAlign){
case 'center':
left -= width / 2;
break;
case 'right':
left -= width;
break;
case 'inner':
if (i === ilen - 1) {
left -= width;
} else if (i > 0) {
left -= width / 2;
}
break;
}
backdrop = {
left,
top,
width: width + labelPadding.width,
height: height + labelPadding.height,
color: optsAtIndex.backdropColor
};
}
items.push({
label,
font,
textOffset,
options: {
rotation,
color,
strokeColor,
strokeWidth,
textAlign: tickTextAlign,
textBaseline,
translation: [
x,
y
],
backdrop
}
});
}
return items;
}
_getXAxisLabelAlignment() {
const { position , ticks } = this.options;
const rotation = -toRadians(this.labelRotation);
if (rotation) {
return position === 'top' ? 'left' : 'right';
}
let align = 'center';
if (ticks.align === 'start') {
align = 'left';
} else if (ticks.align === 'end') {
align = 'right';
} else if (ticks.align === 'inner') {
align = 'inner';
}
return align;
}
_getYAxisLabelAlignment(tl) {
const { position , ticks: { crossAlign , mirror , padding } } = this.options;
const labelSizes = this._getLabelSizes();
const tickAndPadding = tl + padding;
const widest = labelSizes.widest.width;
let textAlign;
let x;
if (position === 'left') {
if (mirror) {
x = this.right + padding;
if (crossAlign === 'near') {
textAlign = 'left';
} else if (crossAlign === 'center') {
textAlign = 'center';
x += widest / 2;
} else {
textAlign = 'right';
x += widest;
}
} else {
x = this.right - tickAndPadding;
if (crossAlign === 'near') {
textAlign = 'right';
} else if (crossAlign === 'center') {
textAlign = 'center';
x -= widest / 2;
} else {
textAlign = 'left';
x = this.left;
}
}
} else if (position === 'right') {
if (mirror) {
x = this.left + padding;
if (crossAlign === 'near') {
textAlign = 'right';
} else if (crossAlign === 'center') {
textAlign = 'center';
x -= widest / 2;
} else {
textAlign = 'left';
x -= widest;
}
} else {
x = this.left + tickAndPadding;
if (crossAlign === 'near') {
textAlign = 'left';
} else if (crossAlign === 'center') {
textAlign = 'center';
x += widest / 2;
} else {
textAlign = 'right';
x = this.right;
}
}
} else {
textAlign = 'right';
}
return {
textAlign,
x
};
}
_computeLabelArea() {
if (this.options.ticks.mirror) {
return;
}
const chart = this.chart;
const position = this.options.position;
if (position === 'left' || position === 'right') {
return {
top: 0,
left: this.left,
bottom: chart.height,
right: this.right
};
}
if (position === 'top' || position === 'bottom') {
return {
top: this.top,
left: 0,
bottom: this.bottom,
right: chart.width
};
}
}
drawBackground() {
const { ctx , options: { backgroundColor } , left , top , width , height } = this;
if (backgroundColor) {
ctx.save();
ctx.fillStyle = backgroundColor;
ctx.fillRect(left, top, width, height);
ctx.restore();
}
}
getLineWidthForValue(value) {
const grid = this.options.grid;
if (!this._isVisible() || !grid.display) {
return 0;
}
const ticks = this.ticks;
const index = ticks.findIndex((t)=>t.value === value);
if (index >= 0) {
const opts = grid.setContext(this.getContext(index));
return opts.lineWidth;
}
return 0;
}
drawGrid(chartArea) {
const grid = this.options.grid;
const ctx = this.ctx;
const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
let i, ilen;
const drawLine = (p1, p2, style)=>{
if (!style.width || !style.color) {
return;
}
ctx.save();
ctx.lineWidth = style.width;
ctx.strokeStyle = style.color;
ctx.setLineDash(style.borderDash || []);
ctx.lineDashOffset = style.borderDashOffset;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
ctx.restore();
};
if (grid.display) {
for(i = 0, ilen = items.length; i < ilen; ++i){
const item = items[i];
if (grid.drawOnChartArea) {
drawLine({
x: item.x1,
y: item.y1
}, {
x: item.x2,
y: item.y2
}, item);
}
if (grid.drawTicks) {
drawLine({
x: item.tx1,
y: item.ty1
}, {
x: item.tx2,
y: item.ty2
}, {
color: item.tickColor,
width: item.tickWidth,
borderDash: item.tickBorderDash,
borderDashOffset: item.tickBorderDashOffset
});
}
}
}
}
drawBorder() {
const { chart , ctx , options: { border , grid } } = this;
const borderOpts = border.setContext(this.getContext());
const axisWidth = border.display ? borderOpts.width : 0;
if (!axisWidth) {
return;
}
const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
const borderValue = this._borderValue;
let x1, x2, y1, y2;
if (this.isHorizontal()) {
x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
y1 = y2 = borderValue;
} else {
y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
x1 = x2 = borderValue;
}
ctx.save();
ctx.lineWidth = borderOpts.width;
ctx.strokeStyle = borderOpts.color;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.restore();
}
drawLabels(chartArea) {
const optionTicks = this.options.ticks;
if (!optionTicks.display) {
return;
}
const ctx = this.ctx;
const area = this._computeLabelArea();
if (area) {
clipArea(ctx, area);
}
const items = this.getLabelItems(chartArea);
for (const item of items){
const renderTextOptions = item.options;
const tickFont = item.font;
const label = item.label;
const y = item.textOffset;
renderText(ctx, label, 0, y, tickFont, renderTextOptions);
}
if (area) {
unclipArea(ctx);
}
}
drawTitle() {
const { ctx , options: { position , title , reverse } } = this;
if (!title.display) {
return;
}
const font = toFont(title.font);
const padding = toPadding(title.padding);
const align = title.align;
let offset = font.lineHeight / 2;
if (position === 'bottom' || position === 'center' || isObject(position)) {
offset += padding.bottom;
if (isArray(title.text)) {
offset += font.lineHeight * (title.text.length - 1);
}
} else {
offset += padding.top;
}
const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
renderText(ctx, title.text, 0, 0, font, {
color: title.color,
maxWidth,
rotation,
textAlign: titleAlign(align, position, reverse),
textBaseline: 'middle',
translation: [
titleX,
titleY
]
});
}
draw(chartArea) {
if (!this._isVisible()) {
return;
}
this.drawBackground();
this.drawGrid(chartArea);
this.drawBorder();
this.drawTitle();
this.drawLabels(chartArea);
}
_layers() {
const opts = this.options;
const tz = opts.ticks && opts.ticks.z || 0;
const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
const bz = valueOrDefault(opts.border && opts.border.z, 0);
if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
return [
{
z: tz,
draw: (chartArea)=>{
this.draw(chartArea);
}
}
];
}
return [
{
z: gz,
draw: (chartArea)=>{
this.drawBackground();
this.drawGrid(chartArea);
this.drawTitle();
}
},
{
z: bz,
draw: ()=>{
this.drawBorder();
}
},
{
z: tz,
draw: (chartArea)=>{
this.drawLabels(chartArea);
}
}
];
}
getMatchingVisibleMetas(type) {
const metas = this.chart.getSortedVisibleDatasetMetas();
const axisID = this.axis + 'AxisID';
const result = [];
let i, ilen;
for(i = 0, ilen = metas.length; i < ilen; ++i){
const meta = metas[i];
if (meta[axisID] === this.id && (!type || meta.type === type)) {
result.push(meta);
}
}
return result;
}
_resolveTickFontOptions(index) {
const opts = this.options.ticks.setContext(this.getContext(index));
return toFont(opts.font);
}
_maxDigits() {
const fontSize = this._resolveTickFontOptions(0).lineHeight;
return (this.isHorizontal() ? this.width : this.height) / fontSize;
}
}
class TypedRegistry {
constructor(type, scope, override){
this.type = type;
this.scope = scope;
this.override = override;
this.items = Object.create(null);
}
isForType(type) {
return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
}
register(item) {
const proto = Object.getPrototypeOf(item);
let parentScope;
if (isIChartComponent(proto)) {
parentScope = this.register(proto);
}
const items = this.items;
const id = item.id;
const scope = this.scope + '.' + id;
if (!id) {
throw new Error('class does not have id: ' + item);
}
if (id in items) {
return scope;
}
items[id] = item;
registerDefaults(item, scope, parentScope);
if (this.override) {
defaults.override(item.id, item.overrides);
}
return scope;
}
get(id) {
return this.items[id];
}
unregister(item) {
const items = this.items;
const id = item.id;
const scope = this.scope;
if (id in items) {
delete items[id];
}
if (scope && id in defaults[scope]) {
delete defaults[scope][id];
if (this.override) {
delete overrides[id];
}
}
}
}
function registerDefaults(item, scope, parentScope) {
const itemDefaults = merge(Object.create(null), [
parentScope ? defaults.get(parentScope) : {},
defaults.get(scope),
item.defaults
]);
defaults.set(scope, itemDefaults);
if (item.defaultRoutes) {
routeDefaults(scope, item.defaultRoutes);
}
if (item.descriptors) {
defaults.describe(scope, item.descriptors);
}
}
function routeDefaults(scope, routes) {
Object.keys(routes).forEach((property)=>{
const propertyParts = property.split('.');
const sourceName = propertyParts.pop();
const sourceScope = [
scope
].concat(propertyParts).join('.');
const parts = routes[property].split('.');
const targetName = parts.pop();
const targetScope = parts.join('.');
defaults.route(sourceScope, sourceName, targetScope, targetName);
});
}
function isIChartComponent(proto) {
return 'id' in proto && 'defaults' in proto;
}
class Registry {
constructor(){
this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
this.elements = new TypedRegistry(chart_Element, 'elements');
this.plugins = new TypedRegistry(Object, 'plugins');
this.scales = new TypedRegistry(Scale, 'scales');
this._typedRegistries = [
this.controllers,
this.scales,
this.elements
];
}
add(...args) {
this._each('register', args);
}
remove(...args) {
this._each('unregister', args);
}
addControllers(...args) {
this._each('register', args, this.controllers);
}
addElements(...args) {
this._each('register', args, this.elements);
}
addPlugins(...args) {
this._each('register', args, this.plugins);
}
addScales(...args) {
this._each('register', args, this.scales);
}
getController(id) {
return this._get(id, this.controllers, 'controller');
}
getElement(id) {
return this._get(id, this.elements, 'element');
}
getPlugin(id) {
return this._get(id, this.plugins, 'plugin');
}
getScale(id) {
return this._get(id, this.scales, 'scale');
}
removeControllers(...args) {
this._each('unregister', args, this.controllers);
}
removeElements(...args) {
this._each('unregister', args, this.elements);
}
removePlugins(...args) {
this._each('unregister', args, this.plugins);
}
removeScales(...args) {
this._each('unregister', args, this.scales);
}
_each(method, args, typedRegistry) {
[
...args
].forEach((arg)=>{
const reg = typedRegistry || this._getRegistryForType(arg);
if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
this._exec(method, reg, arg);
} else {
each(arg, (item)=>{
const itemReg = typedRegistry || this._getRegistryForType(item);
this._exec(method, itemReg, item);
});
}
});
}
_exec(method, registry, component) {
const camelMethod = _capitalize(method);
callback(component['before' + camelMethod], [], component);
registry[method](component);
callback(component['after' + camelMethod], [], component);
}
_getRegistryForType(type) {
for(let i = 0; i < this._typedRegistries.length; i++){
const reg = this._typedRegistries[i];
if (reg.isForType(type)) {
return reg;
}
}
return this.plugins;
}
_get(id, typedRegistry, type) {
const item = typedRegistry.get(id);
if (item === undefined) {
throw new Error('"' + id + '" is not a registered ' + type + '.');
}
return item;
}
}
var registry = /* #__PURE__ */ new Registry();
class PluginService {
constructor(){
this._init = undefined;
}
notify(chart, hook, args, filter) {
if (hook === 'beforeInit') {
this._init = this._createDescriptors(chart, true);
this._notify(this._init, chart, 'install');
}
if (this._init === undefined) {
return;
}
const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
const result = this._notify(descriptors, chart, hook, args);
if (hook === 'afterDestroy') {
this._notify(descriptors, chart, 'stop');
this._notify(this._init, chart, 'uninstall');
this._init = undefined;
}
return result;
}
_notify(descriptors, chart, hook, args) {
args = args || {};
for (const descriptor of descriptors){
const plugin = descriptor.plugin;
const method = plugin[hook];
const params = [
chart,
args,
descriptor.options
];
if (callback(method, params, plugin) === false && args.cancelable) {
return false;
}
}
return true;
}
invalidate() {
if (!isNullOrUndef(this._cache)) {
this._oldCache = this._cache;
this._cache = undefined;
}
}
_descriptors(chart) {
if (this._cache) {
return this._cache;
}
const descriptors = this._cache = this._createDescriptors(chart);
this._notifyStateChanges(chart);
return descriptors;
}
_createDescriptors(chart, all) {
const config = chart && chart.config;
const options = valueOrDefault(config.options && config.options.plugins, {});
const plugins = allPlugins(config);
return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
}
_notifyStateChanges(chart) {
const previousDescriptors = this._oldCache || [];
const descriptors = this._cache;
const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
this._notify(diff(descriptors, previousDescriptors), chart, 'start');
}
}
function allPlugins(config) {
const localIds = {};
const plugins = [];
const keys = Object.keys(registry.plugins.items);
for(let i = 0; i < keys.length; i++){
plugins.push(registry.getPlugin(keys[i]));
}
const local = config.plugins || [];
for(let i = 0; i < local.length; i++){
const plugin = local[i];
if (plugins.indexOf(plugin) === -1) {
plugins.push(plugin);
localIds[plugin.id] = true;
}
}
return {
plugins,
localIds
};
}
function getOpts(options, all) {
if (!all && options === false) {
return null;
}
if (options === true) {
return {};
}
return options;
}
function createDescriptors(chart, { plugins , localIds }, options, all) {
const result = [];
const context = chart.getContext();
for (const plugin of plugins){
const id = plugin.id;
const opts = getOpts(options[id], all);
if (opts === null) {
continue;
}
result.push({
plugin,
options: pluginOpts(chart.config, {
plugin,
local: localIds[id]
}, opts, context)
});
}
return result;
}
function pluginOpts(config, { plugin , local }, opts, context) {
const keys = config.pluginScopeKeys(plugin);
const scopes = config.getOptionScopes(opts, keys);
if (local && plugin.defaults) {
scopes.push(plugin.defaults);
}
return config.createResolver(scopes, context, [
''
], {
scriptable: false,
indexable: false,
allKeys: true
});
}
function getIndexAxis(type, options) {
const datasetDefaults = defaults.datasets[type] || {};
const datasetOptions = (options.datasets || {})[type] || {};
return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
}
function getAxisFromDefaultScaleID(id, indexAxis) {
let axis = id;
if (id === '_index_') {
axis = indexAxis;
} else if (id === '_value_') {
axis = indexAxis === 'x' ? 'y' : 'x';
}
return axis;
}
function getDefaultScaleIDFromAxis(axis, indexAxis) {
return axis === indexAxis ? '_index_' : '_value_';
}
function idMatchesAxis(id) {
if (id === 'x' || id === 'y' || id === 'r') {
return id;
}
}
function axisFromPosition(position) {
if (position === 'top' || position === 'bottom') {
return 'x';
}
if (position === 'left' || position === 'right') {
return 'y';
}
}
function determineAxis(id, ...scaleOptions) {
if (idMatchesAxis(id)) {
return id;
}
for (const opts of scaleOptions){
const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
if (axis) {
return axis;
}
}
throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
}
function getAxisFromDataset(id, axis, dataset) {
if (dataset[axis + 'AxisID'] === id) {
return {
axis
};
}
}
function retrieveAxisFromDatasets(id, config) {
if (config.data && config.data.datasets) {
const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id);
if (boundDs.length) {
return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
}
}
return {};
}
function mergeScaleConfig(config, options) {
const chartDefaults = overrides[config.type] || {
scales: {}
};
const configScales = options.scales || {};
const chartIndexAxis = getIndexAxis(config.type, options);
const scales = Object.create(null);
Object.keys(configScales).forEach((id)=>{
const scaleConf = configScales[id];
if (!isObject(scaleConf)) {
return console.error(`Invalid scale configuration for scale: ${id}`);
}
if (scaleConf._proxy) {
return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
}
const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);
const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
const defaultScaleOptions = chartDefaults.scales || {};
scales[id] = mergeIf(Object.create(null), [
{
axis
},
scaleConf,
defaultScaleOptions[axis],
defaultScaleOptions[defaultId]
]);
});
config.data.datasets.forEach((dataset)=>{
const type = dataset.type || config.type;
const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
const datasetDefaults = overrides[type] || {};
const defaultScaleOptions = datasetDefaults.scales || {};
Object.keys(defaultScaleOptions).forEach((defaultID)=>{
const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
const id = dataset[axis + 'AxisID'] || axis;
scales[id] = scales[id] || Object.create(null);
mergeIf(scales[id], [
{
axis
},
configScales[id],
defaultScaleOptions[defaultID]
]);
});
});
Object.keys(scales).forEach((key)=>{
const scale = scales[key];
mergeIf(scale, [
defaults.scales[scale.type],
defaults.scale
]);
});
return scales;
}
function initOptions(config) {
const options = config.options || (config.options = {});
options.plugins = valueOrDefault(options.plugins, {});
options.scales = mergeScaleConfig(config, options);
}
function initData(data) {
data = data || {};
data.datasets = data.datasets || [];
data.labels = data.labels || [];
return data;
}
function initConfig(config) {
config = config || {};
config.data = initData(config.data);
initOptions(config);
return config;
}
const keyCache = new Map();
const keysCached = new Set();
function cachedKeys(cacheKey, generate) {
let keys = keyCache.get(cacheKey);
if (!keys) {
keys = generate();
keyCache.set(cacheKey, keys);
keysCached.add(keys);
}
return keys;
}
const addIfFound = (set, obj, key)=>{
const opts = resolveObjectKey(obj, key);
if (opts !== undefined) {
set.add(opts);
}
};
class Config {
constructor(config){
this._config = initConfig(config);
this._scopeCache = new Map();
this._resolverCache = new Map();
}
get platform() {
return this._config.platform;
}
get type() {
return this._config.type;
}
set type(type) {
this._config.type = type;
}
get data() {
return this._config.data;
}
set data(data) {
this._config.data = initData(data);
}
get options() {
return this._config.options;
}
set options(options) {
this._config.options = options;
}
get plugins() {
return this._config.plugins;
}
update() {
const config = this._config;
this.clearCache();
initOptions(config);
}
clearCache() {
this._scopeCache.clear();
this._resolverCache.clear();
}
datasetScopeKeys(datasetType) {
return cachedKeys(datasetType, ()=>[
[
`datasets.${datasetType}`,
''
]
]);
}
datasetAnimationScopeKeys(datasetType, transition) {
return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
[
`datasets.${datasetType}.transitions.${transition}`,
`transitions.${transition}`
],
[
`datasets.${datasetType}`,
''
]
]);
}
datasetElementScopeKeys(datasetType, elementType) {
return cachedKeys(`${datasetType}-${elementType}`, ()=>[
[
`datasets.${datasetType}.elements.${elementType}`,
`datasets.${datasetType}`,
`elements.${elementType}`,
''
]
]);
}
pluginScopeKeys(plugin) {
const id = plugin.id;
const type = this.type;
return cachedKeys(`${type}-plugin-${id}`, ()=>[
[
`plugins.${id}`,
...plugin.additionalOptionScopes || []
]
]);
}
_cachedScopes(mainScope, resetCache) {
const _scopeCache = this._scopeCache;
let cache = _scopeCache.get(mainScope);
if (!cache || resetCache) {
cache = new Map();
_scopeCache.set(mainScope, cache);
}
return cache;
}
getOptionScopes(mainScope, keyLists, resetCache) {
const { options , type } = this;
const cache = this._cachedScopes(mainScope, resetCache);
const cached = cache.get(keyLists);
if (cached) {
return cached;
}
const scopes = new Set();
keyLists.forEach((keys)=>{
if (mainScope) {
scopes.add(mainScope);
keys.forEach((key)=>addIfFound(scopes, mainScope, key));
}
keys.forEach((key)=>addIfFound(scopes, options, key));
keys.forEach((key)=>addIfFound(scopes, overrides[type] || {}, key));
keys.forEach((key)=>addIfFound(scopes, defaults, key));
keys.forEach((key)=>addIfFound(scopes, descriptors, key));
});
const array = Array.from(scopes);
if (array.length === 0) {
array.push(Object.create(null));
}
if (keysCached.has(keyLists)) {
cache.set(keyLists, array);
}
return array;
}
chartOptionScopes() {
const { options , type } = this;
return [
options,
overrides[type] || {},
defaults.datasets[type] || {},
{
type
},
defaults,
descriptors
];
}
resolveNamedOptions(scopes, names, context, prefixes = [
''
]) {
const result = {
$shared: true
};
const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
let options = resolver;
if (needContext(resolver, names)) {
result.$shared = false;
context = isFunction(context) ? context() : context;
const subResolver = this.createResolver(scopes, context, subPrefixes);
options = _attachContext(resolver, context, subResolver);
}
for (const prop of names){
result[prop] = options[prop];
}
return result;
}
createResolver(scopes, context, prefixes = [
''
], descriptorDefaults) {
const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
}
}
function getResolver(resolverCache, scopes, prefixes) {
let cache = resolverCache.get(scopes);
if (!cache) {
cache = new Map();
resolverCache.set(scopes, cache);
}
const cacheKey = prefixes.join();
let cached = cache.get(cacheKey);
if (!cached) {
const resolver = _createResolver(scopes, prefixes);
cached = {
resolver,
subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
};
cache.set(cacheKey, cached);
}
return cached;
}
const hasFunction = (value)=>isObject(value) && Object.getOwnPropertyNames(value).some((key)=>isFunction(value[key]));
function needContext(proxy, names) {
const { isScriptable , isIndexable } = _descriptors(proxy);
for (const prop of names){
const scriptable = isScriptable(prop);
const indexable = isIndexable(prop);
const value = (indexable || scriptable) && proxy[prop];
if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) {
return true;
}
}
return false;
}
var version = "4.5.1";
const KNOWN_POSITIONS = [
'top',
'bottom',
'left',
'right',
'chartArea'
];
function positionIsHorizontal(position, axis) {
return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
}
function compare2Level(l1, l2) {
return function(a, b) {
return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
};
}
function onAnimationsComplete(context) {
const chart = context.chart;
const animationOptions = chart.options.animation;
chart.notifyPlugins('afterRender');
callback(animationOptions && animationOptions.onComplete, [
context
], chart);
}
function onAnimationProgress(context) {
const chart = context.chart;
const animationOptions = chart.options.animation;
callback(animationOptions && animationOptions.onProgress, [
context
], chart);
}
function getCanvas(item) {
if (_isDomSupported() && typeof item === 'string') {
item = document.getElementById(item);
} else if (item && item.length) {
item = item[0];
}
if (item && item.canvas) {
item = item.canvas;
}
return item;
}
const instances = {};
const getChart = (key)=>{
const canvas = getCanvas(key);
return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
};
function moveNumericKeys(obj, start, move) {
const keys = Object.keys(obj);
for (const key of keys){
const intKey = +key;
if (intKey >= start) {
const value = obj[key];
delete obj[key];
if (move > 0 || intKey > start) {
obj[intKey + move] = value;
}
}
}
}
function determineLastEvent(e, lastEvent, inChartArea, isClick) {
if (!inChartArea || e.type === 'mouseout') {
return null;
}
if (isClick) {
return lastEvent;
}
return e;
}
class chart_Chart {
static defaults = defaults;
static instances = instances;
static overrides = overrides;
static registry = registry;
static version = version;
static getChart = getChart;
static register(...items) {
registry.add(...items);
invalidatePlugins();
}
static unregister(...items) {
registry.remove(...items);
invalidatePlugins();
}
constructor(item, userConfig){
const config = this.config = new Config(userConfig);
const initialCanvas = getCanvas(item);
const existingChart = getChart(initialCanvas);
if (existingChart) {
throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
}
const options = config.createResolver(config.chartOptionScopes(), this.getContext());
this.platform = new (config.platform || _detectPlatform(initialCanvas))();
this.platform.updateConfig(config);
const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
const canvas = context && context.canvas;
const height = canvas && canvas.height;
const width = canvas && canvas.width;
this.id = uid();
this.ctx = context;
this.canvas = canvas;
this.width = width;
this.height = height;
this._options = options;
this._aspectRatio = this.aspectRatio;
this._layers = [];
this._metasets = [];
this._stacks = undefined;
this.boxes = [];
this.currentDevicePixelRatio = undefined;
this.chartArea = undefined;
this._active = [];
this._lastEvent = undefined;
this._listeners = {};
this._responsiveListeners = undefined;
this._sortedMetasets = [];
this.scales = {};
this._plugins = new PluginService();
this.$proxies = {};
this._hiddenIndices = {};
this.attached = false;
this._animationsDisabled = undefined;
this.$context = undefined;
this._doResize = debounce((mode)=>this.update(mode), options.resizeDelay || 0);
this._dataChanges = [];
instances[this.id] = this;
if (!context || !canvas) {
console.error("Failed to create chart: can't acquire context from the given item");
return;
}
animator.listen(this, 'complete', onAnimationsComplete);
animator.listen(this, 'progress', onAnimationProgress);
this._initialize();
if (this.attached) {
this.update();
}
}
get aspectRatio() {
const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
if (!isNullOrUndef(aspectRatio)) {
return aspectRatio;
}
if (maintainAspectRatio && _aspectRatio) {
return _aspectRatio;
}
return height ? width / height : null;
}
get data() {
return this.config.data;
}
set data(data) {
this.config.data = data;
}
get options() {
return this._options;
}
set options(options) {
this.config.options = options;
}
get registry() {
return registry;
}
_initialize() {
this.notifyPlugins('beforeInit');
if (this.options.responsive) {
this.resize();
} else {
retinaScale(this, this.options.devicePixelRatio);
}
this.bindEvents();
this.notifyPlugins('afterInit');
return this;
}
clear() {
clearCanvas(this.canvas, this.ctx);
return this;
}
stop() {
animator.stop(this);
return this;
}
resize(width, height) {
if (!animator.running(this)) {
this._resize(width, height);
} else {
this._resizeBeforeDraw = {
width,
height
};
}
}
_resize(width, height) {
const options = this.options;
const canvas = this.canvas;
const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
const mode = this.width ? 'resize' : 'attach';
this.width = newSize.width;
this.height = newSize.height;
this._aspectRatio = this.aspectRatio;
if (!retinaScale(this, newRatio, true)) {
return;
}
this.notifyPlugins('resize', {
size: newSize
});
callback(options.onResize, [
this,
newSize
], this);
if (this.attached) {
if (this._doResize(mode)) {
this.render();
}
}
}
ensureScalesHaveIDs() {
const options = this.options;
const scalesOptions = options.scales || {};
each(scalesOptions, (axisOptions, axisID)=>{
axisOptions.id = axisID;
});
}
buildOrUpdateScales() {
const options = this.options;
const scaleOpts = options.scales;
const scales = this.scales;
const updated = Object.keys(scales).reduce((obj, id)=>{
obj[id] = false;
return obj;
}, {});
let items = [];
if (scaleOpts) {
items = items.concat(Object.keys(scaleOpts).map((id)=>{
const scaleOptions = scaleOpts[id];
const axis = determineAxis(id, scaleOptions);
const isRadial = axis === 'r';
const isHorizontal = axis === 'x';
return {
options: scaleOptions,
dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
};
}));
}
each(items, (item)=>{
const scaleOptions = item.options;
const id = scaleOptions.id;
const axis = determineAxis(id, scaleOptions);
const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
scaleOptions.position = item.dposition;
}
updated[id] = true;
let scale = null;
if (id in scales && scales[id].type === scaleType) {
scale = scales[id];
} else {
const scaleClass = registry.getScale(scaleType);
scale = new scaleClass({
id,
type: scaleType,
ctx: this.ctx,
chart: this
});
scales[scale.id] = scale;
}
scale.init(scaleOptions, options);
});
each(updated, (hasUpdated, id)=>{
if (!hasUpdated) {
delete scales[id];
}
});
each(scales, (scale)=>{
layouts.configure(this, scale, scale.options);
layouts.addBox(this, scale);
});
}
_updateMetasets() {
const metasets = this._metasets;
const numData = this.data.datasets.length;
const numMeta = metasets.length;
metasets.sort((a, b)=>a.index - b.index);
if (numMeta > numData) {
for(let i = numData; i < numMeta; ++i){
this._destroyDatasetMeta(i);
}
metasets.splice(numData, numMeta - numData);
}
this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
}
_removeUnreferencedMetasets() {
const { _metasets: metasets , data: { datasets } } = this;
if (metasets.length > datasets.length) {
delete this._stacks;
}
metasets.forEach((meta, index)=>{
if (datasets.filter((x)=>x === meta._dataset).length === 0) {
this._destroyDatasetMeta(index);
}
});
}
buildOrUpdateControllers() {
const newControllers = [];
const datasets = this.data.datasets;
let i, ilen;
this._removeUnreferencedMetasets();
for(i = 0, ilen = datasets.length; i < ilen; i++){
const dataset = datasets[i];
let meta = this.getDatasetMeta(i);
const type = dataset.type || this.config.type;
if (meta.type && meta.type !== type) {
this._destroyDatasetMeta(i);
meta = this.getDatasetMeta(i);
}
meta.type = type;
meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
meta.order = dataset.order || 0;
meta.index = i;
meta.label = '' + dataset.label;
meta.visible = this.isDatasetVisible(i);
if (meta.controller) {
meta.controller.updateIndex(i);
meta.controller.linkScales();
} else {
const ControllerClass = registry.getController(type);
const { datasetElementType , dataElementType } = defaults.datasets[type];
Object.assign(ControllerClass, {
dataElementType: registry.getElement(dataElementType),
datasetElementType: datasetElementType && registry.getElement(datasetElementType)
});
meta.controller = new ControllerClass(this, i);
newControllers.push(meta.controller);
}
}
this._updateMetasets();
return newControllers;
}
_resetElements() {
each(this.data.datasets, (dataset, datasetIndex)=>{
this.getDatasetMeta(datasetIndex).controller.reset();
}, this);
}
reset() {
this._resetElements();
this.notifyPlugins('reset');
}
update(mode) {
const config = this.config;
config.update();
const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
const animsDisabled = this._animationsDisabled = !options.animation;
this._updateScales();
this._checkEventBindings();
this._updateHiddenIndices();
this._plugins.invalidate();
if (this.notifyPlugins('beforeUpdate', {
mode,
cancelable: true
}) === false) {
return;
}
const newControllers = this.buildOrUpdateControllers();
this.notifyPlugins('beforeElementsUpdate');
let minPadding = 0;
for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
const { controller } = this.getDatasetMeta(i);
const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
controller.buildOrUpdateElements(reset);
minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
}
minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
this._updateLayout(minPadding);
if (!animsDisabled) {
each(newControllers, (controller)=>{
controller.reset();
});
}
this._updateDatasets(mode);
this.notifyPlugins('afterUpdate', {
mode
});
this._layers.sort(compare2Level('z', '_idx'));
const { _active , _lastEvent } = this;
if (_lastEvent) {
this._eventHandler(_lastEvent, true);
} else if (_active.length) {
this._updateHoverStyles(_active, _active, true);
}
this.render();
}
_updateScales() {
each(this.scales, (scale)=>{
layouts.removeBox(this, scale);
});
this.ensureScalesHaveIDs();
this.buildOrUpdateScales();
}
_checkEventBindings() {
const options = this.options;
const existingEvents = new Set(Object.keys(this._listeners));
const newEvents = new Set(options.events);
if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
this.unbindEvents();
this.bindEvents();
}
}
_updateHiddenIndices() {
const { _hiddenIndices } = this;
const changes = this._getUniformDataChanges() || [];
for (const { method , start , count } of changes){
const move = method === '_removeElements' ? -count : count;
moveNumericKeys(_hiddenIndices, start, move);
}
}
_getUniformDataChanges() {
const _dataChanges = this._dataChanges;
if (!_dataChanges || !_dataChanges.length) {
return;
}
this._dataChanges = [];
const datasetCount = this.data.datasets.length;
const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
const changeSet = makeSet(0);
for(let i = 1; i < datasetCount; i++){
if (!setsEqual(changeSet, makeSet(i))) {
return;
}
}
return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
method: a[1],
start: +a[2],
count: +a[3]
}));
}
_updateLayout(minPadding) {
if (this.notifyPlugins('beforeLayout', {
cancelable: true
}) === false) {
return;
}
layouts.update(this, this.width, this.height, minPadding);
const area = this.chartArea;
const noArea = area.width <= 0 || area.height <= 0;
this._layers = [];
each(this.boxes, (box)=>{
if (noArea && box.position === 'chartArea') {
return;
}
if (box.configure) {
box.configure();
}
this._layers.push(...box._layers());
}, this);
this._layers.forEach((item, index)=>{
item._idx = index;
});
this.notifyPlugins('afterLayout');
}
_updateDatasets(mode) {
if (this.notifyPlugins('beforeDatasetsUpdate', {
mode,
cancelable: true
}) === false) {
return;
}
for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
this.getDatasetMeta(i).controller.configure();
}
for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
this._updateDataset(i, isFunction(mode) ? mode({
datasetIndex: i
}) : mode);
}
this.notifyPlugins('afterDatasetsUpdate', {
mode
});
}
_updateDataset(index, mode) {
const meta = this.getDatasetMeta(index);
const args = {
meta,
index,
mode,
cancelable: true
};
if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
return;
}
meta.controller._update(mode);
args.cancelable = false;
this.notifyPlugins('afterDatasetUpdate', args);
}
render() {
if (this.notifyPlugins('beforeRender', {
cancelable: true
}) === false) {
return;
}
if (animator.has(this)) {
if (this.attached && !animator.running(this)) {
animator.start(this);
}
} else {
this.draw();
onAnimationsComplete({
chart: this
});
}
}
draw() {
let i;
if (this._resizeBeforeDraw) {
const { width , height } = this._resizeBeforeDraw;
this._resizeBeforeDraw = null;
this._resize(width, height);
}
this.clear();
if (this.width <= 0 || this.height <= 0) {
return;
}
if (this.notifyPlugins('beforeDraw', {
cancelable: true
}) === false) {
return;
}
const layers = this._layers;
for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
layers[i].draw(this.chartArea);
}
this._drawDatasets();
for(; i < layers.length; ++i){
layers[i].draw(this.chartArea);
}
this.notifyPlugins('afterDraw');
}
_getSortedDatasetMetas(filterVisible) {
const metasets = this._sortedMetasets;
const result = [];
let i, ilen;
for(i = 0, ilen = metasets.length; i < ilen; ++i){
const meta = metasets[i];
if (!filterVisible || meta.visible) {
result.push(meta);
}
}
return result;
}
getSortedVisibleDatasetMetas() {
return this._getSortedDatasetMetas(true);
}
_drawDatasets() {
if (this.notifyPlugins('beforeDatasetsDraw', {
cancelable: true
}) === false) {
return;
}
const metasets = this.getSortedVisibleDatasetMetas();
for(let i = metasets.length - 1; i >= 0; --i){
this._drawDataset(metasets[i]);
}
this.notifyPlugins('afterDatasetsDraw');
}
_drawDataset(meta) {
const ctx = this.ctx;
const args = {
meta,
index: meta.index,
cancelable: true
};
const clip = getDatasetClipArea(this, meta);
if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
return;
}
if (clip) {
clipArea(ctx, clip);
}
meta.controller.draw();
if (clip) {
unclipArea(ctx);
}
args.cancelable = false;
this.notifyPlugins('afterDatasetDraw', args);
}
isPointInArea(point) {
return _isPointInArea(point, this.chartArea, this._minPadding);
}
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
const method = Interaction.modes[mode];
if (typeof method === 'function') {
return method(this, e, options, useFinalPosition);
}
return [];
}
getDatasetMeta(datasetIndex) {
const dataset = this.data.datasets[datasetIndex];
const metasets = this._metasets;
let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
if (!meta) {
meta = {
type: null,
data: [],
dataset: null,
controller: null,
hidden: null,
xAxisID: null,
yAxisID: null,
order: dataset && dataset.order || 0,
index: datasetIndex,
_dataset: dataset,
_parsed: [],
_sorted: false
};
metasets.push(meta);
}
return meta;
}
getContext() {
return this.$context || (this.$context = createContext(null, {
chart: this,
type: 'chart'
}));
}
getVisibleDatasetCount() {
return this.getSortedVisibleDatasetMetas().length;
}
isDatasetVisible(datasetIndex) {
const dataset = this.data.datasets[datasetIndex];
if (!dataset) {
return false;
}
const meta = this.getDatasetMeta(datasetIndex);
return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
}
setDatasetVisibility(datasetIndex, visible) {
const meta = this.getDatasetMeta(datasetIndex);
meta.hidden = !visible;
}
toggleDataVisibility(index) {
this._hiddenIndices[index] = !this._hiddenIndices[index];
}
getDataVisibility(index) {
return !this._hiddenIndices[index];
}
_updateVisibility(datasetIndex, dataIndex, visible) {
const mode = visible ? 'show' : 'hide';
const meta = this.getDatasetMeta(datasetIndex);
const anims = meta.controller._resolveAnimations(undefined, mode);
if (defined(dataIndex)) {
meta.data[dataIndex].hidden = !visible;
this.update();
} else {
this.setDatasetVisibility(datasetIndex, visible);
anims.update(meta, {
visible
});
this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
}
}
hide(datasetIndex, dataIndex) {
this._updateVisibility(datasetIndex, dataIndex, false);
}
show(datasetIndex, dataIndex) {
this._updateVisibility(datasetIndex, dataIndex, true);
}
_destroyDatasetMeta(datasetIndex) {
const meta = this._metasets[datasetIndex];
if (meta && meta.controller) {
meta.controller._destroy();
}
delete this._metasets[datasetIndex];
}
_stop() {
let i, ilen;
this.stop();
animator.remove(this);
for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
this._destroyDatasetMeta(i);
}
}
destroy() {
this.notifyPlugins('beforeDestroy');
const { canvas , ctx } = this;
this._stop();
this.config.clearCache();
if (canvas) {
this.unbindEvents();
clearCanvas(canvas, ctx);
this.platform.releaseContext(ctx);
this.canvas = null;
this.ctx = null;
}
delete instances[this.id];
this.notifyPlugins('afterDestroy');
}
toBase64Image(...args) {
return this.canvas.toDataURL(...args);
}
bindEvents() {
this.bindUserEvents();
if (this.options.responsive) {
this.bindResponsiveEvents();
} else {
this.attached = true;
}
}
bindUserEvents() {
const listeners = this._listeners;
const platform = this.platform;
const _add = (type, listener)=>{
platform.addEventListener(this, type, listener);
listeners[type] = listener;
};
const listener = (e, x, y)=>{
e.offsetX = x;
e.offsetY = y;
this._eventHandler(e);
};
each(this.options.events, (type)=>_add(type, listener));
}
bindResponsiveEvents() {
if (!this._responsiveListeners) {
this._responsiveListeners = {};
}
const listeners = this._responsiveListeners;
const platform = this.platform;
const _add = (type, listener)=>{
platform.addEventListener(this, type, listener);
listeners[type] = listener;
};
const _remove = (type, listener)=>{
if (listeners[type]) {
platform.removeEventListener(this, type, listener);
delete listeners[type];
}
};
const listener = (width, height)=>{
if (this.canvas) {
this.resize(width, height);
}
};
let detached;
const attached = ()=>{
_remove('attach', attached);
this.attached = true;
this.resize();
_add('resize', listener);
_add('detach', detached);
};
detached = ()=>{
this.attached = false;
_remove('resize', listener);
this._stop();
this._resize(0, 0);
_add('attach', attached);
};
if (platform.isAttached(this.canvas)) {
attached();
} else {
detached();
}
}
unbindEvents() {
each(this._listeners, (listener, type)=>{
this.platform.removeEventListener(this, type, listener);
});
this._listeners = {};
each(this._responsiveListeners, (listener, type)=>{
this.platform.removeEventListener(this, type, listener);
});
this._responsiveListeners = undefined;
}
updateHoverStyle(items, mode, enabled) {
const prefix = enabled ? 'set' : 'remove';
let meta, item, i, ilen;
if (mode === 'dataset') {
meta = this.getDatasetMeta(items[0].datasetIndex);
meta.controller['_' + prefix + 'DatasetHoverStyle']();
}
for(i = 0, ilen = items.length; i < ilen; ++i){
item = items[i];
const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
if (controller) {
controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
}
}
}
getActiveElements() {
return this._active || [];
}
setActiveElements(activeElements) {
const lastActive = this._active || [];
const active = activeElements.map(({ datasetIndex , index })=>{
const meta = this.getDatasetMeta(datasetIndex);
if (!meta) {
throw new Error('No dataset found at index ' + datasetIndex);
}
return {
datasetIndex,
element: meta.data[index],
index
};
});
const changed = !_elementsEqual(active, lastActive);
if (changed) {
this._active = active;
this._lastEvent = null;
this._updateHoverStyles(active, lastActive);
}
}
notifyPlugins(hook, args, filter) {
return this._plugins.notify(this, hook, args, filter);
}
isPluginEnabled(pluginId) {
return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
}
_updateHoverStyles(active, lastActive, replay) {
const hoverOptions = this.options.hover;
const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
const deactivated = diff(lastActive, active);
const activated = replay ? active : diff(active, lastActive);
if (deactivated.length) {
this.updateHoverStyle(deactivated, hoverOptions.mode, false);
}
if (activated.length && hoverOptions.mode) {
this.updateHoverStyle(activated, hoverOptions.mode, true);
}
}
_eventHandler(e, replay) {
const args = {
event: e,
replay,
cancelable: true,
inChartArea: this.isPointInArea(e)
};
const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
return;
}
const changed = this._handleEvent(e, replay, args.inChartArea);
args.cancelable = false;
this.notifyPlugins('afterEvent', args, eventFilter);
if (changed || args.changed) {
this.render();
}
return this;
}
_handleEvent(e, replay, inChartArea) {
const { _active: lastActive = [] , options } = this;
const useFinalPosition = replay;
const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
const isClick = _isClickEvent(e);
const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
if (inChartArea) {
this._lastEvent = null;
callback(options.onHover, [
e,
active,
this
], this);
if (isClick) {
callback(options.onClick, [
e,
active,
this
], this);
}
}
const changed = !_elementsEqual(active, lastActive);
if (changed || replay) {
this._active = active;
this._updateHoverStyles(active, lastActive, replay);
}
this._lastEvent = lastEvent;
return changed;
}
_getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
if (e.type === 'mouseout') {
return [];
}
if (!inChartArea) {
return lastActive;
}
const hoverOptions = this.options.hover;
return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
}
}
function invalidatePlugins() {
return each(chart_Chart.instances, (chart)=>chart._plugins.invalidate());
}
function clipSelf(ctx, element, endAngle) {
const { startAngle , x , y , outerRadius , innerRadius , options } = element;
const { borderWidth , borderJoinStyle } = options;
const outerAngleClip = Math.min(borderWidth / outerRadius, _normalizeAngle(startAngle - endAngle));
ctx.beginPath();
ctx.arc(x, y, outerRadius - borderWidth / 2, startAngle + outerAngleClip / 2, endAngle - outerAngleClip / 2);
if (innerRadius > 0) {
const innerAngleClip = Math.min(borderWidth / innerRadius, _normalizeAngle(startAngle - endAngle));
ctx.arc(x, y, innerRadius + borderWidth / 2, endAngle - innerAngleClip / 2, startAngle + innerAngleClip / 2, true);
} else {
const clipWidth = Math.min(borderWidth / 2, outerRadius * _normalizeAngle(startAngle - endAngle));
if (borderJoinStyle === 'round') {
ctx.arc(x, y, clipWidth, endAngle - PI / 2, startAngle + PI / 2, true);
} else if (borderJoinStyle === 'bevel') {
const r = 2 * clipWidth * clipWidth;
const endX = -r * Math.cos(endAngle + PI / 2) + x;
const endY = -r * Math.sin(endAngle + PI / 2) + y;
const startX = r * Math.cos(startAngle + PI / 2) + x;
const startY = r * Math.sin(startAngle + PI / 2) + y;
ctx.lineTo(endX, endY);
ctx.lineTo(startX, startY);
}
}
ctx.closePath();
ctx.moveTo(0, 0);
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.clip('evenodd');
}
function clipArc(ctx, element, endAngle) {
const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
let angleMargin = pixelMargin / outerRadius;
// Draw an inner border by clipping the arc and drawing a double-width border
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
ctx.beginPath();
ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
if (innerRadius > pixelMargin) {
angleMargin = pixelMargin / innerRadius;
ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
} else {
ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
}
ctx.closePath();
ctx.clip();
}
function toRadiusCorners(value) {
return _readValueToProps(value, [
'outerStart',
'outerEnd',
'innerStart',
'innerEnd'
]);
}
/**
* Parse border radius from the provided options
*/ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
const o = toRadiusCorners(arc.options.borderRadius);
const halfThickness = (outerRadius - innerRadius) / 2;
const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
// Outer limits are complicated. We want to compute the available angular distance at
// a radius of outerRadius - borderRadius because for small angular distances, this term limits.
// We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
//
// If the borderRadius is large, that value can become negative.
// This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
// we know that the thickness term will dominate and compute the limits at that point
const computeOuterLimit = (val)=>{
const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
};
return {
outerStart: computeOuterLimit(o.outerStart),
outerEnd: computeOuterLimit(o.outerEnd),
innerStart: _limitValue(o.innerStart, 0, innerLimit),
innerEnd: _limitValue(o.innerEnd, 0, innerLimit)
};
}
/**
* Convert (r, 𝜃) to (x, y)
*/ function rThetaToXY(r, theta, x, y) {
return {
x: x + r * Math.cos(theta),
y: y + r * Math.sin(theta)
};
}
/**
* Path the arc, respecting border radius by separating into left and right halves.
*
* Start End
*
* 1--->a--->2 Outer
* / \
* 8 3
* | |
* | |
* 7 4
* \ /
* 6<---b<---5 Inner
*/ function pathArc(ctx, element, offset, spacing, end, circular) {
const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
let spacingOffset = 0;
const alpha = end - start;
if (spacing) {
// When spacing is present, it is the same for all items
// So we adjust the start and end angle of the arc such that
// the distance is the same as it would be without the spacing
const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
spacingOffset = (alpha - adjustedAngle) / 2;
}
const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
const angleOffset = (alpha - beta) / 2;
const startAngle = start + angleOffset + spacingOffset;
const endAngle = end - angleOffset - spacingOffset;
const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
const outerStartAdjustedRadius = outerRadius - outerStart;
const outerEndAdjustedRadius = outerRadius - outerEnd;
const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
const innerStartAdjustedRadius = innerRadius + innerStart;
const innerEndAdjustedRadius = innerRadius + innerEnd;
const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
ctx.beginPath();
if (circular) {
// The first arc segments from point 1 to point a to point 2
const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
// The corner segment from point 2 to point 3
if (outerEnd > 0) {
const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
}
// The line from point 3 to point 4
const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
ctx.lineTo(p4.x, p4.y);
// The corner segment from point 4 to point 5
if (innerEnd > 0) {
const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
}
// The inner arc from point 5 to point b to point 6
const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
// The corner segment from point 6 to point 7
if (innerStart > 0) {
const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
}
// The line from point 7 to point 8
const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
ctx.lineTo(p8.x, p8.y);
// The corner segment from point 8 to point 1
if (outerStart > 0) {
const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
}
} else {
ctx.moveTo(x, y);
const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
ctx.lineTo(outerStartX, outerStartY);
const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
ctx.lineTo(outerEndX, outerEndY);
}
ctx.closePath();
}
function drawArc(ctx, element, offset, spacing, circular) {
const { fullCircles , startAngle , circumference } = element;
let endAngle = element.endAngle;
if (fullCircles) {
pathArc(ctx, element, offset, spacing, endAngle, circular);
for(let i = 0; i < fullCircles; ++i){
ctx.fill();
}
if (!isNaN(circumference)) {
endAngle = startAngle + (circumference % TAU || TAU);
}
}
pathArc(ctx, element, offset, spacing, endAngle, circular);
ctx.fill();
return endAngle;
}
function drawBorder(ctx, element, offset, spacing, circular) {
const { fullCircles , startAngle , circumference , options } = element;
const { borderWidth , borderJoinStyle , borderDash , borderDashOffset , borderRadius } = options;
const inner = options.borderAlign === 'inner';
if (!borderWidth) {
return;
}
ctx.setLineDash(borderDash || []);
ctx.lineDashOffset = borderDashOffset;
if (inner) {
ctx.lineWidth = borderWidth * 2;
ctx.lineJoin = borderJoinStyle || 'round';
} else {
ctx.lineWidth = borderWidth;
ctx.lineJoin = borderJoinStyle || 'bevel';
}
let endAngle = element.endAngle;
if (fullCircles) {
pathArc(ctx, element, offset, spacing, endAngle, circular);
for(let i = 0; i < fullCircles; ++i){
ctx.stroke();
}
if (!isNaN(circumference)) {
endAngle = startAngle + (circumference % TAU || TAU);
}
}
if (inner) {
clipArc(ctx, element, endAngle);
}
if (options.selfJoin && endAngle - startAngle >= PI && borderRadius === 0 && borderJoinStyle !== 'miter') {
clipSelf(ctx, element, endAngle);
}
if (!fullCircles) {
pathArc(ctx, element, offset, spacing, endAngle, circular);
ctx.stroke();
}
}
class ArcElement extends chart_Element {
static id = 'arc';
static defaults = {
borderAlign: 'center',
borderColor: '#fff',
borderDash: [],
borderDashOffset: 0,
borderJoinStyle: undefined,
borderRadius: 0,
borderWidth: 2,
offset: 0,
spacing: 0,
angle: undefined,
circular: true,
selfJoin: false
};
static defaultRoutes = {
backgroundColor: 'backgroundColor'
};
static descriptors = {
_scriptable: true,
_indexable: (name)=>name !== 'borderDash'
};
circumference;
endAngle;
fullCircles;
innerRadius;
outerRadius;
pixelMargin;
startAngle;
constructor(cfg){
super();
this.options = undefined;
this.circumference = undefined;
this.startAngle = undefined;
this.endAngle = undefined;
this.innerRadius = undefined;
this.outerRadius = undefined;
this.pixelMargin = 0;
this.fullCircles = 0;
if (cfg) {
Object.assign(this, cfg);
}
}
inRange(chartX, chartY, useFinalPosition) {
const point = this.getProps([
'x',
'y'
], useFinalPosition);
const { angle , distance } = getAngleFromPoint(point, {
x: chartX,
y: chartY
});
const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
'startAngle',
'endAngle',
'innerRadius',
'outerRadius',
'circumference'
], useFinalPosition);
const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
const _circumference = valueOrDefault(circumference, endAngle - startAngle);
const nonZeroBetween = _angleBetween(angle, startAngle, endAngle) && startAngle !== endAngle;
const betweenAngles = _circumference >= TAU || nonZeroBetween;
const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
return betweenAngles && withinRadius;
}
getCenterPoint(useFinalPosition) {
const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
'x',
'y',
'startAngle',
'endAngle',
'innerRadius',
'outerRadius'
], useFinalPosition);
const { offset , spacing } = this.options;
const halfAngle = (startAngle + endAngle) / 2;
const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
return {
x: x + Math.cos(halfAngle) * halfRadius,
y: y + Math.sin(halfAngle) * halfRadius
};
}
tooltipPosition(useFinalPosition) {
return this.getCenterPoint(useFinalPosition);
}
draw(ctx) {
const { options , circumference } = this;
const offset = (options.offset || 0) / 4;
const spacing = (options.spacing || 0) / 2;
const circular = options.circular;
this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
return;
}
ctx.save();
const halfAngle = (this.startAngle + this.endAngle) / 2;
ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
const fix = 1 - Math.sin(Math.min(PI, circumference || 0));
const radiusOffset = offset * fix;
ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor;
drawArc(ctx, this, radiusOffset, spacing, circular);
drawBorder(ctx, this, radiusOffset, spacing, circular);
ctx.restore();
}
}
function setStyle(ctx, options, style = options) {
ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
}
function lineTo(ctx, previous, target) {
ctx.lineTo(target.x, target.y);
}
function getLineMethod(options) {
if (options.stepped) {
return _steppedLineTo;
}
if (options.tension || options.cubicInterpolationMode === 'monotone') {
return _bezierCurveTo;
}
return lineTo;
}
function pathVars(points, segment, params = {}) {
const count = points.length;
const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
const { start: segmentStart , end: segmentEnd } = segment;
const start = Math.max(paramsStart, segmentStart);
const end = Math.min(paramsEnd, segmentEnd);
const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
return {
count,
start,
loop: segment.loop,
ilen: end < start && !outside ? count + end - start : end - start
};
}
function pathSegment(ctx, line, segment, params) {
const { points , options } = line;
const { count , start , loop , ilen } = pathVars(points, segment, params);
const lineMethod = getLineMethod(options);
let { move =true , reverse } = params || {};
let i, point, prev;
for(i = 0; i <= ilen; ++i){
point = points[(start + (reverse ? ilen - i : i)) % count];
if (point.skip) {
continue;
} else if (move) {
ctx.moveTo(point.x, point.y);
move = false;
} else {
lineMethod(ctx, prev, point, reverse, options.stepped);
}
prev = point;
}
if (loop) {
point = points[(start + (reverse ? ilen : 0)) % count];
lineMethod(ctx, prev, point, reverse, options.stepped);
}
return !!loop;
}
function fastPathSegment(ctx, line, segment, params) {
const points = line.points;
const { count , start , ilen } = pathVars(points, segment, params);
const { move =true , reverse } = params || {};
let avgX = 0;
let countX = 0;
let i, point, prevX, minY, maxY, lastY;
const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
const drawX = ()=>{
if (minY !== maxY) {
ctx.lineTo(avgX, maxY);
ctx.lineTo(avgX, minY);
ctx.lineTo(avgX, lastY);
}
};
if (move) {
point = points[pointIndex(0)];
ctx.moveTo(point.x, point.y);
}
for(i = 0; i <= ilen; ++i){
point = points[pointIndex(i)];
if (point.skip) {
continue;
}
const x = point.x;
const y = point.y;
const truncX = x | 0;
if (truncX === prevX) {
if (y < minY) {
minY = y;
} else if (y > maxY) {
maxY = y;
}
avgX = (countX * avgX + x) / ++countX;
} else {
drawX();
ctx.lineTo(x, y);
prevX = truncX;
countX = 0;
minY = maxY = y;
}
lastY = y;
}
drawX();
}
function _getSegmentMethod(line) {
const opts = line.options;
const borderDash = opts.borderDash && opts.borderDash.length;
const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
return useFastPath ? fastPathSegment : pathSegment;
}
function _getInterpolationMethod(options) {
if (options.stepped) {
return _steppedInterpolation;
}
if (options.tension || options.cubicInterpolationMode === 'monotone') {
return _bezierInterpolation;
}
return _pointInLine;
}
function strokePathWithCache(ctx, line, start, count) {
let path = line._path;
if (!path) {
path = line._path = new Path2D();
if (line.path(path, start, count)) {
path.closePath();
}
}
setStyle(ctx, line.options);
ctx.stroke(path);
}
function strokePathDirect(ctx, line, start, count) {
const { segments , options } = line;
const segmentMethod = _getSegmentMethod(line);
for (const segment of segments){
setStyle(ctx, options, segment.style);
ctx.beginPath();
if (segmentMethod(ctx, line, segment, {
start,
end: start + count - 1
})) {
ctx.closePath();
}
ctx.stroke();
}
}
const usePath2D = typeof Path2D === 'function';
function draw(ctx, line, start, count) {
if (usePath2D && !line.options.segment) {
strokePathWithCache(ctx, line, start, count);
} else {
strokePathDirect(ctx, line, start, count);
}
}
class LineElement extends chart_Element {
static id = 'line';
static defaults = {
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0,
borderJoinStyle: 'miter',
borderWidth: 3,
capBezierPoints: true,
cubicInterpolationMode: 'default',
fill: false,
spanGaps: false,
stepped: false,
tension: 0
};
static defaultRoutes = {
backgroundColor: 'backgroundColor',
borderColor: 'borderColor'
};
static descriptors = {
_scriptable: true,
_indexable: (name)=>name !== 'borderDash' && name !== 'fill'
};
constructor(cfg){
super();
this.animated = true;
this.options = undefined;
this._chart = undefined;
this._loop = undefined;
this._fullLoop = undefined;
this._path = undefined;
this._points = undefined;
this._segments = undefined;
this._decimated = false;
this._pointsUpdated = false;
this._datasetIndex = undefined;
if (cfg) {
Object.assign(this, cfg);
}
}
updateControlPoints(chartArea, indexAxis) {
const options = this.options;
if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
const loop = options.spanGaps ? this._loop : this._fullLoop;
_updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
this._pointsUpdated = true;
}
}
set points(points) {
this._points = points;
delete this._segments;
delete this._path;
this._pointsUpdated = false;
}
get points() {
return this._points;
}
get segments() {
return this._segments || (this._segments = _computeSegments(this, this.options.segment));
}
first() {
const segments = this.segments;
const points = this.points;
return segments.length && points[segments[0].start];
}
last() {
const segments = this.segments;
const points = this.points;
const count = segments.length;
return count && points[segments[count - 1].end];
}
interpolate(point, property) {
const options = this.options;
const value = point[property];
const points = this.points;
const segments = _boundSegments(this, {
property,
start: value,
end: value
});
if (!segments.length) {
return;
}
const result = [];
const _interpolate = _getInterpolationMethod(options);
let i, ilen;
for(i = 0, ilen = segments.length; i < ilen; ++i){
const { start , end } = segments[i];
const p1 = points[start];
const p2 = points[end];
if (p1 === p2) {
result.push(p1);
continue;
}
const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
const interpolated = _interpolate(p1, p2, t, options.stepped);
interpolated[property] = point[property];
result.push(interpolated);
}
return result.length === 1 ? result[0] : result;
}
pathSegment(ctx, segment, params) {
const segmentMethod = _getSegmentMethod(this);
return segmentMethod(ctx, this, segment, params);
}
path(ctx, start, count) {
const segments = this.segments;
const segmentMethod = _getSegmentMethod(this);
let loop = this._loop;
start = start || 0;
count = count || this.points.length - start;
for (const segment of segments){
loop &= segmentMethod(ctx, this, segment, {
start,
end: start + count - 1
});
}
return !!loop;
}
draw(ctx, chartArea, start, count) {
const options = this.options || {};
const points = this.points || [];
if (points.length && options.borderWidth) {
ctx.save();
draw(ctx, this, start, count);
ctx.restore();
}
if (this.animated) {
this._pointsUpdated = false;
this._path = undefined;
}
}
}
function inRange$1(el, pos, axis, useFinalPosition) {
const options = el.options;
const { [axis]: value } = el.getProps([
axis
], useFinalPosition);
return Math.abs(pos - value) < options.radius + options.hitRadius;
}
class PointElement extends chart_Element {
static id = 'point';
parsed;
skip;
stop;
/**
* @type {any}
*/ static defaults = {
borderWidth: 1,
hitRadius: 1,
hoverBorderWidth: 1,
hoverRadius: 4,
pointStyle: 'circle',
radius: 3,
rotation: 0
};
/**
* @type {any}
*/ static defaultRoutes = {
backgroundColor: 'backgroundColor',
borderColor: 'borderColor'
};
constructor(cfg){
super();
this.options = undefined;
this.parsed = undefined;
this.skip = undefined;
this.stop = undefined;
if (cfg) {
Object.assign(this, cfg);
}
}
inRange(mouseX, mouseY, useFinalPosition) {
const options = this.options;
const { x , y } = this.getProps([
'x',
'y'
], useFinalPosition);
return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
}
inXRange(mouseX, useFinalPosition) {
return inRange$1(this, mouseX, 'x', useFinalPosition);
}
inYRange(mouseY, useFinalPosition) {
return inRange$1(this, mouseY, 'y', useFinalPosition);
}
getCenterPoint(useFinalPosition) {
const { x , y } = this.getProps([
'x',
'y'
], useFinalPosition);
return {
x,
y
};
}
size(options) {
options = options || this.options || {};
let radius = options.radius || 0;
radius = Math.max(radius, radius && options.hoverRadius || 0);
const borderWidth = radius && options.borderWidth || 0;
return (radius + borderWidth) * 2;
}
draw(ctx, area) {
const options = this.options;
if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
return;
}
ctx.strokeStyle = options.borderColor;
ctx.lineWidth = options.borderWidth;
ctx.fillStyle = options.backgroundColor;
drawPoint(ctx, options, this.x, this.y);
}
getRange() {
const options = this.options || {};
// @ts-expect-error Fallbacks should never be hit in practice
return options.radius + options.hitRadius;
}
}
function getBarBounds(bar, useFinalPosition) {
const { x , y , base , width , height } = bar.getProps([
'x',
'y',
'base',
'width',
'height'
], useFinalPosition);
let left, right, top, bottom, half;
if (bar.horizontal) {
half = height / 2;
left = Math.min(x, base);
right = Math.max(x, base);
top = y - half;
bottom = y + half;
} else {
half = width / 2;
left = x - half;
right = x + half;
top = Math.min(y, base);
bottom = Math.max(y, base);
}
return {
left,
top,
right,
bottom
};
}
function skipOrLimit(skip, value, min, max) {
return skip ? 0 : _limitValue(value, min, max);
}
function parseBorderWidth(bar, maxW, maxH) {
const value = bar.options.borderWidth;
const skip = bar.borderSkipped;
const o = toTRBL(value);
return {
t: skipOrLimit(skip.top, o.top, 0, maxH),
r: skipOrLimit(skip.right, o.right, 0, maxW),
b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
l: skipOrLimit(skip.left, o.left, 0, maxW)
};
}
function parseBorderRadius(bar, maxW, maxH) {
const { enableBorderRadius } = bar.getProps([
'enableBorderRadius'
]);
const value = bar.options.borderRadius;
const o = toTRBLCorners(value);
const maxR = Math.min(maxW, maxH);
const skip = bar.borderSkipped;
const enableBorder = enableBorderRadius || isObject(value);
return {
topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
};
}
function boundingRects(bar) {
const bounds = getBarBounds(bar);
const width = bounds.right - bounds.left;
const height = bounds.bottom - bounds.top;
const border = parseBorderWidth(bar, width / 2, height / 2);
const radius = parseBorderRadius(bar, width / 2, height / 2);
return {
outer: {
x: bounds.left,
y: bounds.top,
w: width,
h: height,
radius
},
inner: {
x: bounds.left + border.l,
y: bounds.top + border.t,
w: width - border.l - border.r,
h: height - border.t - border.b,
radius: {
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
}
}
};
}
function inRange(bar, x, y, useFinalPosition) {
const skipX = x === null;
const skipY = y === null;
const skipBoth = skipX && skipY;
const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom));
}
function hasRadius(radius) {
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
}
function addNormalRectPath(ctx, rect) {
ctx.rect(rect.x, rect.y, rect.w, rect.h);
}
function inflateRect(rect, amount, refRect = {}) {
const x = rect.x !== refRect.x ? -amount : 0;
const y = rect.y !== refRect.y ? -amount : 0;
const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
return {
x: rect.x + x,
y: rect.y + y,
w: rect.w + w,
h: rect.h + h,
radius: rect.radius
};
}
class BarElement extends chart_Element {
static id = 'bar';
static defaults = {
borderSkipped: 'start',
borderWidth: 0,
borderRadius: 0,
inflateAmount: 'auto',
pointStyle: undefined
};
static defaultRoutes = {
backgroundColor: 'backgroundColor',
borderColor: 'borderColor'
};
constructor(cfg){
super();
this.options = undefined;
this.horizontal = undefined;
this.base = undefined;
this.width = undefined;
this.height = undefined;
this.inflateAmount = undefined;
if (cfg) {
Object.assign(this, cfg);
}
}
draw(ctx) {
const { inflateAmount , options: { borderColor , backgroundColor } } = this;
const { inner , outer } = boundingRects(this);
const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
ctx.save();
if (outer.w !== inner.w || outer.h !== inner.h) {
ctx.beginPath();
addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
ctx.clip();
addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
ctx.fillStyle = borderColor;
ctx.fill('evenodd');
}
ctx.beginPath();
addRectPath(ctx, inflateRect(inner, inflateAmount));
ctx.fillStyle = backgroundColor;
ctx.fill();
ctx.restore();
}
inRange(mouseX, mouseY, useFinalPosition) {
return inRange(this, mouseX, mouseY, useFinalPosition);
}
inXRange(mouseX, useFinalPosition) {
return inRange(this, mouseX, null, useFinalPosition);
}
inYRange(mouseY, useFinalPosition) {
return inRange(this, null, mouseY, useFinalPosition);
}
getCenterPoint(useFinalPosition) {
const { x , y , base , horizontal } = this.getProps([
'x',
'y',
'base',
'horizontal'
], useFinalPosition);
return {
x: horizontal ? (x + base) / 2 : x,
y: horizontal ? y : (y + base) / 2
};
}
getRange(axis) {
return axis === 'x' ? this.width / 2 : this.height / 2;
}
}
var chart_elements = /*#__PURE__*/Object.freeze({
__proto__: null,
ArcElement: ArcElement,
BarElement: BarElement,
LineElement: LineElement,
PointElement: PointElement
});
const BORDER_COLORS = [
'rgb(54, 162, 235)',
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)' // grey
];
// Border colors with 50% transparency
const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
function getBorderColor(i) {
return BORDER_COLORS[i % BORDER_COLORS.length];
}
function getBackgroundColor(i) {
return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
}
function colorizeDefaultDataset(dataset, i) {
dataset.borderColor = getBorderColor(i);
dataset.backgroundColor = getBackgroundColor(i);
return ++i;
}
function colorizeDoughnutDataset(dataset, i) {
dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
return i;
}
function colorizePolarAreaDataset(dataset, i) {
dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
return i;
}
function getColorizer(chart) {
let i = 0;
return (dataset, datasetIndex)=>{
const controller = chart.getDatasetMeta(datasetIndex).controller;
if (controller instanceof DoughnutController) {
i = colorizeDoughnutDataset(dataset, i);
} else if (controller instanceof PolarAreaController) {
i = colorizePolarAreaDataset(dataset, i);
} else if (controller) {
i = colorizeDefaultDataset(dataset, i);
}
};
}
function containsColorsDefinitions(descriptors) {
let k;
for(k in descriptors){
if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
return true;
}
}
return false;
}
function containsColorsDefinition(descriptor) {
return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
}
function containsDefaultColorsDefenitions() {
return defaults.borderColor !== 'rgba(0,0,0,0.1)' || defaults.backgroundColor !== 'rgba(0,0,0,0.1)';
}
var plugin_colors = {
id: 'colors',
defaults: {
enabled: true,
forceOverride: false
},
beforeLayout (chart, _args, options) {
if (!options.enabled) {
return;
}
const { data: { datasets } , options: chartOptions } = chart.config;
const { elements } = chartOptions;
const containsColorDefenition = containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements) || containsDefaultColorsDefenitions();
if (!options.forceOverride && containsColorDefenition) {
return;
}
const colorizer = getColorizer(chart);
datasets.forEach(colorizer);
}
};
function lttbDecimation(data, start, count, availableWidth, options) {
const samples = options.samples || availableWidth;
if (samples >= count) {
return data.slice(start, start + count);
}
const decimated = [];
const bucketWidth = (count - 2) / (samples - 2);
let sampledIndex = 0;
const endIndex = start + count - 1;
let a = start;
let i, maxAreaPoint, maxArea, area, nextA;
decimated[sampledIndex++] = data[a];
for(i = 0; i < samples - 2; i++){
let avgX = 0;
let avgY = 0;
let j;
const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
const avgRangeLength = avgRangeEnd - avgRangeStart;
for(j = avgRangeStart; j < avgRangeEnd; j++){
avgX += data[j].x;
avgY += data[j].y;
}
avgX /= avgRangeLength;
avgY /= avgRangeLength;
const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
const { x: pointAx , y: pointAy } = data[a];
maxArea = area = -1;
for(j = rangeOffs; j < rangeTo; j++){
area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
if (area > maxArea) {
maxArea = area;
maxAreaPoint = data[j];
nextA = j;
}
}
decimated[sampledIndex++] = maxAreaPoint;
a = nextA;
}
decimated[sampledIndex++] = data[endIndex];
return decimated;
}
function minMaxDecimation(data, start, count, availableWidth) {
let avgX = 0;
let countX = 0;
let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
const decimated = [];
const endIndex = start + count - 1;
const xMin = data[start].x;
const xMax = data[endIndex].x;
const dx = xMax - xMin;
for(i = start; i < start + count; ++i){
point = data[i];
x = (point.x - xMin) / dx * availableWidth;
y = point.y;
const truncX = x | 0;
if (truncX === prevX) {
if (y < minY) {
minY = y;
minIndex = i;
} else if (y > maxY) {
maxY = y;
maxIndex = i;
}
avgX = (countX * avgX + point.x) / ++countX;
} else {
const lastIndex = i - 1;
if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
const intermediateIndex1 = Math.min(minIndex, maxIndex);
const intermediateIndex2 = Math.max(minIndex, maxIndex);
if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
decimated.push({
...data[intermediateIndex1],
x: avgX
});
}
if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
decimated.push({
...data[intermediateIndex2],
x: avgX
});
}
}
if (i > 0 && lastIndex !== startIndex) {
decimated.push(data[lastIndex]);
}
decimated.push(point);
prevX = truncX;
countX = 0;
minY = maxY = y;
minIndex = maxIndex = startIndex = i;
}
}
return decimated;
}
function cleanDecimatedDataset(dataset) {
if (dataset._decimated) {
const data = dataset._data;
delete dataset._decimated;
delete dataset._data;
Object.defineProperty(dataset, 'data', {
configurable: true,
enumerable: true,
writable: true,
value: data
});
}
}
function cleanDecimatedData(chart) {
chart.data.datasets.forEach((dataset)=>{
cleanDecimatedDataset(dataset);
});
}
function getStartAndCountOfVisiblePointsSimplified(meta, points) {
const pointCount = points.length;
let start = 0;
let count;
const { iScale } = meta;
const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
if (minDefined) {
start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
}
if (maxDefined) {
count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
} else {
count = pointCount - start;
}
return {
start,
count
};
}
var plugin_decimation = {
id: 'decimation',
defaults: {
algorithm: 'min-max',
enabled: false
},
beforeElementsUpdate: (chart, args, options)=>{
if (!options.enabled) {
cleanDecimatedData(chart);
return;
}
const availableWidth = chart.width;
chart.data.datasets.forEach((dataset, datasetIndex)=>{
const { _data , indexAxis } = dataset;
const meta = chart.getDatasetMeta(datasetIndex);
const data = _data || dataset.data;
if (resolve([
indexAxis,
chart.options.indexAxis
]) === 'y') {
return;
}
if (!meta.controller.supportsDecimation) {
return;
}
const xAxis = chart.scales[meta.xAxisID];
if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
return;
}
if (chart.options.parsing) {
return;
}
let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
const threshold = options.threshold || 4 * availableWidth;
if (count <= threshold) {
cleanDecimatedDataset(dataset);
return;
}
if (isNullOrUndef(_data)) {
dataset._data = data;
delete dataset.data;
Object.defineProperty(dataset, 'data', {
configurable: true,
enumerable: true,
get: function() {
return this._decimated;
},
set: function(d) {
this._data = d;
}
});
}
let decimated;
switch(options.algorithm){
case 'lttb':
decimated = lttbDecimation(data, start, count, availableWidth, options);
break;
case 'min-max':
decimated = minMaxDecimation(data, start, count, availableWidth);
break;
default:
throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
}
dataset._decimated = decimated;
});
},
destroy (chart) {
cleanDecimatedData(chart);
}
};
function _segments(line, target, property) {
const segments = line.segments;
const points = line.points;
const tpoints = target.points;
const parts = [];
for (const segment of segments){
let { start , end } = segment;
end = _findSegmentEnd(start, end, points);
const bounds = _getBounds(property, points[start], points[end], segment.loop);
if (!target.segments) {
parts.push({
source: segment,
target: bounds,
start: points[start],
end: points[end]
});
continue;
}
const targetSegments = _boundSegments(target, bounds);
for (const tgt of targetSegments){
const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
const fillSources = _boundSegment(segment, points, subBounds);
for (const fillSource of fillSources){
parts.push({
source: fillSource,
target: tgt,
start: {
[property]: _getEdge(bounds, subBounds, 'start', Math.max)
},
end: {
[property]: _getEdge(bounds, subBounds, 'end', Math.min)
}
});
}
}
}
return parts;
}
function _getBounds(property, first, last, loop) {
if (loop) {
return;
}
let start = first[property];
let end = last[property];
if (property === 'angle') {
start = _normalizeAngle(start);
end = _normalizeAngle(end);
}
return {
property,
start,
end
};
}
function _pointsFromSegments(boundary, line) {
const { x =null , y =null } = boundary || {};
const linePoints = line.points;
const points = [];
line.segments.forEach(({ start , end })=>{
end = _findSegmentEnd(start, end, linePoints);
const first = linePoints[start];
const last = linePoints[end];
if (y !== null) {
points.push({
x: first.x,
y
});
points.push({
x: last.x,
y
});
} else if (x !== null) {
points.push({
x,
y: first.y
});
points.push({
x,
y: last.y
});
}
});
return points;
}
function _findSegmentEnd(start, end, points) {
for(; end > start; end--){
const point = points[end];
if (!isNaN(point.x) && !isNaN(point.y)) {
break;
}
}
return end;
}
function _getEdge(a, b, prop, fn) {
if (a && b) {
return fn(a[prop], b[prop]);
}
return a ? a[prop] : b ? b[prop] : 0;
}
function _createBoundaryLine(boundary, line) {
let points = [];
let _loop = false;
if (isArray(boundary)) {
_loop = true;
points = boundary;
} else {
points = _pointsFromSegments(boundary, line);
}
return points.length ? new LineElement({
points,
options: {
tension: 0
},
_loop,
_fullLoop: _loop
}) : null;
}
function _shouldApplyFill(source) {
return source && source.fill !== false;
}
function _resolveTarget(sources, index, propagate) {
const source = sources[index];
let fill = source.fill;
const visited = [
index
];
let target;
if (!propagate) {
return fill;
}
while(fill !== false && visited.indexOf(fill) === -1){
if (!isNumberFinite(fill)) {
return fill;
}
target = sources[fill];
if (!target) {
return false;
}
if (target.visible) {
return fill;
}
visited.push(fill);
fill = target.fill;
}
return false;
}
function _decodeFill(line, index, count) {
const fill = parseFillOption(line);
if (isObject(fill)) {
return isNaN(fill.value) ? false : fill;
}
let target = parseFloat(fill);
if (isNumberFinite(target) && Math.floor(target) === target) {
return decodeTargetIndex(fill[0], index, target, count);
}
return [
'origin',
'start',
'end',
'stack',
'shape'
].indexOf(fill) >= 0 && fill;
}
function decodeTargetIndex(firstCh, index, target, count) {
if (firstCh === '-' || firstCh === '+') {
target = index + target;
}
if (target === index || target < 0 || target >= count) {
return false;
}
return target;
}
function _getTargetPixel(fill, scale) {
let pixel = null;
if (fill === 'start') {
pixel = scale.bottom;
} else if (fill === 'end') {
pixel = scale.top;
} else if (isObject(fill)) {
pixel = scale.getPixelForValue(fill.value);
} else if (scale.getBasePixel) {
pixel = scale.getBasePixel();
}
return pixel;
}
function _getTargetValue(fill, scale, startValue) {
let value;
if (fill === 'start') {
value = startValue;
} else if (fill === 'end') {
value = scale.options.reverse ? scale.min : scale.max;
} else if (isObject(fill)) {
value = fill.value;
} else {
value = scale.getBaseValue();
}
return value;
}
function parseFillOption(line) {
const options = line.options;
const fillOption = options.fill;
let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
if (fill === undefined) {
fill = !!options.backgroundColor;
}
if (fill === false || fill === null) {
return false;
}
if (fill === true) {
return 'origin';
}
return fill;
}
function _buildStackLine(source) {
const { scale , index , line } = source;
const points = [];
const segments = line.segments;
const sourcePoints = line.points;
const linesBelow = getLinesBelow(scale, index);
linesBelow.push(_createBoundaryLine({
x: null,
y: scale.bottom
}, line));
for(let i = 0; i < segments.length; i++){
const segment = segments[i];
for(let j = segment.start; j <= segment.end; j++){
addPointsBelow(points, sourcePoints[j], linesBelow);
}
}
return new LineElement({
points,
options: {}
});
}
function getLinesBelow(scale, index) {
const below = [];
const metas = scale.getMatchingVisibleMetas('line');
for(let i = 0; i < metas.length; i++){
const meta = metas[i];
if (meta.index === index) {
break;
}
if (!meta.hidden) {
below.unshift(meta.dataset);
}
}
return below;
}
function addPointsBelow(points, sourcePoint, linesBelow) {
const postponed = [];
for(let j = 0; j < linesBelow.length; j++){
const line = linesBelow[j];
const { first , last , point } = findPoint(line, sourcePoint, 'x');
if (!point || first && last) {
continue;
}
if (first) {
postponed.unshift(point);
} else {
points.push(point);
if (!last) {
break;
}
}
}
points.push(...postponed);
}
function findPoint(line, sourcePoint, property) {
const point = line.interpolate(sourcePoint, property);
if (!point) {
return {};
}
const pointValue = point[property];
const segments = line.segments;
const linePoints = line.points;
let first = false;
let last = false;
for(let i = 0; i < segments.length; i++){
const segment = segments[i];
const firstValue = linePoints[segment.start][property];
const lastValue = linePoints[segment.end][property];
if (_isBetween(pointValue, firstValue, lastValue)) {
first = pointValue === firstValue;
last = pointValue === lastValue;
break;
}
}
return {
first,
last,
point
};
}
class simpleArc {
constructor(opts){
this.x = opts.x;
this.y = opts.y;
this.radius = opts.radius;
}
pathSegment(ctx, bounds, opts) {
const { x , y , radius } = this;
bounds = bounds || {
start: 0,
end: TAU
};
ctx.arc(x, y, radius, bounds.end, bounds.start, true);
return !opts.bounds;
}
interpolate(point) {
const { x , y , radius } = this;
const angle = point.angle;
return {
x: x + Math.cos(angle) * radius,
y: y + Math.sin(angle) * radius,
angle
};
}
}
function _getTarget(source) {
const { chart , fill , line } = source;
if (isNumberFinite(fill)) {
return getLineByIndex(chart, fill);
}
if (fill === 'stack') {
return _buildStackLine(source);
}
if (fill === 'shape') {
return true;
}
const boundary = computeBoundary(source);
if (boundary instanceof simpleArc) {
return boundary;
}
return _createBoundaryLine(boundary, line);
}
function getLineByIndex(chart, index) {
const meta = chart.getDatasetMeta(index);
const visible = meta && chart.isDatasetVisible(index);
return visible ? meta.dataset : null;
}
function computeBoundary(source) {
const scale = source.scale || {};
if (scale.getPointPositionForValue) {
return computeCircularBoundary(source);
}
return computeLinearBoundary(source);
}
function computeLinearBoundary(source) {
const { scale ={} , fill } = source;
const pixel = _getTargetPixel(fill, scale);
if (isNumberFinite(pixel)) {
const horizontal = scale.isHorizontal();
return {
x: horizontal ? pixel : null,
y: horizontal ? null : pixel
};
}
return null;
}
function computeCircularBoundary(source) {
const { scale , fill } = source;
const options = scale.options;
const length = scale.getLabels().length;
const start = options.reverse ? scale.max : scale.min;
const value = _getTargetValue(fill, scale, start);
const target = [];
if (options.grid.circular) {
const center = scale.getPointPositionForValue(0, start);
return new simpleArc({
x: center.x,
y: center.y,
radius: scale.getDistanceFromCenterForValue(value)
});
}
for(let i = 0; i < length; ++i){
target.push(scale.getPointPositionForValue(i, value));
}
return target;
}
function _drawfill(ctx, source, area) {
const target = _getTarget(source);
const { chart , index , line , scale , axis } = source;
const lineOpts = line.options;
const fillOption = lineOpts.fill;
const color = lineOpts.backgroundColor;
const { above =color , below =color } = fillOption || {};
const meta = chart.getDatasetMeta(index);
const clip = getDatasetClipArea(chart, meta);
if (target && line.points.length) {
clipArea(ctx, area);
doFill(ctx, {
line,
target,
above,
below,
area,
scale,
axis,
clip
});
unclipArea(ctx);
}
}
function doFill(ctx, cfg) {
const { line , target , above , below , area , scale , clip } = cfg;
const property = line._loop ? 'angle' : cfg.axis;
ctx.save();
let fillColor = below;
if (below !== above) {
if (property === 'x') {
clipVertical(ctx, target, area.top);
fill(ctx, {
line,
target,
color: above,
scale,
property,
clip
});
ctx.restore();
ctx.save();
clipVertical(ctx, target, area.bottom);
} else if (property === 'y') {
clipHorizontal(ctx, target, area.left);
fill(ctx, {
line,
target,
color: below,
scale,
property,
clip
});
ctx.restore();
ctx.save();
clipHorizontal(ctx, target, area.right);
fillColor = above;
}
}
fill(ctx, {
line,
target,
color: fillColor,
scale,
property,
clip
});
ctx.restore();
}
function clipVertical(ctx, target, clipY) {
const { segments , points } = target;
let first = true;
let lineLoop = false;
ctx.beginPath();
for (const segment of segments){
const { start , end } = segment;
const firstPoint = points[start];
const lastPoint = points[_findSegmentEnd(start, end, points)];
if (first) {
ctx.moveTo(firstPoint.x, firstPoint.y);
first = false;
} else {
ctx.lineTo(firstPoint.x, clipY);
ctx.lineTo(firstPoint.x, firstPoint.y);
}
lineLoop = !!target.pathSegment(ctx, segment, {
move: lineLoop
});
if (lineLoop) {
ctx.closePath();
} else {
ctx.lineTo(lastPoint.x, clipY);
}
}
ctx.lineTo(target.first().x, clipY);
ctx.closePath();
ctx.clip();
}
function clipHorizontal(ctx, target, clipX) {
const { segments , points } = target;
let first = true;
let lineLoop = false;
ctx.beginPath();
for (const segment of segments){
const { start , end } = segment;
const firstPoint = points[start];
const lastPoint = points[_findSegmentEnd(start, end, points)];
if (first) {
ctx.moveTo(firstPoint.x, firstPoint.y);
first = false;
} else {
ctx.lineTo(clipX, firstPoint.y);
ctx.lineTo(firstPoint.x, firstPoint.y);
}
lineLoop = !!target.pathSegment(ctx, segment, {
move: lineLoop
});
if (lineLoop) {
ctx.closePath();
} else {
ctx.lineTo(clipX, lastPoint.y);
}
}
ctx.lineTo(clipX, target.first().y);
ctx.closePath();
ctx.clip();
}
function fill(ctx, cfg) {
const { line , target , property , color , scale , clip } = cfg;
const segments = _segments(line, target, property);
for (const { source: src , target: tgt , start , end } of segments){
const { style: { backgroundColor =color } = {} } = src;
const notShape = target !== true;
ctx.save();
ctx.fillStyle = backgroundColor;
clipBounds(ctx, scale, clip, notShape && _getBounds(property, start, end));
ctx.beginPath();
const lineLoop = !!line.pathSegment(ctx, src);
let loop;
if (notShape) {
if (lineLoop) {
ctx.closePath();
} else {
interpolatedLineTo(ctx, target, end, property);
}
const targetLoop = !!target.pathSegment(ctx, tgt, {
move: lineLoop,
reverse: true
});
loop = lineLoop && targetLoop;
if (!loop) {
interpolatedLineTo(ctx, target, start, property);
}
}
ctx.closePath();
ctx.fill(loop ? 'evenodd' : 'nonzero');
ctx.restore();
}
}
function clipBounds(ctx, scale, clip, bounds) {
const chartArea = scale.chart.chartArea;
const { property , start , end } = bounds || {};
if (property === 'x' || property === 'y') {
let left, top, right, bottom;
if (property === 'x') {
left = start;
top = chartArea.top;
right = end;
bottom = chartArea.bottom;
} else {
left = chartArea.left;
top = start;
right = chartArea.right;
bottom = end;
}
ctx.beginPath();
if (clip) {
left = Math.max(left, clip.left);
right = Math.min(right, clip.right);
top = Math.max(top, clip.top);
bottom = Math.min(bottom, clip.bottom);
}
ctx.rect(left, top, right - left, bottom - top);
ctx.clip();
}
}
function interpolatedLineTo(ctx, target, point, property) {
const interpolatedPoint = target.interpolate(point, property);
if (interpolatedPoint) {
ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
}
}
var index = {
id: 'filler',
afterDatasetsUpdate (chart, _args, options) {
const count = (chart.data.datasets || []).length;
const sources = [];
let meta, i, line, source;
for(i = 0; i < count; ++i){
meta = chart.getDatasetMeta(i);
line = meta.dataset;
source = null;
if (line && line.options && line instanceof LineElement) {
source = {
visible: chart.isDatasetVisible(i),
index: i,
fill: _decodeFill(line, i, count),
chart,
axis: meta.controller.options.indexAxis,
scale: meta.vScale,
line
};
}
meta.$filler = source;
sources.push(source);
}
for(i = 0; i < count; ++i){
source = sources[i];
if (!source || source.fill === false) {
continue;
}
source.fill = _resolveTarget(sources, i, options.propagate);
}
},
beforeDraw (chart, _args, options) {
const draw = options.drawTime === 'beforeDraw';
const metasets = chart.getSortedVisibleDatasetMetas();
const area = chart.chartArea;
for(let i = metasets.length - 1; i >= 0; --i){
const source = metasets[i].$filler;
if (!source) {
continue;
}
source.line.updateControlPoints(area, source.axis);
if (draw && source.fill) {
_drawfill(chart.ctx, source, area);
}
}
},
beforeDatasetsDraw (chart, _args, options) {
if (options.drawTime !== 'beforeDatasetsDraw') {
return;
}
const metasets = chart.getSortedVisibleDatasetMetas();
for(let i = metasets.length - 1; i >= 0; --i){
const source = metasets[i].$filler;
if (_shouldApplyFill(source)) {
_drawfill(chart.ctx, source, chart.chartArea);
}
}
},
beforeDatasetDraw (chart, args, options) {
const source = args.meta.$filler;
if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
return;
}
_drawfill(chart.ctx, source, chart.chartArea);
},
defaults: {
propagate: true,
drawTime: 'beforeDatasetDraw'
}
};
const getBoxSize = (labelOpts, fontSize)=>{
let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
if (labelOpts.usePointStyle) {
boxHeight = Math.min(boxHeight, fontSize);
boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
}
return {
boxWidth,
boxHeight,
itemHeight: Math.max(fontSize, boxHeight)
};
};
const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
class Legend extends chart_Element {
constructor(config){
super();
this._added = false;
this.legendHitBoxes = [];
this._hoveredItem = null;
this.doughnutMode = false;
this.chart = config.chart;
this.options = config.options;
this.ctx = config.ctx;
this.legendItems = undefined;
this.columnSizes = undefined;
this.lineWidths = undefined;
this.maxHeight = undefined;
this.maxWidth = undefined;
this.top = undefined;
this.bottom = undefined;
this.left = undefined;
this.right = undefined;
this.height = undefined;
this.width = undefined;
this._margins = undefined;
this.position = undefined;
this.weight = undefined;
this.fullSize = undefined;
}
update(maxWidth, maxHeight, margins) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this._margins = margins;
this.setDimensions();
this.buildLabels();
this.fit();
}
setDimensions() {
if (this.isHorizontal()) {
this.width = this.maxWidth;
this.left = this._margins.left;
this.right = this.width;
} else {
this.height = this.maxHeight;
this.top = this._margins.top;
this.bottom = this.height;
}
}
buildLabels() {
const labelOpts = this.options.labels || {};
let legendItems = callback(labelOpts.generateLabels, [
this.chart
], this) || [];
if (labelOpts.filter) {
legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
}
if (labelOpts.sort) {
legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
}
if (this.options.reverse) {
legendItems.reverse();
}
this.legendItems = legendItems;
}
fit() {
const { options , ctx } = this;
if (!options.display) {
this.width = this.height = 0;
return;
}
const labelOpts = options.labels;
const labelFont = toFont(labelOpts.font);
const fontSize = labelFont.size;
const titleHeight = this._computeTitleHeight();
const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
let width, height;
ctx.font = labelFont.string;
if (this.isHorizontal()) {
width = this.maxWidth;
height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
} else {
height = this.maxHeight;
width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
}
this.width = Math.min(width, options.maxWidth || this.maxWidth);
this.height = Math.min(height, options.maxHeight || this.maxHeight);
}
_fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
const { ctx , maxWidth , options: { labels: { padding } } } = this;
const hitboxes = this.legendHitBoxes = [];
const lineWidths = this.lineWidths = [
0
];
const lineHeight = itemHeight + padding;
let totalHeight = titleHeight;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
let row = -1;
let top = -lineHeight;
this.legendItems.forEach((legendItem, i)=>{
const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
totalHeight += lineHeight;
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
top += lineHeight;
row++;
}
hitboxes[i] = {
left: 0,
top,
row,
width: itemWidth,
height: itemHeight
};
lineWidths[lineWidths.length - 1] += itemWidth + padding;
});
return totalHeight;
}
_fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
const { ctx , maxHeight , options: { labels: { padding } } } = this;
const hitboxes = this.legendHitBoxes = [];
const columnSizes = this.columnSizes = [];
const heightLimit = maxHeight - titleHeight;
let totalWidth = padding;
let currentColWidth = 0;
let currentColHeight = 0;
let left = 0;
let col = 0;
this.legendItems.forEach((legendItem, i)=>{
const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
totalWidth += currentColWidth + padding;
columnSizes.push({
width: currentColWidth,
height: currentColHeight
});
left += currentColWidth + padding;
col++;
currentColWidth = currentColHeight = 0;
}
hitboxes[i] = {
left,
top: currentColHeight,
col,
width: itemWidth,
height: itemHeight
};
currentColWidth = Math.max(currentColWidth, itemWidth);
currentColHeight += itemHeight + padding;
});
totalWidth += currentColWidth;
columnSizes.push({
width: currentColWidth,
height: currentColHeight
});
return totalWidth;
}
adjustHitBoxes() {
if (!this.options.display) {
return;
}
const titleHeight = this._computeTitleHeight();
const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
if (this.isHorizontal()) {
let row = 0;
let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
for (const hitbox of hitboxes){
if (row !== hitbox.row) {
row = hitbox.row;
left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
}
hitbox.top += this.top + titleHeight + padding;
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
left += hitbox.width + padding;
}
} else {
let col = 0;
let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
for (const hitbox of hitboxes){
if (hitbox.col !== col) {
col = hitbox.col;
top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
}
hitbox.top = top;
hitbox.left += this.left + padding;
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
top += hitbox.height + padding;
}
}
}
isHorizontal() {
return this.options.position === 'top' || this.options.position === 'bottom';
}
draw() {
if (this.options.display) {
const ctx = this.ctx;
clipArea(ctx, this);
this._draw();
unclipArea(ctx);
}
}
_draw() {
const { options: opts , columnSizes , lineWidths , ctx } = this;
const { align , labels: labelOpts } = opts;
const defaultColor = defaults.color;
const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
const labelFont = toFont(labelOpts.font);
const { padding } = labelOpts;
const fontSize = labelFont.size;
const halfFontSize = fontSize / 2;
let cursor;
this.drawTitle();
ctx.textAlign = rtlHelper.textAlign('left');
ctx.textBaseline = 'middle';
ctx.lineWidth = 0.5;
ctx.font = labelFont.string;
const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
const drawLegendBox = function(x, y, legendItem) {
if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
return;
}
ctx.save();
const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
ctx.lineWidth = lineWidth;
ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
if (labelOpts.usePointStyle) {
const drawOptions = {
radius: boxHeight * Math.SQRT2 / 2,
pointStyle: legendItem.pointStyle,
rotation: legendItem.rotation,
borderWidth: lineWidth
};
const centerX = rtlHelper.xPlus(x, boxWidth / 2);
const centerY = y + halfFontSize;
drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
} else {
const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
const borderRadius = toTRBLCorners(legendItem.borderRadius);
ctx.beginPath();
if (Object.values(borderRadius).some((v)=>v !== 0)) {
addRoundedRectPath(ctx, {
x: xBoxLeft,
y: yBoxTop,
w: boxWidth,
h: boxHeight,
radius: borderRadius
});
} else {
ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
}
ctx.fill();
if (lineWidth !== 0) {
ctx.stroke();
}
}
ctx.restore();
};
const fillText = function(x, y, legendItem) {
renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
strikethrough: legendItem.hidden,
textAlign: rtlHelper.textAlign(legendItem.textAlign)
});
};
const isHorizontal = this.isHorizontal();
const titleHeight = this._computeTitleHeight();
if (isHorizontal) {
cursor = {
x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
y: this.top + padding + titleHeight,
line: 0
};
} else {
cursor = {
x: this.left + padding,
y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
line: 0
};
}
overrideTextDirection(this.ctx, opts.textDirection);
const lineHeight = itemHeight + padding;
this.legendItems.forEach((legendItem, i)=>{
ctx.strokeStyle = legendItem.fontColor;
ctx.fillStyle = legendItem.fontColor;
const textWidth = ctx.measureText(legendItem.text).width;
const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
const width = boxWidth + halfFontSize + textWidth;
let x = cursor.x;
let y = cursor.y;
rtlHelper.setWidth(this.width);
if (isHorizontal) {
if (i > 0 && x + width + padding > this.right) {
y = cursor.y += lineHeight;
cursor.line++;
x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
}
} else if (i > 0 && y + lineHeight > this.bottom) {
x = cursor.x = x + columnSizes[cursor.line].width + padding;
cursor.line++;
y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
}
const realX = rtlHelper.x(x);
drawLegendBox(realX, y, legendItem);
x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
fillText(rtlHelper.x(x), y, legendItem);
if (isHorizontal) {
cursor.x += width + padding;
} else if (typeof legendItem.text !== 'string') {
const fontLineHeight = labelFont.lineHeight;
cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
} else {
cursor.y += lineHeight;
}
});
restoreTextDirection(this.ctx, opts.textDirection);
}
drawTitle() {
const opts = this.options;
const titleOpts = opts.title;
const titleFont = toFont(titleOpts.font);
const titlePadding = toPadding(titleOpts.padding);
if (!titleOpts.display) {
return;
}
const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
const ctx = this.ctx;
const position = titleOpts.position;
const halfFontSize = titleFont.size / 2;
const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
let y;
let left = this.left;
let maxWidth = this.width;
if (this.isHorizontal()) {
maxWidth = Math.max(...this.lineWidths);
y = this.top + topPaddingPlusHalfFontSize;
left = _alignStartEnd(opts.align, left, this.right - maxWidth);
} else {
const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
}
const x = _alignStartEnd(position, left, left + maxWidth);
ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
ctx.textBaseline = 'middle';
ctx.strokeStyle = titleOpts.color;
ctx.fillStyle = titleOpts.color;
ctx.font = titleFont.string;
renderText(ctx, titleOpts.text, x, y, titleFont);
}
_computeTitleHeight() {
const titleOpts = this.options.title;
const titleFont = toFont(titleOpts.font);
const titlePadding = toPadding(titleOpts.padding);
return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
}
_getLegendItemAt(x, y) {
let i, hitBox, lh;
if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) {
lh = this.legendHitBoxes;
for(i = 0; i < lh.length; ++i){
hitBox = lh[i];
if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
return this.legendItems[i];
}
}
}
return null;
}
handleEvent(e) {
const opts = this.options;
if (!isListened(e.type, opts)) {
return;
}
const hoveredItem = this._getLegendItemAt(e.x, e.y);
if (e.type === 'mousemove' || e.type === 'mouseout') {
const previous = this._hoveredItem;
const sameItem = itemsEqual(previous, hoveredItem);
if (previous && !sameItem) {
callback(opts.onLeave, [
e,
previous,
this
], this);
}
this._hoveredItem = hoveredItem;
if (hoveredItem && !sameItem) {
callback(opts.onHover, [
e,
hoveredItem,
this
], this);
}
} else if (hoveredItem) {
callback(opts.onClick, [
e,
hoveredItem,
this
], this);
}
}
}
function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
return {
itemWidth,
itemHeight
};
}
function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
let legendItemText = legendItem.text;
if (legendItemText && typeof legendItemText !== 'string') {
legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
}
return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
}
function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
let itemHeight = _itemHeight;
if (typeof legendItem.text !== 'string') {
itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
}
return itemHeight;
}
function calculateLegendItemHeight(legendItem, fontLineHeight) {
const labelHeight = legendItem.text ? legendItem.text.length : 0;
return fontLineHeight * labelHeight;
}
function isListened(type, opts) {
if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
return true;
}
if (opts.onClick && (type === 'click' || type === 'mouseup')) {
return true;
}
return false;
}
var plugin_legend = {
id: 'legend',
_element: Legend,
start (chart, _args, options) {
const legend = chart.legend = new Legend({
ctx: chart.ctx,
options,
chart
});
layouts.configure(chart, legend, options);
layouts.addBox(chart, legend);
},
stop (chart) {
layouts.removeBox(chart, chart.legend);
delete chart.legend;
},
beforeUpdate (chart, _args, options) {
const legend = chart.legend;
layouts.configure(chart, legend, options);
legend.options = options;
},
afterUpdate (chart) {
const legend = chart.legend;
legend.buildLabels();
legend.adjustHitBoxes();
},
afterEvent (chart, args) {
if (!args.replay) {
chart.legend.handleEvent(args.event);
}
},
defaults: {
display: true,
position: 'top',
align: 'center',
fullSize: true,
reverse: false,
weight: 1000,
onClick (e, legendItem, legend) {
const index = legendItem.datasetIndex;
const ci = legend.chart;
if (ci.isDatasetVisible(index)) {
ci.hide(index);
legendItem.hidden = true;
} else {
ci.show(index);
legendItem.hidden = false;
}
},
onHover: null,
onLeave: null,
labels: {
color: (ctx)=>ctx.chart.options.color,
boxWidth: 40,
padding: 10,
generateLabels (chart) {
const datasets = chart.data.datasets;
const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
return chart._getSortedDatasetMetas().map((meta)=>{
const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
const borderWidth = toPadding(style.borderWidth);
return {
text: datasets[meta.index].label,
fillStyle: style.backgroundColor,
fontColor: color,
hidden: !meta.visible,
lineCap: style.borderCapStyle,
lineDash: style.borderDash,
lineDashOffset: style.borderDashOffset,
lineJoin: style.borderJoinStyle,
lineWidth: (borderWidth.width + borderWidth.height) / 4,
strokeStyle: style.borderColor,
pointStyle: pointStyle || style.pointStyle,
rotation: style.rotation,
textAlign: textAlign || style.textAlign,
borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
datasetIndex: meta.index
};
}, this);
}
},
title: {
color: (ctx)=>ctx.chart.options.color,
display: false,
position: 'center',
text: ''
}
},
descriptors: {
_scriptable: (name)=>!name.startsWith('on'),
labels: {
_scriptable: (name)=>![
'generateLabels',
'filter',
'sort'
].includes(name)
}
}
};
class Title extends chart_Element {
constructor(config){
super();
this.chart = config.chart;
this.options = config.options;
this.ctx = config.ctx;
this._padding = undefined;
this.top = undefined;
this.bottom = undefined;
this.left = undefined;
this.right = undefined;
this.width = undefined;
this.height = undefined;
this.position = undefined;
this.weight = undefined;
this.fullSize = undefined;
}
update(maxWidth, maxHeight) {
const opts = this.options;
this.left = 0;
this.top = 0;
if (!opts.display) {
this.width = this.height = this.right = this.bottom = 0;
return;
}
this.width = this.right = maxWidth;
this.height = this.bottom = maxHeight;
const lineCount = isArray(opts.text) ? opts.text.length : 1;
this._padding = toPadding(opts.padding);
const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
if (this.isHorizontal()) {
this.height = textSize;
} else {
this.width = textSize;
}
}
isHorizontal() {
const pos = this.options.position;
return pos === 'top' || pos === 'bottom';
}
_drawArgs(offset) {
const { top , left , bottom , right , options } = this;
const align = options.align;
let rotation = 0;
let maxWidth, titleX, titleY;
if (this.isHorizontal()) {
titleX = _alignStartEnd(align, left, right);
titleY = top + offset;
maxWidth = right - left;
} else {
if (options.position === 'left') {
titleX = left + offset;
titleY = _alignStartEnd(align, bottom, top);
rotation = PI * -0.5;
} else {
titleX = right - offset;
titleY = _alignStartEnd(align, top, bottom);
rotation = PI * 0.5;
}
maxWidth = bottom - top;
}
return {
titleX,
titleY,
maxWidth,
rotation
};
}
draw() {
const ctx = this.ctx;
const opts = this.options;
if (!opts.display) {
return;
}
const fontOpts = toFont(opts.font);
const lineHeight = fontOpts.lineHeight;
const offset = lineHeight / 2 + this._padding.top;
const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
renderText(ctx, opts.text, 0, 0, fontOpts, {
color: opts.color,
maxWidth,
rotation,
textAlign: _toLeftRightCenter(opts.align),
textBaseline: 'middle',
translation: [
titleX,
titleY
]
});
}
}
function createTitle(chart, titleOpts) {
const title = new Title({
ctx: chart.ctx,
options: titleOpts,
chart
});
layouts.configure(chart, title, titleOpts);
layouts.addBox(chart, title);
chart.titleBlock = title;
}
var plugin_title = {
id: 'title',
_element: Title,
start (chart, _args, options) {
createTitle(chart, options);
},
stop (chart) {
const titleBlock = chart.titleBlock;
layouts.removeBox(chart, titleBlock);
delete chart.titleBlock;
},
beforeUpdate (chart, _args, options) {
const title = chart.titleBlock;
layouts.configure(chart, title, options);
title.options = options;
},
defaults: {
align: 'center',
display: false,
font: {
weight: 'bold'
},
fullSize: true,
padding: 10,
position: 'top',
text: '',
weight: 2000
},
defaultRoutes: {
color: 'color'
},
descriptors: {
_scriptable: true,
_indexable: false
}
};
const chart_map = new WeakMap();
var plugin_subtitle = {
id: 'subtitle',
start (chart, _args, options) {
const title = new Title({
ctx: chart.ctx,
options,
chart
});
layouts.configure(chart, title, options);
layouts.addBox(chart, title);
chart_map.set(chart, title);
},
stop (chart) {
layouts.removeBox(chart, chart_map.get(chart));
chart_map.delete(chart);
},
beforeUpdate (chart, _args, options) {
const title = chart_map.get(chart);
layouts.configure(chart, title, options);
title.options = options;
},
defaults: {
align: 'center',
display: false,
font: {
weight: 'normal'
},
fullSize: true,
padding: 0,
position: 'top',
text: '',
weight: 1500
},
defaultRoutes: {
color: 'color'
},
descriptors: {
_scriptable: true,
_indexable: false
}
};
const positioners = {
average (items) {
if (!items.length) {
return false;
}
let i, len;
let xSet = new Set();
let y = 0;
let count = 0;
for(i = 0, len = items.length; i < len; ++i){
const el = items[i].element;
if (el && el.hasValue()) {
const pos = el.tooltipPosition();
xSet.add(pos.x);
y += pos.y;
++count;
}
}
if (count === 0 || xSet.size === 0) {
return false;
}
const xAverage = [
...xSet
].reduce((a, b)=>a + b) / xSet.size;
return {
x: xAverage,
y: y / count
};
},
nearest (items, eventPosition) {
if (!items.length) {
return false;
}
let x = eventPosition.x;
let y = eventPosition.y;
let minDistance = Number.POSITIVE_INFINITY;
let i, len, nearestElement;
for(i = 0, len = items.length; i < len; ++i){
const el = items[i].element;
if (el && el.hasValue()) {
const center = el.getCenterPoint();
const d = distanceBetweenPoints(eventPosition, center);
if (d < minDistance) {
minDistance = d;
nearestElement = el;
}
}
}
if (nearestElement) {
const tp = nearestElement.tooltipPosition();
x = tp.x;
y = tp.y;
}
return {
x,
y
};
}
};
function pushOrConcat(base, toPush) {
if (toPush) {
if (isArray(toPush)) {
Array.prototype.push.apply(base, toPush);
} else {
base.push(toPush);
}
}
return base;
}
function splitNewlines(str) {
if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
return str.split('\n');
}
return str;
}
function createTooltipItem(chart, item) {
const { element , datasetIndex , index } = item;
const controller = chart.getDatasetMeta(datasetIndex).controller;
const { label , value } = controller.getLabelAndValue(index);
return {
chart,
label,
parsed: controller.getParsed(index),
raw: chart.data.datasets[datasetIndex].data[index],
formattedValue: value,
dataset: controller.getDataset(),
dataIndex: index,
datasetIndex,
element
};
}
function getTooltipSize(tooltip, options) {
const ctx = tooltip.chart.ctx;
const { body , footer , title } = tooltip;
const { boxWidth , boxHeight } = options;
const bodyFont = toFont(options.bodyFont);
const titleFont = toFont(options.titleFont);
const footerFont = toFont(options.footerFont);
const titleLineCount = title.length;
const footerLineCount = footer.length;
const bodyLineItemCount = body.length;
const padding = toPadding(options.padding);
let height = padding.height;
let width = 0;
let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
if (titleLineCount) {
height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
}
if (combinedBodyLength) {
const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
}
if (footerLineCount) {
height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
}
let widthPadding = 0;
const maxLineWidth = function(line) {
width = Math.max(width, ctx.measureText(line).width + widthPadding);
};
ctx.save();
ctx.font = titleFont.string;
each(tooltip.title, maxLineWidth);
ctx.font = bodyFont.string;
each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
each(body, (bodyItem)=>{
each(bodyItem.before, maxLineWidth);
each(bodyItem.lines, maxLineWidth);
each(bodyItem.after, maxLineWidth);
});
widthPadding = 0;
ctx.font = footerFont.string;
each(tooltip.footer, maxLineWidth);
ctx.restore();
width += padding.width;
return {
width,
height
};
}
function determineYAlign(chart, size) {
const { y , height } = size;
if (y < height / 2) {
return 'top';
} else if (y > chart.height - height / 2) {
return 'bottom';
}
return 'center';
}
function doesNotFitWithAlign(xAlign, chart, options, size) {
const { x , width } = size;
const caret = options.caretSize + options.caretPadding;
if (xAlign === 'left' && x + width + caret > chart.width) {
return true;
}
if (xAlign === 'right' && x - width - caret < 0) {
return true;
}
}
function determineXAlign(chart, options, size, yAlign) {
const { x , width } = size;
const { width: chartWidth , chartArea: { left , right } } = chart;
let xAlign = 'center';
if (yAlign === 'center') {
xAlign = x <= (left + right) / 2 ? 'left' : 'right';
} else if (x <= width / 2) {
xAlign = 'left';
} else if (x >= chartWidth - width / 2) {
xAlign = 'right';
}
if (doesNotFitWithAlign(xAlign, chart, options, size)) {
xAlign = 'center';
}
return xAlign;
}
function determineAlignment(chart, options, size) {
const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
return {
xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
yAlign
};
}
function alignX(size, xAlign) {
let { x , width } = size;
if (xAlign === 'right') {
x -= width;
} else if (xAlign === 'center') {
x -= width / 2;
}
return x;
}
function alignY(size, yAlign, paddingAndSize) {
let { y , height } = size;
if (yAlign === 'top') {
y += paddingAndSize;
} else if (yAlign === 'bottom') {
y -= height + paddingAndSize;
} else {
y -= height / 2;
}
return y;
}
function getBackgroundPoint(options, size, alignment, chart) {
const { caretSize , caretPadding , cornerRadius } = options;
const { xAlign , yAlign } = alignment;
const paddingAndSize = caretSize + caretPadding;
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
let x = alignX(size, xAlign);
const y = alignY(size, yAlign, paddingAndSize);
if (yAlign === 'center') {
if (xAlign === 'left') {
x += paddingAndSize;
} else if (xAlign === 'right') {
x -= paddingAndSize;
}
} else if (xAlign === 'left') {
x -= Math.max(topLeft, bottomLeft) + caretSize;
} else if (xAlign === 'right') {
x += Math.max(topRight, bottomRight) + caretSize;
}
return {
x: _limitValue(x, 0, chart.width - size.width),
y: _limitValue(y, 0, chart.height - size.height)
};
}
function getAlignedX(tooltip, align, options) {
const padding = toPadding(options.padding);
return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
}
function getBeforeAfterBodyLines(callback) {
return pushOrConcat([], splitNewlines(callback));
}
function createTooltipContext(parent, tooltip, tooltipItems) {
return createContext(parent, {
tooltip,
tooltipItems,
type: 'tooltip'
});
}
function overrideCallbacks(callbacks, context) {
const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
return override ? callbacks.override(override) : callbacks;
}
const defaultCallbacks = {
beforeTitle: noop,
title (tooltipItems) {
if (tooltipItems.length > 0) {
const item = tooltipItems[0];
const labels = item.chart.data.labels;
const labelCount = labels ? labels.length : 0;
if (this && this.options && this.options.mode === 'dataset') {
return item.dataset.label || '';
} else if (item.label) {
return item.label;
} else if (labelCount > 0 && item.dataIndex < labelCount) {
return labels[item.dataIndex];
}
}
return '';
},
afterTitle: noop,
beforeBody: noop,
beforeLabel: noop,
label (tooltipItem) {
if (this && this.options && this.options.mode === 'dataset') {
return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
}
let label = tooltipItem.dataset.label || '';
if (label) {
label += ': ';
}
const value = tooltipItem.formattedValue;
if (!isNullOrUndef(value)) {
label += value;
}
return label;
},
labelColor (tooltipItem) {
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
const options = meta.controller.getStyle(tooltipItem.dataIndex);
return {
borderColor: options.borderColor,
backgroundColor: options.backgroundColor,
borderWidth: options.borderWidth,
borderDash: options.borderDash,
borderDashOffset: options.borderDashOffset,
borderRadius: 0
};
},
labelTextColor () {
return this.options.bodyColor;
},
labelPointStyle (tooltipItem) {
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
const options = meta.controller.getStyle(tooltipItem.dataIndex);
return {
pointStyle: options.pointStyle,
rotation: options.rotation
};
},
afterLabel: noop,
afterBody: noop,
beforeFooter: noop,
footer: noop,
afterFooter: noop
};
function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
const result = callbacks[name].call(ctx, arg);
if (typeof result === 'undefined') {
return defaultCallbacks[name].call(ctx, arg);
}
return result;
}
class Tooltip extends chart_Element {
static positioners = positioners;
constructor(config){
super();
this.opacity = 0;
this._active = [];
this._eventPosition = undefined;
this._size = undefined;
this._cachedAnimations = undefined;
this._tooltipItems = [];
this.$animations = undefined;
this.$context = undefined;
this.chart = config.chart;
this.options = config.options;
this.dataPoints = undefined;
this.title = undefined;
this.beforeBody = undefined;
this.body = undefined;
this.afterBody = undefined;
this.footer = undefined;
this.xAlign = undefined;
this.yAlign = undefined;
this.x = undefined;
this.y = undefined;
this.height = undefined;
this.width = undefined;
this.caretX = undefined;
this.caretY = undefined;
this.labelColors = undefined;
this.labelPointStyles = undefined;
this.labelTextColors = undefined;
}
initialize(options) {
this.options = options;
this._cachedAnimations = undefined;
this.$context = undefined;
}
_resolveAnimations() {
const cached = this._cachedAnimations;
if (cached) {
return cached;
}
const chart = this.chart;
const options = this.options.setContext(this.getContext());
const opts = options.enabled && chart.options.animation && options.animations;
const animations = new Animations(this.chart, opts);
if (opts._cacheable) {
this._cachedAnimations = Object.freeze(animations);
}
return animations;
}
getContext() {
return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
}
getTitle(context, options) {
const { callbacks } = options;
const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
let lines = [];
lines = pushOrConcat(lines, splitNewlines(beforeTitle));
lines = pushOrConcat(lines, splitNewlines(title));
lines = pushOrConcat(lines, splitNewlines(afterTitle));
return lines;
}
getBeforeBody(tooltipItems, options) {
return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
}
getBody(tooltipItems, options) {
const { callbacks } = options;
const bodyItems = [];
each(tooltipItems, (context)=>{
const bodyItem = {
before: [],
lines: [],
after: []
};
const scoped = overrideCallbacks(callbacks, context);
pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
bodyItems.push(bodyItem);
});
return bodyItems;
}
getAfterBody(tooltipItems, options) {
return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
}
getFooter(tooltipItems, options) {
const { callbacks } = options;
const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
let lines = [];
lines = pushOrConcat(lines, splitNewlines(beforeFooter));
lines = pushOrConcat(lines, splitNewlines(footer));
lines = pushOrConcat(lines, splitNewlines(afterFooter));
return lines;
}
_createItems(options) {
const active = this._active;
const data = this.chart.data;
const labelColors = [];
const labelPointStyles = [];
const labelTextColors = [];
let tooltipItems = [];
let i, len;
for(i = 0, len = active.length; i < len; ++i){
tooltipItems.push(createTooltipItem(this.chart, active[i]));
}
if (options.filter) {
tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
}
if (options.itemSort) {
tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
}
each(tooltipItems, (context)=>{
const scoped = overrideCallbacks(options.callbacks, context);
labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
});
this.labelColors = labelColors;
this.labelPointStyles = labelPointStyles;
this.labelTextColors = labelTextColors;
this.dataPoints = tooltipItems;
return tooltipItems;
}
update(changed, replay) {
const options = this.options.setContext(this.getContext());
const active = this._active;
let properties;
let tooltipItems = [];
if (!active.length) {
if (this.opacity !== 0) {
properties = {
opacity: 0
};
}
} else {
const position = positioners[options.position].call(this, active, this._eventPosition);
tooltipItems = this._createItems(options);
this.title = this.getTitle(tooltipItems, options);
this.beforeBody = this.getBeforeBody(tooltipItems, options);
this.body = this.getBody(tooltipItems, options);
this.afterBody = this.getAfterBody(tooltipItems, options);
this.footer = this.getFooter(tooltipItems, options);
const size = this._size = getTooltipSize(this, options);
const positionAndSize = Object.assign({}, position, size);
const alignment = determineAlignment(this.chart, options, positionAndSize);
const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
this.xAlign = alignment.xAlign;
this.yAlign = alignment.yAlign;
properties = {
opacity: 1,
x: backgroundPoint.x,
y: backgroundPoint.y,
width: size.width,
height: size.height,
caretX: position.x,
caretY: position.y
};
}
this._tooltipItems = tooltipItems;
this.$context = undefined;
if (properties) {
this._resolveAnimations().update(this, properties);
}
if (changed && options.external) {
options.external.call(this, {
chart: this.chart,
tooltip: this,
replay
});
}
}
drawCaret(tooltipPoint, ctx, size, options) {
const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
ctx.lineTo(caretPosition.x1, caretPosition.y1);
ctx.lineTo(caretPosition.x2, caretPosition.y2);
ctx.lineTo(caretPosition.x3, caretPosition.y3);
}
getCaretPosition(tooltipPoint, size, options) {
const { xAlign , yAlign } = this;
const { caretSize , cornerRadius } = options;
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
const { x: ptX , y: ptY } = tooltipPoint;
const { width , height } = size;
let x1, x2, x3, y1, y2, y3;
if (yAlign === 'center') {
y2 = ptY + height / 2;
if (xAlign === 'left') {
x1 = ptX;
x2 = x1 - caretSize;
y1 = y2 + caretSize;
y3 = y2 - caretSize;
} else {
x1 = ptX + width;
x2 = x1 + caretSize;
y1 = y2 - caretSize;
y3 = y2 + caretSize;
}
x3 = x1;
} else {
if (xAlign === 'left') {
x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
} else if (xAlign === 'right') {
x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
} else {
x2 = this.caretX;
}
if (yAlign === 'top') {
y1 = ptY;
y2 = y1 - caretSize;
x1 = x2 - caretSize;
x3 = x2 + caretSize;
} else {
y1 = ptY + height;
y2 = y1 + caretSize;
x1 = x2 + caretSize;
x3 = x2 - caretSize;
}
y3 = y1;
}
return {
x1,
x2,
x3,
y1,
y2,
y3
};
}
drawTitle(pt, ctx, options) {
const title = this.title;
const length = title.length;
let titleFont, titleSpacing, i;
if (length) {
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
pt.x = getAlignedX(this, options.titleAlign, options);
ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
ctx.textBaseline = 'middle';
titleFont = toFont(options.titleFont);
titleSpacing = options.titleSpacing;
ctx.fillStyle = options.titleColor;
ctx.font = titleFont.string;
for(i = 0; i < length; ++i){
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
pt.y += titleFont.lineHeight + titleSpacing;
if (i + 1 === length) {
pt.y += options.titleMarginBottom - titleSpacing;
}
}
}
}
_drawColorBox(ctx, pt, i, rtlHelper, options) {
const labelColor = this.labelColors[i];
const labelPointStyle = this.labelPointStyles[i];
const { boxHeight , boxWidth } = options;
const bodyFont = toFont(options.bodyFont);
const colorX = getAlignedX(this, 'left', options);
const rtlColorX = rtlHelper.x(colorX);
const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
const colorY = pt.y + yOffSet;
if (options.usePointStyle) {
const drawOptions = {
radius: Math.min(boxWidth, boxHeight) / 2,
pointStyle: labelPointStyle.pointStyle,
rotation: labelPointStyle.rotation,
borderWidth: 1
};
const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
const centerY = colorY + boxHeight / 2;
ctx.strokeStyle = options.multiKeyBackground;
ctx.fillStyle = options.multiKeyBackground;
drawPoint(ctx, drawOptions, centerX, centerY);
ctx.strokeStyle = labelColor.borderColor;
ctx.fillStyle = labelColor.backgroundColor;
drawPoint(ctx, drawOptions, centerX, centerY);
} else {
ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1;
ctx.strokeStyle = labelColor.borderColor;
ctx.setLineDash(labelColor.borderDash || []);
ctx.lineDashOffset = labelColor.borderDashOffset || 0;
const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
const borderRadius = toTRBLCorners(labelColor.borderRadius);
if (Object.values(borderRadius).some((v)=>v !== 0)) {
ctx.beginPath();
ctx.fillStyle = options.multiKeyBackground;
addRoundedRectPath(ctx, {
x: outerX,
y: colorY,
w: boxWidth,
h: boxHeight,
radius: borderRadius
});
ctx.fill();
ctx.stroke();
ctx.fillStyle = labelColor.backgroundColor;
ctx.beginPath();
addRoundedRectPath(ctx, {
x: innerX,
y: colorY + 1,
w: boxWidth - 2,
h: boxHeight - 2,
radius: borderRadius
});
ctx.fill();
} else {
ctx.fillStyle = options.multiKeyBackground;
ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
ctx.fillStyle = labelColor.backgroundColor;
ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
}
}
ctx.fillStyle = this.labelTextColors[i];
}
drawBody(pt, ctx, options) {
const { body } = this;
const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
const bodyFont = toFont(options.bodyFont);
let bodyLineHeight = bodyFont.lineHeight;
let xLinePadding = 0;
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
const fillLineOfText = function(line) {
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
pt.y += bodyLineHeight + bodySpacing;
};
const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
let bodyItem, textColor, lines, i, j, ilen, jlen;
ctx.textAlign = bodyAlign;
ctx.textBaseline = 'middle';
ctx.font = bodyFont.string;
pt.x = getAlignedX(this, bodyAlignForCalculation, options);
ctx.fillStyle = options.bodyColor;
each(this.beforeBody, fillLineOfText);
xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
for(i = 0, ilen = body.length; i < ilen; ++i){
bodyItem = body[i];
textColor = this.labelTextColors[i];
ctx.fillStyle = textColor;
each(bodyItem.before, fillLineOfText);
lines = bodyItem.lines;
if (displayColors && lines.length) {
this._drawColorBox(ctx, pt, i, rtlHelper, options);
bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
}
for(j = 0, jlen = lines.length; j < jlen; ++j){
fillLineOfText(lines[j]);
bodyLineHeight = bodyFont.lineHeight;
}
each(bodyItem.after, fillLineOfText);
}
xLinePadding = 0;
bodyLineHeight = bodyFont.lineHeight;
each(this.afterBody, fillLineOfText);
pt.y -= bodySpacing;
}
drawFooter(pt, ctx, options) {
const footer = this.footer;
const length = footer.length;
let footerFont, i;
if (length) {
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
pt.x = getAlignedX(this, options.footerAlign, options);
pt.y += options.footerMarginTop;
ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
ctx.textBaseline = 'middle';
footerFont = toFont(options.footerFont);
ctx.fillStyle = options.footerColor;
ctx.font = footerFont.string;
for(i = 0; i < length; ++i){
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
pt.y += footerFont.lineHeight + options.footerSpacing;
}
}
}
drawBackground(pt, ctx, tooltipSize, options) {
const { xAlign , yAlign } = this;
const { x , y } = pt;
const { width , height } = tooltipSize;
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(options.cornerRadius);
ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor;
ctx.lineWidth = options.borderWidth;
ctx.beginPath();
ctx.moveTo(x + topLeft, y);
if (yAlign === 'top') {
this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + width - topRight, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
if (yAlign === 'center' && xAlign === 'right') {
this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + width, y + height - bottomRight);
ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
if (yAlign === 'bottom') {
this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x + bottomLeft, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
if (yAlign === 'center' && xAlign === 'left') {
this.drawCaret(pt, ctx, tooltipSize, options);
}
ctx.lineTo(x, y + topLeft);
ctx.quadraticCurveTo(x, y, x + topLeft, y);
ctx.closePath();
ctx.fill();
if (options.borderWidth > 0) {
ctx.stroke();
}
}
_updateAnimationTarget(options) {
const chart = this.chart;
const anims = this.$animations;
const animX = anims && anims.x;
const animY = anims && anims.y;
if (animX || animY) {
const position = positioners[options.position].call(this, this._active, this._eventPosition);
if (!position) {
return;
}
const size = this._size = getTooltipSize(this, options);
const positionAndSize = Object.assign({}, position, this._size);
const alignment = determineAlignment(chart, options, positionAndSize);
const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
if (animX._to !== point.x || animY._to !== point.y) {
this.xAlign = alignment.xAlign;
this.yAlign = alignment.yAlign;
this.width = size.width;
this.height = size.height;
this.caretX = position.x;
this.caretY = position.y;
this._resolveAnimations().update(this, point);
}
}
}
_willRender() {
return !!this.opacity;
}
draw(ctx) {
const options = this.options.setContext(this.getContext());
let opacity = this.opacity;
if (!opacity) {
return;
}
this._updateAnimationTarget(options);
const tooltipSize = {
width: this.width,
height: this.height
};
const pt = {
x: this.x,
y: this.y
};
opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
const padding = toPadding(options.padding);
const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
if (options.enabled && hasTooltipContent) {
ctx.save();
ctx.globalAlpha = opacity;
this.drawBackground(pt, ctx, tooltipSize, options);
overrideTextDirection(ctx, options.textDirection);
pt.y += padding.top;
this.drawTitle(pt, ctx, options);
this.drawBody(pt, ctx, options);
this.drawFooter(pt, ctx, options);
restoreTextDirection(ctx, options.textDirection);
ctx.restore();
}
}
getActiveElements() {
return this._active || [];
}
setActiveElements(activeElements, eventPosition) {
const lastActive = this._active;
const active = activeElements.map(({ datasetIndex , index })=>{
const meta = this.chart.getDatasetMeta(datasetIndex);
if (!meta) {
throw new Error('Cannot find a dataset at index ' + datasetIndex);
}
return {
datasetIndex,
element: meta.data[index],
index
};
});
const changed = !_elementsEqual(lastActive, active);
const positionChanged = this._positionChanged(active, eventPosition);
if (changed || positionChanged) {
this._active = active;
this._eventPosition = eventPosition;
this._ignoreReplayEvents = true;
this.update(true);
}
}
handleEvent(e, replay, inChartArea = true) {
if (replay && this._ignoreReplayEvents) {
return false;
}
this._ignoreReplayEvents = false;
const options = this.options;
const lastActive = this._active || [];
const active = this._getActiveElements(e, lastActive, replay, inChartArea);
const positionChanged = this._positionChanged(active, e);
const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
if (changed) {
this._active = active;
if (options.enabled || options.external) {
this._eventPosition = {
x: e.x,
y: e.y
};
this.update(true, replay);
}
}
return changed;
}
_getActiveElements(e, lastActive, replay, inChartArea) {
const options = this.options;
if (e.type === 'mouseout') {
return [];
}
if (!inChartArea) {
return lastActive.filter((i)=>this.chart.data.datasets[i.datasetIndex] && this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined);
}
const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
if (options.reverse) {
active.reverse();
}
return active;
}
_positionChanged(active, e) {
const { caretX , caretY , options } = this;
const position = positioners[options.position].call(this, active, e);
return position !== false && (caretX !== position.x || caretY !== position.y);
}
}
var plugin_tooltip = {
id: 'tooltip',
_element: Tooltip,
positioners,
afterInit (chart, _args, options) {
if (options) {
chart.tooltip = new Tooltip({
chart,
options
});
}
},
beforeUpdate (chart, _args, options) {
if (chart.tooltip) {
chart.tooltip.initialize(options);
}
},
reset (chart, _args, options) {
if (chart.tooltip) {
chart.tooltip.initialize(options);
}
},
afterDraw (chart) {
const tooltip = chart.tooltip;
if (tooltip && tooltip._willRender()) {
const args = {
tooltip
};
if (chart.notifyPlugins('beforeTooltipDraw', {
...args,
cancelable: true
}) === false) {
return;
}
tooltip.draw(chart.ctx);
chart.notifyPlugins('afterTooltipDraw', args);
}
},
afterEvent (chart, args) {
if (chart.tooltip) {
const useFinalPosition = args.replay;
if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
args.changed = true;
}
}
},
defaults: {
enabled: true,
external: null,
position: 'average',
backgroundColor: 'rgba(0,0,0,0.8)',
titleColor: '#fff',
titleFont: {
weight: 'bold'
},
titleSpacing: 2,
titleMarginBottom: 6,
titleAlign: 'left',
bodyColor: '#fff',
bodySpacing: 2,
bodyFont: {},
bodyAlign: 'left',
footerColor: '#fff',
footerSpacing: 2,
footerMarginTop: 6,
footerFont: {
weight: 'bold'
},
footerAlign: 'left',
padding: 6,
caretPadding: 2,
caretSize: 5,
cornerRadius: 6,
boxHeight: (ctx, opts)=>opts.bodyFont.size,
boxWidth: (ctx, opts)=>opts.bodyFont.size,
multiKeyBackground: '#fff',
displayColors: true,
boxPadding: 0,
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
animation: {
duration: 400,
easing: 'easeOutQuart'
},
animations: {
numbers: {
type: 'number',
properties: [
'x',
'y',
'width',
'height',
'caretX',
'caretY'
]
},
opacity: {
easing: 'linear',
duration: 200
}
},
callbacks: defaultCallbacks
},
defaultRoutes: {
bodyFont: 'font',
footerFont: 'font',
titleFont: 'font'
},
descriptors: {
_scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
_indexable: false,
callbacks: {
_scriptable: false,
_indexable: false
},
animation: {
_fallback: false
},
animations: {
_fallback: 'animation'
}
},
additionalOptionScopes: [
'interaction'
]
};
var plugins = /*#__PURE__*/Object.freeze({
__proto__: null,
Colors: plugin_colors,
Decimation: plugin_decimation,
Filler: index,
Legend: plugin_legend,
SubTitle: plugin_subtitle,
Title: plugin_title,
Tooltip: plugin_tooltip
});
const addIfString = (labels, raw, index, addedLabels)=>{
if (typeof raw === 'string') {
index = labels.push(raw) - 1;
addedLabels.unshift({
index,
label: raw
});
} else if (isNaN(raw)) {
index = null;
}
return index;
};
function findOrAddLabel(labels, raw, index, addedLabels) {
const first = labels.indexOf(raw);
if (first === -1) {
return addIfString(labels, raw, index, addedLabels);
}
const last = labels.lastIndexOf(raw);
return first !== last ? index : first;
}
const validIndex = (index, max)=>index === null ? null : _limitValue(Math.round(index), 0, max);
function _getLabelForValue(value) {
const labels = this.getLabels();
if (value >= 0 && value < labels.length) {
return labels[value];
}
return value;
}
class CategoryScale extends Scale {
static id = 'category';
static defaults = {
ticks: {
callback: _getLabelForValue
}
};
constructor(cfg){
super(cfg);
this._startValue = undefined;
this._valueRange = 0;
this._addedLabels = [];
}
init(scaleOptions) {
const added = this._addedLabels;
if (added.length) {
const labels = this.getLabels();
for (const { index , label } of added){
if (labels[index] === label) {
labels.splice(index, 1);
}
}
this._addedLabels = [];
}
super.init(scaleOptions);
}
parse(raw, index) {
if (isNullOrUndef(raw)) {
return null;
}
const labels = this.getLabels();
index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
return validIndex(index, labels.length - 1);
}
determineDataLimits() {
const { minDefined , maxDefined } = this.getUserBounds();
let { min , max } = this.getMinMax(true);
if (this.options.bounds === 'ticks') {
if (!minDefined) {
min = 0;
}
if (!maxDefined) {
max = this.getLabels().length - 1;
}
}
this.min = min;
this.max = max;
}
buildTicks() {
const min = this.min;
const max = this.max;
const offset = this.options.offset;
const ticks = [];
let labels = this.getLabels();
labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
this._startValue = this.min - (offset ? 0.5 : 0);
for(let value = min; value <= max; value++){
ticks.push({
value
});
}
return ticks;
}
getLabelForValue(value) {
return _getLabelForValue.call(this, value);
}
configure() {
super.configure();
if (!this.isHorizontal()) {
this._reversePixels = !this._reversePixels;
}
}
getPixelForValue(value) {
if (typeof value !== 'number') {
value = this.parse(value);
}
return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
}
getPixelForTick(index) {
const ticks = this.ticks;
if (index < 0 || index > ticks.length - 1) {
return null;
}
return this.getPixelForValue(ticks[index].value);
}
getValueForPixel(pixel) {
return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
}
getBasePixel() {
return this.bottom;
}
}
function generateTicks$1(generationOptions, dataRange) {
const ticks = [];
const MIN_SPACING = 1e-14;
const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
const unit = step || 1;
const maxSpaces = maxTicks - 1;
const { min: rmin , max: rmax } = dataRange;
const minDefined = !isNullOrUndef(min);
const maxDefined = !isNullOrUndef(max);
const countDefined = !isNullOrUndef(count);
const minSpacing = (rmax - rmin) / (maxDigits + 1);
let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
let factor, niceMin, niceMax, numSpaces;
if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
return [
{
value: rmin
},
{
value: rmax
}
];
}
numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
if (numSpaces > maxSpaces) {
spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
}
if (!isNullOrUndef(precision)) {
factor = Math.pow(10, precision);
spacing = Math.ceil(spacing * factor) / factor;
}
if (bounds === 'ticks') {
niceMin = Math.floor(rmin / spacing) * spacing;
niceMax = Math.ceil(rmax / spacing) * spacing;
} else {
niceMin = rmin;
niceMax = rmax;
}
if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
spacing = (max - min) / numSpaces;
niceMin = min;
niceMax = max;
} else if (countDefined) {
niceMin = minDefined ? min : niceMin;
niceMax = maxDefined ? max : niceMax;
numSpaces = count - 1;
spacing = (niceMax - niceMin) / numSpaces;
} else {
numSpaces = (niceMax - niceMin) / spacing;
if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
}
const decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin));
factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
niceMin = Math.round(niceMin * factor) / factor;
niceMax = Math.round(niceMax * factor) / factor;
let j = 0;
if (minDefined) {
if (includeBounds && niceMin !== min) {
ticks.push({
value: min
});
if (niceMin < min) {
j++;
}
if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
j++;
}
} else if (niceMin < min) {
j++;
}
}
for(; j < numSpaces; ++j){
const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
if (maxDefined && tickValue > max) {
break;
}
ticks.push({
value: tickValue
});
}
if (maxDefined && includeBounds && niceMax !== max) {
if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
ticks[ticks.length - 1].value = max;
} else {
ticks.push({
value: max
});
}
} else if (!maxDefined || niceMax === max) {
ticks.push({
value: niceMax
});
}
return ticks;
}
function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
const rad = toRadians(minRotation);
const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
const length = 0.75 * minSpacing * ('' + value).length;
return Math.min(minSpacing / ratio, length);
}
class LinearScaleBase extends Scale {
constructor(cfg){
super(cfg);
this.start = undefined;
this.end = undefined;
this._startValue = undefined;
this._endValue = undefined;
this._valueRange = 0;
}
parse(raw, index) {
if (isNullOrUndef(raw)) {
return null;
}
if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
return null;
}
return +raw;
}
handleTickRangeOptions() {
const { beginAtZero } = this.options;
const { minDefined , maxDefined } = this.getUserBounds();
let { min , max } = this;
const setMin = (v)=>min = minDefined ? min : v;
const setMax = (v)=>max = maxDefined ? max : v;
if (beginAtZero) {
const minSign = sign(min);
const maxSign = sign(max);
if (minSign < 0 && maxSign < 0) {
setMax(0);
} else if (minSign > 0 && maxSign > 0) {
setMin(0);
}
}
if (min === max) {
let offset = max === 0 ? 1 : Math.abs(max * 0.05);
setMax(max + offset);
if (!beginAtZero) {
setMin(min - offset);
}
}
this.min = min;
this.max = max;
}
getTickLimit() {
const tickOpts = this.options.ticks;
let { maxTicksLimit , stepSize } = tickOpts;
let maxTicks;
if (stepSize) {
maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
if (maxTicks > 1000) {
console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
maxTicks = 1000;
}
} else {
maxTicks = this.computeTickLimit();
maxTicksLimit = maxTicksLimit || 11;
}
if (maxTicksLimit) {
maxTicks = Math.min(maxTicksLimit, maxTicks);
}
return maxTicks;
}
computeTickLimit() {
return Number.POSITIVE_INFINITY;
}
buildTicks() {
const opts = this.options;
const tickOpts = opts.ticks;
let maxTicks = this.getTickLimit();
maxTicks = Math.max(2, maxTicks);
const numericGeneratorOptions = {
maxTicks,
bounds: opts.bounds,
min: opts.min,
max: opts.max,
precision: tickOpts.precision,
step: tickOpts.stepSize,
count: tickOpts.count,
maxDigits: this._maxDigits(),
horizontal: this.isHorizontal(),
minRotation: tickOpts.minRotation || 0,
includeBounds: tickOpts.includeBounds !== false
};
const dataRange = this._range || this;
const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
if (opts.bounds === 'ticks') {
_setMinAndMaxByKey(ticks, this, 'value');
}
if (opts.reverse) {
ticks.reverse();
this.start = this.max;
this.end = this.min;
} else {
this.start = this.min;
this.end = this.max;
}
return ticks;
}
configure() {
const ticks = this.ticks;
let start = this.min;
let end = this.max;
super.configure();
if (this.options.offset && ticks.length) {
const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
start -= offset;
end += offset;
}
this._startValue = start;
this._endValue = end;
this._valueRange = end - start;
}
getLabelForValue(value) {
return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
}
}
class LinearScale extends LinearScaleBase {
static id = 'linear';
static defaults = {
ticks: {
callback: Ticks.formatters.numeric
}
};
determineDataLimits() {
const { min , max } = this.getMinMax(true);
this.min = isNumberFinite(min) ? min : 0;
this.max = isNumberFinite(max) ? max : 1;
this.handleTickRangeOptions();
}
computeTickLimit() {
const horizontal = this.isHorizontal();
const length = horizontal ? this.width : this.height;
const minRotation = toRadians(this.options.ticks.minRotation);
const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
const tickFont = this._resolveTickFontOptions(0);
return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
}
getPixelForValue(value) {
return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
}
getValueForPixel(pixel) {
return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
}
}
const log10Floor = (v)=>Math.floor(log10(v));
const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
function isMajor(tickVal) {
const remain = tickVal / Math.pow(10, log10Floor(tickVal));
return remain === 1;
}
function steps(min, max, rangeExp) {
const rangeStep = Math.pow(10, rangeExp);
const start = Math.floor(min / rangeStep);
const end = Math.ceil(max / rangeStep);
return end - start;
}
function startExp(min, max) {
const range = max - min;
let rangeExp = log10Floor(range);
while(steps(min, max, rangeExp) > 10){
rangeExp++;
}
while(steps(min, max, rangeExp) < 10){
rangeExp--;
}
return Math.min(rangeExp, log10Floor(min));
}
function generateTicks(generationOptions, { min , max }) {
min = finiteOrDefault(generationOptions.min, min);
const ticks = [];
const minExp = log10Floor(min);
let exp = startExp(min, max);
let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
const stepSize = Math.pow(10, exp);
const base = minExp > exp ? Math.pow(10, minExp) : 0;
const start = Math.round((min - base) * precision) / precision;
const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
let significand = Math.floor((start - offset) / Math.pow(10, exp));
let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
while(value < max){
ticks.push({
value,
major: isMajor(value),
significand
});
if (significand >= 10) {
significand = significand < 15 ? 15 : 20;
} else {
significand++;
}
if (significand >= 20) {
exp++;
significand = 2;
precision = exp >= 0 ? 1 : precision;
}
value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
}
const lastTick = finiteOrDefault(generationOptions.max, value);
ticks.push({
value: lastTick,
major: isMajor(lastTick),
significand
});
return ticks;
}
class LogarithmicScale extends Scale {
static id = 'logarithmic';
static defaults = {
ticks: {
callback: Ticks.formatters.logarithmic,
major: {
enabled: true
}
}
};
constructor(cfg){
super(cfg);
this.start = undefined;
this.end = undefined;
this._startValue = undefined;
this._valueRange = 0;
}
parse(raw, index) {
const value = LinearScaleBase.prototype.parse.apply(this, [
raw,
index
]);
if (value === 0) {
this._zero = true;
return undefined;
}
return isNumberFinite(value) && value > 0 ? value : null;
}
determineDataLimits() {
const { min , max } = this.getMinMax(true);
this.min = isNumberFinite(min) ? Math.max(0, min) : null;
this.max = isNumberFinite(max) ? Math.max(0, max) : null;
if (this.options.beginAtZero) {
this._zero = true;
}
if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) {
this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
}
this.handleTickRangeOptions();
}
handleTickRangeOptions() {
const { minDefined , maxDefined } = this.getUserBounds();
let min = this.min;
let max = this.max;
const setMin = (v)=>min = minDefined ? min : v;
const setMax = (v)=>max = maxDefined ? max : v;
if (min === max) {
if (min <= 0) {
setMin(1);
setMax(10);
} else {
setMin(changeExponent(min, -1));
setMax(changeExponent(max, +1));
}
}
if (min <= 0) {
setMin(changeExponent(max, -1));
}
if (max <= 0) {
setMax(changeExponent(min, +1));
}
this.min = min;
this.max = max;
}
buildTicks() {
const opts = this.options;
const generationOptions = {
min: this._userMin,
max: this._userMax
};
const ticks = generateTicks(generationOptions, this);
if (opts.bounds === 'ticks') {
_setMinAndMaxByKey(ticks, this, 'value');
}
if (opts.reverse) {
ticks.reverse();
this.start = this.max;
this.end = this.min;
} else {
this.start = this.min;
this.end = this.max;
}
return ticks;
}
getLabelForValue(value) {
return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
}
configure() {
const start = this.min;
super.configure();
this._startValue = log10(start);
this._valueRange = log10(this.max) - log10(start);
}
getPixelForValue(value) {
if (value === undefined || value === 0) {
value = this.min;
}
if (value === null || isNaN(value)) {
return NaN;
}
return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange);
}
getValueForPixel(pixel) {
const decimal = this.getDecimalForPixel(pixel);
return Math.pow(10, this._startValue + decimal * this._valueRange);
}
}
function getTickBackdropHeight(opts) {
const tickOpts = opts.ticks;
if (tickOpts.display && opts.display) {
const padding = toPadding(tickOpts.backdropPadding);
return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
}
return 0;
}
function measureLabelSize(ctx, font, label) {
label = isArray(label) ? label : [
label
];
return {
w: _longestText(ctx, font.string, label),
h: label.length * font.lineHeight
};
}
function determineLimits(angle, pos, size, min, max) {
if (angle === min || angle === max) {
return {
start: pos - size / 2,
end: pos + size / 2
};
} else if (angle < min || angle > max) {
return {
start: pos - size,
end: pos
};
}
return {
start: pos,
end: pos + size
};
}
function fitWithPointLabels(scale) {
const orig = {
l: scale.left + scale._padding.left,
r: scale.right - scale._padding.right,
t: scale.top + scale._padding.top,
b: scale.bottom - scale._padding.bottom
};
const limits = Object.assign({}, orig);
const labelSizes = [];
const padding = [];
const valueCount = scale._pointLabels.length;
const pointLabelOpts = scale.options.pointLabels;
const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
for(let i = 0; i < valueCount; i++){
const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
padding[i] = opts.padding;
const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
const plFont = toFont(opts.font);
const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
labelSizes[i] = textSize;
const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
const angle = Math.round(toDegrees(angleRadians));
const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
updateLimits(limits, orig, angleRadians, hLimits, vLimits);
}
scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
}
function updateLimits(limits, orig, angle, hLimits, vLimits) {
const sin = Math.abs(Math.sin(angle));
const cos = Math.abs(Math.cos(angle));
let x = 0;
let y = 0;
if (hLimits.start < orig.l) {
x = (orig.l - hLimits.start) / sin;
limits.l = Math.min(limits.l, orig.l - x);
} else if (hLimits.end > orig.r) {
x = (hLimits.end - orig.r) / sin;
limits.r = Math.max(limits.r, orig.r + x);
}
if (vLimits.start < orig.t) {
y = (orig.t - vLimits.start) / cos;
limits.t = Math.min(limits.t, orig.t - y);
} else if (vLimits.end > orig.b) {
y = (vLimits.end - orig.b) / cos;
limits.b = Math.max(limits.b, orig.b + y);
}
}
function createPointLabelItem(scale, index, itemOpts) {
const outerDistance = scale.drawingArea;
const { extra , additionalAngle , padding , size } = itemOpts;
const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
const y = yForAngle(pointLabelPosition.y, size.h, angle);
const textAlign = getTextAlignForAngle(angle);
const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
return {
visible: true,
x: pointLabelPosition.x,
y,
textAlign,
left,
top: y,
right: left + size.w,
bottom: y + size.h
};
}
function isNotOverlapped(item, area) {
if (!area) {
return true;
}
const { left , top , right , bottom } = item;
const apexesInArea = _isPointInArea({
x: left,
y: top
}, area) || _isPointInArea({
x: left,
y: bottom
}, area) || _isPointInArea({
x: right,
y: top
}, area) || _isPointInArea({
x: right,
y: bottom
}, area);
return !apexesInArea;
}
function buildPointLabelItems(scale, labelSizes, padding) {
const items = [];
const valueCount = scale._pointLabels.length;
const opts = scale.options;
const { centerPointLabels , display } = opts.pointLabels;
const itemOpts = {
extra: getTickBackdropHeight(opts) / 2,
additionalAngle: centerPointLabels ? PI / valueCount : 0
};
let area;
for(let i = 0; i < valueCount; i++){
itemOpts.padding = padding[i];
itemOpts.size = labelSizes[i];
const item = createPointLabelItem(scale, i, itemOpts);
items.push(item);
if (display === 'auto') {
item.visible = isNotOverlapped(item, area);
if (item.visible) {
area = item;
}
}
}
return items;
}
function getTextAlignForAngle(angle) {
if (angle === 0 || angle === 180) {
return 'center';
} else if (angle < 180) {
return 'left';
}
return 'right';
}
function leftForTextAlign(x, w, align) {
if (align === 'right') {
x -= w;
} else if (align === 'center') {
x -= w / 2;
}
return x;
}
function yForAngle(y, h, angle) {
if (angle === 90 || angle === 270) {
y -= h / 2;
} else if (angle > 270 || angle < 90) {
y -= h;
}
return y;
}
function drawPointLabelBox(ctx, opts, item) {
const { left , top , right , bottom } = item;
const { backdropColor } = opts;
if (!isNullOrUndef(backdropColor)) {
const borderRadius = toTRBLCorners(opts.borderRadius);
const padding = toPadding(opts.backdropPadding);
ctx.fillStyle = backdropColor;
const backdropLeft = left - padding.left;
const backdropTop = top - padding.top;
const backdropWidth = right - left + padding.width;
const backdropHeight = bottom - top + padding.height;
if (Object.values(borderRadius).some((v)=>v !== 0)) {
ctx.beginPath();
addRoundedRectPath(ctx, {
x: backdropLeft,
y: backdropTop,
w: backdropWidth,
h: backdropHeight,
radius: borderRadius
});
ctx.fill();
} else {
ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
}
}
}
function drawPointLabels(scale, labelCount) {
const { ctx , options: { pointLabels } } = scale;
for(let i = labelCount - 1; i >= 0; i--){
const item = scale._pointLabelItems[i];
if (!item.visible) {
continue;
}
const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
drawPointLabelBox(ctx, optsAtIndex, item);
const plFont = toFont(optsAtIndex.font);
const { x , y , textAlign } = item;
renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
color: optsAtIndex.color,
textAlign: textAlign,
textBaseline: 'middle'
});
}
}
function pathRadiusLine(scale, radius, circular, labelCount) {
const { ctx } = scale;
if (circular) {
ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
} else {
let pointPosition = scale.getPointPosition(0, radius);
ctx.moveTo(pointPosition.x, pointPosition.y);
for(let i = 1; i < labelCount; i++){
pointPosition = scale.getPointPosition(i, radius);
ctx.lineTo(pointPosition.x, pointPosition.y);
}
}
}
function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
const ctx = scale.ctx;
const circular = gridLineOpts.circular;
const { color , lineWidth } = gridLineOpts;
if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
return;
}
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.setLineDash(borderOpts.dash || []);
ctx.lineDashOffset = borderOpts.dashOffset;
ctx.beginPath();
pathRadiusLine(scale, radius, circular, labelCount);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
function createPointLabelContext(parent, index, label) {
return createContext(parent, {
label,
index,
type: 'pointLabel'
});
}
class RadialLinearScale extends LinearScaleBase {
static id = 'radialLinear';
static defaults = {
display: true,
animate: true,
position: 'chartArea',
angleLines: {
display: true,
lineWidth: 1,
borderDash: [],
borderDashOffset: 0.0
},
grid: {
circular: false
},
startAngle: 0,
ticks: {
showLabelBackdrop: true,
callback: Ticks.formatters.numeric
},
pointLabels: {
backdropColor: undefined,
backdropPadding: 2,
display: true,
font: {
size: 10
},
callback (label) {
return label;
},
padding: 5,
centerPointLabels: false
}
};
static defaultRoutes = {
'angleLines.color': 'borderColor',
'pointLabels.color': 'color',
'ticks.color': 'color'
};
static descriptors = {
angleLines: {
_fallback: 'grid'
}
};
constructor(cfg){
super(cfg);
this.xCenter = undefined;
this.yCenter = undefined;
this.drawingArea = undefined;
this._pointLabels = [];
this._pointLabelItems = [];
}
setDimensions() {
const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
const w = this.width = this.maxWidth - padding.width;
const h = this.height = this.maxHeight - padding.height;
this.xCenter = Math.floor(this.left + w / 2 + padding.left);
this.yCenter = Math.floor(this.top + h / 2 + padding.top);
this.drawingArea = Math.floor(Math.min(w, h) / 2);
}
determineDataLimits() {
const { min , max } = this.getMinMax(false);
this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
this.handleTickRangeOptions();
}
computeTickLimit() {
return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
}
generateTickLabels(ticks) {
LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
this._pointLabels = this.getLabels().map((value, index)=>{
const label = callback(this.options.pointLabels.callback, [
value,
index
], this);
return label || label === 0 ? label : '';
}).filter((v, i)=>this.chart.getDataVisibility(i));
}
fit() {
const opts = this.options;
if (opts.display && opts.pointLabels.display) {
fitWithPointLabels(this);
} else {
this.setCenterPoint(0, 0, 0, 0);
}
}
setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
}
getIndexAngle(index) {
const angleMultiplier = TAU / (this._pointLabels.length || 1);
const startAngle = this.options.startAngle || 0;
return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
}
getDistanceFromCenterForValue(value) {
if (isNullOrUndef(value)) {
return NaN;
}
const scalingFactor = this.drawingArea / (this.max - this.min);
if (this.options.reverse) {
return (this.max - value) * scalingFactor;
}
return (value - this.min) * scalingFactor;
}
getValueForDistanceFromCenter(distance) {
if (isNullOrUndef(distance)) {
return NaN;
}
const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
}
getPointLabelContext(index) {
const pointLabels = this._pointLabels || [];
if (index >= 0 && index < pointLabels.length) {
const pointLabel = pointLabels[index];
return createPointLabelContext(this.getContext(), index, pointLabel);
}
}
getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
return {
x: Math.cos(angle) * distanceFromCenter + this.xCenter,
y: Math.sin(angle) * distanceFromCenter + this.yCenter,
angle
};
}
getPointPositionForValue(index, value) {
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
}
getBasePosition(index) {
return this.getPointPositionForValue(index || 0, this.getBaseValue());
}
getPointLabelPosition(index) {
const { left , top , right , bottom } = this._pointLabelItems[index];
return {
left,
top,
right,
bottom
};
}
drawBackground() {
const { backgroundColor , grid: { circular } } = this.options;
if (backgroundColor) {
const ctx = this.ctx;
ctx.save();
ctx.beginPath();
pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
ctx.closePath();
ctx.fillStyle = backgroundColor;
ctx.fill();
ctx.restore();
}
}
drawGrid() {
const ctx = this.ctx;
const opts = this.options;
const { angleLines , grid , border } = opts;
const labelCount = this._pointLabels.length;
let i, offset, position;
if (opts.pointLabels.display) {
drawPointLabels(this, labelCount);
}
if (grid.display) {
this.ticks.forEach((tick, index)=>{
if (index !== 0 || index === 0 && this.min < 0) {
offset = this.getDistanceFromCenterForValue(tick.value);
const context = this.getContext(index);
const optsAtIndex = grid.setContext(context);
const optsAtIndexBorder = border.setContext(context);
drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
}
});
}
if (angleLines.display) {
ctx.save();
for(i = labelCount - 1; i >= 0; i--){
const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
const { color , lineWidth } = optsAtIndex;
if (!lineWidth || !color) {
continue;
}
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.setLineDash(optsAtIndex.borderDash);
ctx.lineDashOffset = optsAtIndex.borderDashOffset;
offset = this.getDistanceFromCenterForValue(opts.reverse ? this.min : this.max);
position = this.getPointPosition(i, offset);
ctx.beginPath();
ctx.moveTo(this.xCenter, this.yCenter);
ctx.lineTo(position.x, position.y);
ctx.stroke();
}
ctx.restore();
}
}
drawBorder() {}
drawLabels() {
const ctx = this.ctx;
const opts = this.options;
const tickOpts = opts.ticks;
if (!tickOpts.display) {
return;
}
const startAngle = this.getIndexAngle(0);
let offset, width;
ctx.save();
ctx.translate(this.xCenter, this.yCenter);
ctx.rotate(startAngle);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
this.ticks.forEach((tick, index)=>{
if (index === 0 && this.min >= 0 && !opts.reverse) {
return;
}
const optsAtIndex = tickOpts.setContext(this.getContext(index));
const tickFont = toFont(optsAtIndex.font);
offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
if (optsAtIndex.showLabelBackdrop) {
ctx.font = tickFont.string;
width = ctx.measureText(tick.label).width;
ctx.fillStyle = optsAtIndex.backdropColor;
const padding = toPadding(optsAtIndex.backdropPadding);
ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
}
renderText(ctx, tick.label, 0, -offset, tickFont, {
color: optsAtIndex.color,
strokeColor: optsAtIndex.textStrokeColor,
strokeWidth: optsAtIndex.textStrokeWidth
});
});
ctx.restore();
}
drawTitle() {}
}
const INTERVALS = {
millisecond: {
common: true,
size: 1,
steps: 1000
},
second: {
common: true,
size: 1000,
steps: 60
},
minute: {
common: true,
size: 60000,
steps: 60
},
hour: {
common: true,
size: 3600000,
steps: 24
},
day: {
common: true,
size: 86400000,
steps: 30
},
week: {
common: false,
size: 604800000,
steps: 4
},
month: {
common: true,
size: 2.628e9,
steps: 12
},
quarter: {
common: false,
size: 7.884e9,
steps: 4
},
year: {
common: true,
size: 3.154e10
}
};
const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
function sorter(a, b) {
return a - b;
}
function parse(scale, input) {
if (isNullOrUndef(input)) {
return null;
}
const adapter = scale._adapter;
const { parser , round , isoWeekday } = scale._parseOpts;
let value = input;
if (typeof parser === 'function') {
value = parser(value);
}
if (!isNumberFinite(value)) {
value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
}
if (value === null) {
return null;
}
if (round) {
value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
}
return +value;
}
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
const ilen = UNITS.length;
for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
const interval = INTERVALS[UNITS[i]];
const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
return UNITS[i];
}
}
return UNITS[ilen - 1];
}
function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
const unit = UNITS[i];
if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
return unit;
}
}
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
}
function determineMajorUnit(unit) {
for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
if (INTERVALS[UNITS[i]].common) {
return UNITS[i];
}
}
}
function addTick(ticks, time, timestamps) {
if (!timestamps) {
ticks[time] = true;
} else if (timestamps.length) {
const { lo , hi } = _lookup(timestamps, time);
const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
ticks[timestamp] = true;
}
}
function setMajorTicks(scale, ticks, map, majorUnit) {
const adapter = scale._adapter;
const first = +adapter.startOf(ticks[0].value, majorUnit);
const last = ticks[ticks.length - 1].value;
let major, index;
for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
index = map[major];
if (index >= 0) {
ticks[index].major = true;
}
}
return ticks;
}
function ticksFromTimestamps(scale, values, majorUnit) {
const ticks = [];
const map = {};
const ilen = values.length;
let i, value;
for(i = 0; i < ilen; ++i){
value = values[i];
map[value] = i;
ticks.push({
value,
major: false
});
}
return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
}
class TimeScale extends Scale {
static id = 'time';
static defaults = {
bounds: 'data',
adapters: {},
time: {
parser: false,
unit: false,
round: false,
isoWeekday: false,
minUnit: 'millisecond',
displayFormats: {}
},
ticks: {
source: 'auto',
callback: false,
major: {
enabled: false
}
}
};
constructor(props){
super(props);
this._cache = {
data: [],
labels: [],
all: []
};
this._unit = 'day';
this._majorUnit = undefined;
this._offsets = {};
this._normalized = false;
this._parseOpts = undefined;
}
init(scaleOpts, opts = {}) {
const time = scaleOpts.time || (scaleOpts.time = {});
const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
adapter.init(opts);
mergeIf(time.displayFormats, adapter.formats());
this._parseOpts = {
parser: time.parser,
round: time.round,
isoWeekday: time.isoWeekday
};
super.init(scaleOpts);
this._normalized = opts.normalized;
}
parse(raw, index) {
if (raw === undefined) {
return null;
}
return parse(this, raw);
}
beforeLayout() {
super.beforeLayout();
this._cache = {
data: [],
labels: [],
all: []
};
}
determineDataLimits() {
const options = this.options;
const adapter = this._adapter;
const unit = options.time.unit || 'day';
let { min , max , minDefined , maxDefined } = this.getUserBounds();
function _applyBounds(bounds) {
if (!minDefined && !isNaN(bounds.min)) {
min = Math.min(min, bounds.min);
}
if (!maxDefined && !isNaN(bounds.max)) {
max = Math.max(max, bounds.max);
}
}
if (!minDefined || !maxDefined) {
_applyBounds(this._getLabelBounds());
if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
_applyBounds(this.getMinMax(false));
}
}
min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
this.min = Math.min(min, max - 1);
this.max = Math.max(min + 1, max);
}
_getLabelBounds() {
const arr = this.getLabelTimestamps();
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
if (arr.length) {
min = arr[0];
max = arr[arr.length - 1];
}
return {
min,
max
};
}
buildTicks() {
const options = this.options;
const timeOpts = options.time;
const tickOpts = options.ticks;
const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
if (options.bounds === 'ticks' && timestamps.length) {
this.min = this._userMin || timestamps[0];
this.max = this._userMax || timestamps[timestamps.length - 1];
}
const min = this.min;
const max = this.max;
const ticks = _filterBetween(timestamps, min, max);
this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
this.initOffsets(timestamps);
if (options.reverse) {
ticks.reverse();
}
return ticksFromTimestamps(this, ticks, this._majorUnit);
}
afterAutoSkip() {
if (this.options.offsetAfterAutoskip) {
this.initOffsets(this.ticks.map((tick)=>+tick.value));
}
}
initOffsets(timestamps = []) {
let start = 0;
let end = 0;
let first, last;
if (this.options.offset && timestamps.length) {
first = this.getDecimalForValue(timestamps[0]);
if (timestamps.length === 1) {
start = 1 - first;
} else {
start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
}
last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
if (timestamps.length === 1) {
end = last;
} else {
end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
}
}
const limit = timestamps.length < 3 ? 0.5 : 0.25;
start = _limitValue(start, 0, limit);
end = _limitValue(end, 0, limit);
this._offsets = {
start,
end,
factor: 1 / (start + 1 + end)
};
}
_generate() {
const adapter = this._adapter;
const min = this.min;
const max = this.max;
const options = this.options;
const timeOpts = options.time;
const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
const stepSize = valueOrDefault(options.ticks.stepSize, 1);
const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
const hasWeekday = isNumber(weekday) || weekday === true;
const ticks = {};
let first = min;
let time, count;
if (hasWeekday) {
first = +adapter.startOf(first, 'isoWeek', weekday);
}
first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
if (adapter.diff(max, min, minor) > 100000 * stepSize) {
throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
}
const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
addTick(ticks, time, timestamps);
}
if (time === max || options.bounds === 'ticks' || count === 1) {
addTick(ticks, time, timestamps);
}
return Object.keys(ticks).sort(sorter).map((x)=>+x);
}
getLabelForValue(value) {
const adapter = this._adapter;
const timeOpts = this.options.time;
if (timeOpts.tooltipFormat) {
return adapter.format(value, timeOpts.tooltipFormat);
}
return adapter.format(value, timeOpts.displayFormats.datetime);
}
format(value, format) {
const options = this.options;
const formats = options.time.displayFormats;
const unit = this._unit;
const fmt = format || formats[unit];
return this._adapter.format(value, fmt);
}
_tickFormatFunction(time, index, ticks, format) {
const options = this.options;
const formatter = options.ticks.callback;
if (formatter) {
return callback(formatter, [
time,
index,
ticks
], this);
}
const formats = options.time.displayFormats;
const unit = this._unit;
const majorUnit = this._majorUnit;
const minorFormat = unit && formats[unit];
const majorFormat = majorUnit && formats[majorUnit];
const tick = ticks[index];
const major = majorUnit && majorFormat && tick && tick.major;
return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
}
generateTickLabels(ticks) {
let i, ilen, tick;
for(i = 0, ilen = ticks.length; i < ilen; ++i){
tick = ticks[i];
tick.label = this._tickFormatFunction(tick.value, i, ticks);
}
}
getDecimalForValue(value) {
return value === null ? NaN : (value - this.min) / (this.max - this.min);
}
getPixelForValue(value) {
const offsets = this._offsets;
const pos = this.getDecimalForValue(value);
return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
}
getValueForPixel(pixel) {
const offsets = this._offsets;
const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
return this.min + pos * (this.max - this.min);
}
_getLabelSize(label) {
const ticksOpts = this.options.ticks;
const tickLabelWidth = this.ctx.measureText(label).width;
const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
const cosRotation = Math.cos(angle);
const sinRotation = Math.sin(angle);
const tickFontSize = this._resolveTickFontOptions(0).size;
return {
w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
};
}
_getLabelCapacity(exampleTime) {
const timeOpts = this.options.time;
const displayFormats = timeOpts.displayFormats;
const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
exampleTime
], this._majorUnit), format);
const size = this._getLabelSize(exampleLabel);
const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
return capacity > 0 ? capacity : 1;
}
getDataTimestamps() {
let timestamps = this._cache.data || [];
let i, ilen;
if (timestamps.length) {
return timestamps;
}
const metas = this.getMatchingVisibleMetas();
if (this._normalized && metas.length) {
return this._cache.data = metas[0].controller.getAllParsedValues(this);
}
for(i = 0, ilen = metas.length; i < ilen; ++i){
timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
}
return this._cache.data = this.normalize(timestamps);
}
getLabelTimestamps() {
const timestamps = this._cache.labels || [];
let i, ilen;
if (timestamps.length) {
return timestamps;
}
const labels = this.getLabels();
for(i = 0, ilen = labels.length; i < ilen; ++i){
timestamps.push(parse(this, labels[i]));
}
return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
}
normalize(values) {
return _arrayUnique(values.sort(sorter));
}
}
function chart_interpolate(table, val, reverse) {
let lo = 0;
let hi = table.length - 1;
let prevSource, nextSource, prevTarget, nextTarget;
if (reverse) {
if (val >= table[lo].pos && val <= table[hi].pos) {
({ lo , hi } = _lookupByKey(table, 'pos', val));
}
({ pos: prevSource , time: prevTarget } = table[lo]);
({ pos: nextSource , time: nextTarget } = table[hi]);
} else {
if (val >= table[lo].time && val <= table[hi].time) {
({ lo , hi } = _lookupByKey(table, 'time', val));
}
({ time: prevSource , pos: prevTarget } = table[lo]);
({ time: nextSource , pos: nextTarget } = table[hi]);
}
const span = nextSource - prevSource;
return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
}
class TimeSeriesScale extends TimeScale {
static id = 'timeseries';
static defaults = TimeScale.defaults;
constructor(props){
super(props);
this._table = [];
this._minPos = undefined;
this._tableRange = undefined;
}
initOffsets() {
const timestamps = this._getTimestampsForTable();
const table = this._table = this.buildLookupTable(timestamps);
this._minPos = chart_interpolate(table, this.min);
this._tableRange = chart_interpolate(table, this.max) - this._minPos;
super.initOffsets(timestamps);
}
buildLookupTable(timestamps) {
const { min , max } = this;
const items = [];
const table = [];
let i, ilen, prev, curr, next;
for(i = 0, ilen = timestamps.length; i < ilen; ++i){
curr = timestamps[i];
if (curr >= min && curr <= max) {
items.push(curr);
}
}
if (items.length < 2) {
return [
{
time: min,
pos: 0
},
{
time: max,
pos: 1
}
];
}
for(i = 0, ilen = items.length; i < ilen; ++i){
next = items[i + 1];
prev = items[i - 1];
curr = items[i];
if (Math.round((next + prev) / 2) !== curr) {
table.push({
time: curr,
pos: i / (ilen - 1)
});
}
}
return table;
}
_generate() {
const min = this.min;
const max = this.max;
let timestamps = super.getDataTimestamps();
if (!timestamps.includes(min) || !timestamps.length) {
timestamps.splice(0, 0, min);
}
if (!timestamps.includes(max) || timestamps.length === 1) {
timestamps.push(max);
}
return timestamps.sort((a, b)=>a - b);
}
_getTimestampsForTable() {
let timestamps = this._cache.all || [];
if (timestamps.length) {
return timestamps;
}
const data = this.getDataTimestamps();
const label = this.getLabelTimestamps();
if (data.length && label.length) {
timestamps = this.normalize(data.concat(label));
} else {
timestamps = data.length ? data : label;
}
timestamps = this._cache.all = timestamps;
return timestamps;
}
getDecimalForValue(value) {
return (chart_interpolate(this._table, value) - this._minPos) / this._tableRange;
}
getValueForPixel(pixel) {
const offsets = this._offsets;
const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
return chart_interpolate(this._table, decimal * this._tableRange + this._minPos, true);
}
}
var scales = /*#__PURE__*/Object.freeze({
__proto__: null,
CategoryScale: CategoryScale,
LinearScale: LinearScale,
LogarithmicScale: LogarithmicScale,
RadialLinearScale: RadialLinearScale,
TimeScale: TimeScale,
TimeSeriesScale: TimeSeriesScale
});
const registerables = [
controllers,
chart_elements,
plugins,
scales
];
//# sourceMappingURL=chart.js.map
;// ../assets/scripts/constants/colors.js
const COLORS = {
'white': '#ffffff',
'red-50': '#ffebee',
'red-100': '#ffcdd2',
'red-200': '#ef9a9a',
'red-300': '#e57373',
'red-400': '#ef5350',
'red-500': '#f44336',
'red-600': '#e53935',
'red-700': '#d32f2f',
'red-800': '#c62828',
'red-900': '#b71c1c',
'red-a100': '#ff8a80',
'red-a200': '#ff5252',
'red-a400': '#ff1744',
'red-a700': '#d50000',
'pink-50': '#fce4ec',
'pink-100': '#f8bbd0',
'pink-200': '#f48fb1',
'pink-300': '#f06292',
'pink-400': '#ec407a',
'pink-500': '#e91e63',
'pink-600': '#d81b60',
'pink-700': '#c2185b',
'pink-800': '#ad1457',
'pink-900': '#880e4f',
'pink-a100': '#ff80ab',
'pink-a200': '#ff4081',
'pink-a400': '#f50057',
'pink-a700': '#c51162',
'purple-50': '#f3e5f5',
'purple-100': '#e1bee7',
'purple-200': '#ce93d8',
'purple-300': '#ba68c8',
'purple-400': '#ab47bc',
'purple-500': '#9c27b0',
'purple-600': '#8e24aa',
'purple-700': '#7b1fa2',
'purple-800': '#6a1b9a',
'purple-900': '#4a148c',
'purple-a100': '#ea80fc',
'purple-a200': '#e040fb',
'purple-a400': '#d500f9',
'purple-a700': '#aa00ff',
'deep-purple-50': '#ede7f6',
'deep-purple-100': '#d1c4e9',
'deep-purple-200': '#b39ddb',
'deep-purple-300': '#9575cd',
'deep-purple-400': '#7e57c2',
'deep-purple-500': '#673ab7',
'deep-purple-600': '#5e35b1',
'deep-purple-700': '#512da8',
'deep-purple-800': '#4527a0',
'deep-purple-900': '#311b92',
'deep-purple-a100': '#b388ff',
'deep-purple-a200': '#7c4dff',
'deep-purple-a400': '#651fff',
'deep-purple-a700': '#6200ea',
'indigo-50': '#e8eaf6',
'indigo-100': '#c5cae9',
'indigo-200': '#9fa8da',
'indigo-300': '#7986cb',
'indigo-400': '#5c6bc0',
'indigo-500': '#3f51b5',
'indigo-600': '#3949ab',
'indigo-700': '#303f9f',
'indigo-800': '#283593',
'indigo-900': '#1a237e',
'indigo-a100': '#8c9eff',
'indigo-a200': '#536dfe',
'indigo-a400': '#3d5afe',
'indigo-a700': '#304ffe',
'blue-50': '#e3f2fd',
'blue-100': '#bbdefb',
'blue-200': '#90caf9',
'blue-300': '#64b5f6',
'blue-400': '#42a5f5',
'blue-500': '#2196f3',
'blue-600': '#1e88e5',
'blue-700': '#1976d2',
'blue-800': '#1565c0',
'blue-900': '#0d47a1',
'blue-a100': '#82b1ff',
'blue-a200': '#448aff',
'blue-a400': '#2979ff',
'blue-a700': '#2962ff',
'light-blue-50': '#e1f5fe',
'light-blue-100': '#b3e5fc',
'light-blue-200': '#81d4fa',
'light-blue-300': '#4fc3f7',
'light-blue-400': '#29b6f6',
'light-blue-500': '#03a9f4',
'light-blue-600': '#039be5',
'light-blue-700': '#0288d1',
'light-blue-800': '#0277bd',
'light-blue-900': '#01579b',
'light-blue-a100': '#80d8ff',
'light-blue-a200': '#40c4ff',
'light-blue-a400': '#00b0ff',
'light-blue-a700': '#0091ea',
'cyan-50': '#e0f7fa',
'cyan-100': '#b2ebf2',
'cyan-200': '#80deea',
'cyan-300': '#4dd0e1',
'cyan-400': '#26c6da',
'cyan-500': '#00bcd4',
'cyan-600': '#00acc1',
'cyan-700': '#0097a7',
'cyan-800': '#00838f',
'cyan-900': '#006064',
'cyan-a100': '#84ffff',
'cyan-a200': '#18ffff',
'cyan-a400': '#00e5ff',
'cyan-a700': '#00b8d4',
'teal-50': '#e0f2f1',
'teal-100': '#b2dfdb',
'teal-200': '#80cbc4',
'teal-300': '#4db6ac',
'teal-400': '#26a69a',
'teal-500': '#009688',
'teal-600': '#00897b',
'teal-700': '#00796b',
'teal-800': '#00695c',
'teal-900': '#004d40',
'teal-a100': '#a7ffeb',
'teal-a200': '#64ffda',
'teal-a400': '#1de9b6',
'teal-a700': '#00bfa5',
'green-50': '#e8f5e9',
'green-100': '#c8e6c9',
'green-200': '#a5d6a7',
'green-300': '#81c784',
'green-400': '#66bb6a',
'green-500': '#4caf50',
'green-600': '#43a047',
'green-700': '#388e3c',
'green-800': '#2e7d32',
'green-900': '#1b5e20',
'green-a100': '#b9f6ca',
'green-a200': '#69f0ae',
'green-a400': '#00e676',
'green-a700': '#00c853',
'light-green-50': '#f1f8e9',
'light-green-100': '#dcedc8',
'light-green-200': '#c5e1a5',
'light-green-300': '#aed581',
'light-green-400': '#9ccc65',
'light-green-500': '#8bc34a',
'light-green-600': '#7cb342',
'light-green-700': '#689f38',
'light-green-800': '#558b2f',
'light-green-900': '#33691e',
'light-green-a100': '#ccff90',
'light-green-a200': '#b2ff59',
'light-green-a400': '#76ff03',
'light-green-a700': '#64dd17',
'lime-50': '#f9fbe7',
'lime-100': '#f0f4c3',
'lime-200': '#e6ee9c',
'lime-300': '#dce775',
'lime-400': '#d4e157',
'lime-500': '#cddc39',
'lime-600': '#c0ca33',
'lime-700': '#afb42b',
'lime-800': '#9e9d24',
'lime-900': '#827717',
'lime-a100': '#f4ff81',
'lime-a200': '#eeff41',
'lime-a400': '#c6ff00',
'lime-a700': '#aeea00',
'yellow-50': '#fffde7',
'yellow-100': '#fff9c4',
'yellow-200': '#fff59d',
'yellow-300': '#fff176',
'yellow-400': '#ffee58',
'yellow-500': '#ffeb3b',
'yellow-600': '#fdd835',
'yellow-700': '#fbc02d',
'yellow-800': '#f9a825',
'yellow-900': '#f57f17',
'yellow-a100': '#ffff8d',
'yellow-a200': '#ffff00',
'yellow-a400': '#ffea00',
'yellow-a700': '#ffd600',
'amber-50': '#fff8e1',
'amber-100': '#ffecb3',
'amber-200': '#ffe082',
'amber-300': '#ffd54f',
'amber-400': '#ffca28',
'amber-500': '#ffc107',
'amber-600': '#ffb300',
'amber-700': '#ffa000',
'amber-800': '#ff8f00',
'amber-900': '#ff6f00',
'amber-a100': '#ffe57f',
'amber-a200': '#ffd740',
'amber-a400': '#ffc400',
'amber-a700': '#ffab00',
'orange-50': '#fff3e0',
'orange-100': '#ffe0b2',
'orange-200': '#ffcc80',
'orange-300': '#ffb74d',
'orange-400': '#ffa726',
'orange-500': '#ff9800',
'orange-600': '#fb8c00',
'orange-700': '#f57c00',
'orange-800': '#ef6c00',
'orange-900': '#e65100',
'orange-a100': '#ffd180',
'orange-a200': '#ffab40',
'orange-a400': '#ff9100',
'orange-a700': '#ff6d00',
'deep-orange-50': '#fbe9e7',
'deep-orange-100': '#ffccbc',
'deep-orange-200': '#ffab91',
'deep-orange-300': '#ff8a65',
'deep-orange-400': '#ff7043',
'deep-orange-500': '#ff5722',
'deep-orange-600': '#f4511e',
'deep-orange-700': '#e64a19',
'deep-orange-800': '#d84315',
'deep-orange-900': '#bf360c',
'deep-orange-a100': '#ff9e80',
'deep-orange-a200': '#ff6e40',
'deep-orange-a400': '#ff3d00',
'deep-orange-a700': '#dd2c00',
'brown-50': '#efebe9',
'brown-100': '#d7ccc8',
'brown-200': '#bcaaa4',
'brown-300': '#a1887f',
'brown-400': '#8d6e63',
'brown-500': '#795548',
'brown-600': '#6d4c41',
'brown-700': '#5d4037',
'brown-800': '#4e342e',
'brown-900': '#3e2723',
'grey-50': '#fafafa',
'grey-100': '#f5f5f5',
'grey-200': '#eeeeee',
'grey-300': '#e0e0e0',
'grey-400': '#bdbdbd',
'grey-500': '#9e9e9e',
'grey-600': '#757575',
'grey-700': '#616161',
'grey-800': '#424242',
'grey-900': '#212121',
'blue-grey-50': '#eceff1',
'blue-grey-100': '#cfd8dc',
'blue-grey-200': '#b0bec5',
'blue-grey-300': '#90a4ae',
'blue-grey-400': '#78909c',
'blue-grey-500': '#607d8b',
'blue-grey-600': '#546e7a',
'blue-grey-700': '#455a64',
'blue-grey-800': '#37474f',
'blue-grey-900': '#263238'
};
const GREYS = {
'grey-100': '#f9fafb',
'grey-200': '#f2f3f5',
'grey-300': '#e6eaf0',
'grey-400': '#d3d9e3',
'grey-500': '#b9c2d0',
'grey-600': '#7c8695',
'grey-700': '#72777a',
'grey-800': '#565a5c',
'grey-900': '#313435'
};
;// ../assets/scripts/components/Chart.js
/**
* Modern Chart Component
* Replaces jQuery Sparkline with Chart.js
*/
// Register Chart.js components
chart_Chart.register(...registerables);
class ChartComponent {
constructor() {
this.charts = new Map(); // Store chart instances
this.debounceTimer = null;
this.init();
}
init() {
// Only disable resizing for small sparkline charts
this.createSparklines();
this.createOtherCharts();
this.setupResizeHandler();
}
/**
* Create sparklines (only for dashboard page)
*/
createSparklines() {
// Only create sparklines if we're on a page that has them
const sparklineExists = document.getElementById('sparklinedash');
if (!sparklineExists) {
return;
}
const sparklineConfigs = [{
id: 'sparklinedash',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#4caf50'
}, {
id: 'sparklinedash2',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#9675ce'
}, {
id: 'sparklinedash3',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#03a9f3'
}, {
id: 'sparklinedash4',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#f96262'
}];
sparklineConfigs.forEach(config => {
// Only create if the target element exists
if (document.getElementById(config.id)) {
this.createSparklineChart(config);
}
});
}
/**
* Create sparkline chart from configuration
*/
createSparklineChart(_ref) {
let {
id,
data,
color
} = _ref;
let canvas = document.getElementById(id);
// Only proceed if we have a valid target element
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace the span with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
// Set canvas dimensions to match original sparkline
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
backgroundColor: color,
borderColor: color,
borderWidth: 0,
barPercentage: 0.6,
categoryPercentage: 0.8
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
},
elements: {
bar: {
borderRadius: 1
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create other chart types (only if they exist on the page)
*/
createOtherCharts() {
// Determine if we're on the dashboard or charts page
const isChartsPage = document.getElementById('area-chart') !== null;
const isDashboard = !isChartsPage && document.getElementById('line-chart') !== null;
// Create Monthly Stats chart with enhanced dual-line data (dashboard only)
if (isDashboard) {
this.createMonthlyStatsChart();
}
// Charts page specific charts (only on charts page)
if (isChartsPage) {
this.createChartsPageCharts();
}
// Only create charts if their target elements exist
if (document.getElementById('sparkline')) {
this.createLineChart('sparkline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7]);
}
if (document.getElementById('compositebar')) {
this.createCompositeChart('compositebar', [4, 1, 5, 7, 9, 9, 8, 7, 6, 6, 4, 7, 8, 4, 3, 2, 2, 5, 6, 7]);
}
// Regular sparklines with custom colors (only on pages that have them)
this.createCustomSparklines();
// Easy Pie Charts (only if they exist)
this.createEasyPieCharts();
}
/**
* Create enhanced Monthly Stats chart with dual lines and more data
*/
createMonthlyStatsChart() {
const canvas = document.getElementById('line-chart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Enhanced data for monthly stats
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const salesData = [120, 135, 145, 165, 180, 195, 210, 225, 240, 220, 200, 185];
const profitData = [45, 52, 58, 62, 68, 75, 82, 88, 92, 85, 78, 72];
const chart = new chart_Chart(ctx, {
type: 'line',
data: {
labels: months,
datasets: [{
label: 'Sales ($K)',
data: salesData,
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#4caf50',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
tension: 0.4,
fill: false
}, {
label: 'Profit ($K)',
data: profitData,
borderColor: '#2196f3',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#2196f3',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
tension: 0.4,
fill: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
padding: 20,
font: {
size: 12,
weight: '600'
}
}
},
tooltip: {
enabled: true,
cornerRadius: 8,
displayColors: true,
intersect: false,
mode: 'index',
callbacks: {
label(context) {
return `${context.dataset.label}: $${context.parsed.y}K`;
}
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
font: {
size: 11
}
}
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
},
callback(value) {
return `$${value}K`;
}
}
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
this.charts.set('line-chart', chart);
}
/**
* Create line chart (only if target exists)
*/
createLineChart(id, data) {
let canvas = document.getElementById(id);
// Only proceed if target element exists
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace element with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
borderColor: COLORS['blue-500'],
backgroundColor: 'transparent',
borderWidth: 1,
pointRadius: 0,
tension: 0.4
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create composite chart (only if target exists)
*/
createCompositeChart(id, data) {
let canvas = document.getElementById(id);
// Only proceed if target element exists
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace element with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
type: 'bar',
data,
backgroundColor: '#aaf',
borderColor: '#aaf',
borderWidth: 0
}, {
type: 'line',
data,
borderColor: 'red',
backgroundColor: 'transparent',
borderWidth: 1,
pointRadius: 0,
tension: 0.4
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create custom sparklines for different elements (only if they exist)
*/
createCustomSparklines() {
const sparklineElements = document.querySelectorAll('.sparkline');
const sparkbarElements = document.querySelectorAll('.sparkbar');
const sparktriElements = document.querySelectorAll('.sparktri');
const sparkdiscElements = document.querySelectorAll('.sparkdisc');
const sparkbullElements = document.querySelectorAll('.sparkbull');
const sparkboxElements = document.querySelectorAll('.sparkbox');
// Only create if we have elements
if (sparklineElements.length === 0 && sparkbarElements.length === 0 && sparktriElements.length === 0 && sparkdiscElements.length === 0 && sparkbullElements.length === 0 && sparkboxElements.length === 0) {
return;
}
const values = [5, 4, 5, -2, 0, 3, -5, 6, 7, 9, 9, 5, -3, -2, 2, -4];
const valuesAlt = [1, 1, 0, 1, -1, -1, 1, -1, 0, 0, 1, 1];
sparklineElements.forEach((element, index) => {
this.createCustomLineChart(element, values, `sparkline-${index}`);
});
sparkbarElements.forEach((element, index) => {
this.createCustomBarChart(element, values, `sparkbar-${index}`);
});
sparktriElements.forEach((element, index) => {
this.createTristateChart(element, valuesAlt, `sparktri-${index}`);
});
sparkdiscElements.forEach((element, index) => {
this.createDiscreteChart(element, values, `sparkdisc-${index}`);
});
sparkbullElements.forEach((element, index) => {
this.createBulletChart(element, values, `sparkbull-${index}`);
});
sparkboxElements.forEach((element, index) => {
this.createBoxChart(element, values, `sparkbox-${index}`);
});
}
/**
* Create custom line chart for sparkline elements
*/
createCustomLineChart(element, data, id) {
// Create canvas if it doesn't exist
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
borderColor: COLORS['red-500'],
backgroundColor: 'transparent',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: COLORS['red-500'],
tension: 0.4
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
// Disable animations to prevent resize triggers
events: [],
// Disable all events to prevent resize
onResize: null,
// Explicitly disable resize callback
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
} // Disable tooltip to prevent events
}
}
});
this.charts.set(id, chart);
}
/**
* Create custom bar chart for sparkbar elements
*/
createCustomBarChart(element, data, id) {
// Create canvas if it doesn't exist
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
backgroundColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
borderColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
borderWidth: 1,
barPercentage: 0.8
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
callbacks: {
label: context => `${context.parsed.y}°Celsius`
}
}
}
}
});
this.charts.set(id, chart);
}
/**
* Setup resize handler for charts
*/
setupResizeHandler() {
// Setup responsive resize for large charts only
window.addEventListener('resize', () => {
this.debounceResize();
});
// Listen for sidebar toggle events
window.addEventListener('sidebar:toggle', () => {
this.debounceResize();
});
}
/**
* Debounced resize handler
*/
debounceResize() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.redrawLargeChartsOnly();
}, 150);
}
/**
* Redraw only large charts, not sparklines
*/
redrawLargeChartsOnly() {
const largeChartIds = ['line-chart', 'area-chart', 'scatter-chart', 'bar-chart', 'doughnut-chart', 'polar-chart', 'radar-chart', 'mixed-chart', 'bubble-chart'];
largeChartIds.forEach(id => {
const chart = this.charts.get(id);
if (chart && chart.options.responsive) {
chart.resize();
}
});
}
/**
* Redraw all charts (used sparingly)
*/
redrawCharts() {
this.charts.forEach(chart => {
if (chart.options.responsive) {
chart.resize();
}
});
}
/**
* Update chart data
*/
updateChart(id, newData) {
const chart = this.charts.get(id);
if (chart) {
chart.data.datasets[0].data = newData;
chart.update();
}
}
/**
* Create charts for the charts.html page
*/
createChartsPageCharts() {
// Line Chart
this.createLargeChart('line-chart', 'line', {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.4
}]
});
// Area Chart
this.createLargeChart('area-chart', 'line', {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.4)',
fill: true,
tension: 0.4
}]
});
// Scatter Chart with more data points
this.createLargeChart('scatter-chart', 'scatter', {
datasets: [{
label: 'Dataset 1',
data: [{
x: -15,
y: 8
}, {
x: -12,
y: 12
}, {
x: -8,
y: 3
}, {
x: -5,
y: 15
}, {
x: -2,
y: 7
}, {
x: 0,
y: 10
}, {
x: 3,
y: 18
}, {
x: 6,
y: 5
}, {
x: 9,
y: 22
}, {
x: 12,
y: 8
}, {
x: 15,
y: 14
}, {
x: 18,
y: 19
}, {
x: -10,
y: 0
}, {
x: 10,
y: 5
}, {
x: 0.5,
y: 5.5
}, {
x: 7,
y: 12
}, {
x: -7,
y: 17
}, {
x: 4,
y: 9
}, {
x: 11,
y: 16
}, {
x: -3,
y: 11
}],
backgroundColor: 'rgba(255, 99, 132, 0.7)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1
}, {
label: 'Dataset 2',
data: [{
x: -13,
y: 4
}, {
x: -9,
y: 8
}, {
x: -6,
y: 13
}, {
x: -1,
y: 6
}, {
x: 2,
y: 11
}, {
x: 5,
y: 15
}, {
x: 8,
y: 2
}, {
x: 13,
y: 17
}, {
x: 16,
y: 9
}, {
x: -4,
y: 14
}, {
x: 1,
y: 20
}, {
x: 14,
y: 4
}],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1
}]
});
// Bar Chart
this.createLargeChart('bar-chart', 'bar', {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 205, 86, 0.6)', 'rgba(75, 192, 192, 0.6)', 'rgba(153, 102, 255, 0.6)', 'rgba(255, 159, 64, 0.6)'],
borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)'],
borderWidth: 1
}]
});
// Doughnut Chart
this.createLargeChart('doughnut-chart', 'doughnut', {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: 'My First Dataset',
data: [300, 50, 100, 75, 120, 60],
backgroundColor: ['rgba(255, 99, 132, 0.8)', 'rgba(54, 162, 235, 0.8)', 'rgba(255, 205, 86, 0.8)', 'rgba(75, 192, 192, 0.8)', 'rgba(153, 102, 255, 0.8)', 'rgba(255, 159, 64, 0.8)'],
borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)'],
borderWidth: 2,
hoverOffset: 10
}]
});
// Polar Area Chart
this.createLargeChart('polar-chart', 'polarArea', {
labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
datasets: [{
label: 'My First Dataset',
data: [11, 16, 7, 3, 14],
backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(255, 205, 86, 0.7)', 'rgba(201, 203, 207, 0.7)', 'rgba(54, 162, 235, 0.7)'],
borderColor: ['rgb(255, 99, 132)', 'rgb(75, 192, 192)', 'rgb(255, 205, 86)', 'rgb(201, 203, 207)', 'rgb(54, 162, 235)'],
borderWidth: 2
}]
});
// Radar Chart
this.createLargeChart('radar-chart', 'radar', {
labels: ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency', 'Innovation'],
datasets: [{
label: 'Product A',
data: [65, 59, 90, 81, 56, 55],
fill: true,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 2,
pointBackgroundColor: 'rgb(54, 162, 235)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(54, 162, 235)'
}, {
label: 'Product B',
data: [28, 48, 40, 95, 86, 27],
fill: true,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 2,
pointBackgroundColor: 'rgb(255, 99, 132)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 99, 132)'
}]
});
// Mixed Chart (Bar + Line)
this.createLargeChart('mixed-chart', 'bar', {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
type: 'bar',
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1
}, {
type: 'line',
label: 'Revenue',
data: [18, 25, 8, 15, 12, 18],
fill: false,
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderWidth: 3,
tension: 0.4,
pointRadius: 5,
pointHoverRadius: 7
}]
});
// Bubble Chart
this.createLargeChart('bubble-chart', 'bubble', {
datasets: [{
label: 'First Dataset',
data: [{
x: 20,
y: 30,
r: 15
}, {
x: 40,
y: 10,
r: 10
}, {
x: 30,
y: 40,
r: 20
}, {
x: 50,
y: 35,
r: 12
}, {
x: 10,
y: 50,
r: 8
}, {
x: 60,
y: 20,
r: 18
}, {
x: 25,
y: 25,
r: 14
}],
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 2
}, {
label: 'Second Dataset',
data: [{
x: 15,
y: 45,
r: 12
}, {
x: 35,
y: 15,
r: 16
}, {
x: 45,
y: 25,
r: 9
}, {
x: 55,
y: 45,
r: 14
}, {
x: 25,
y: 35,
r: 11
}],
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 2
}]
});
}
/**
* Create large chart for charts page
*/
createLargeChart(id, type, data) {
const canvas = document.getElementById(id);
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Define chart-specific options
const chartOptions = this.getChartOptions(type);
const chart = new chart_Chart(ctx, {
type,
data,
options: chartOptions
});
this.charts.set(id, chart);
}
/**
* Get chart-specific options based on chart type
*/
getChartOptions(type) {
const baseOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
padding: 20,
font: {
size: 12,
weight: '600'
}
}
},
tooltip: {
enabled: true,
cornerRadius: 8,
displayColors: true
}
}
};
// Chart type specific configurations
switch (type) {
case 'doughnut':
case 'pie':
return {
...baseOptions,
plugins: {
...baseOptions.plugins,
legend: {
...baseOptions.plugins.legend,
position: 'right'
}
},
interaction: {
intersect: false
}
};
case 'polarArea':
return {
...baseOptions,
scales: {
r: {
pointLabels: {
display: true,
centerPointLabels: true,
font: {
size: 10
}
},
grid: {}
}
}
};
case 'radar':
return {
...baseOptions,
scales: {
r: {
angleLines: {
display: true
},
grid: {},
pointLabels: {
font: {
size: 11
}
},
ticks: {
display: true,
font: {
size: 10
}
}
}
}
};
case 'bubble':
return {
...baseOptions,
scales: {
x: {
type: 'linear',
position: 'bottom',
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
}
},
plugins: {
...baseOptions.plugins,
tooltip: {
...baseOptions.plugins.tooltip,
callbacks: {
label(context) {
return `${context.dataset.label}: (${context.parsed.x}, ${context.parsed.y}), Size: ${context.parsed._custom}`;
}
}
}
}
};
case 'scatter':
return {
...baseOptions,
scales: {
x: {
type: 'linear',
position: 'bottom',
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
},
y: {
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
}
}
};
default:
// For line, bar, area, mixed charts
return {
...baseOptions,
scales: {
x: {
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5]
},
ticks: {
font: {
size: 11
}
}
}
}
};
}
}
/**
* Create tristate chart (for .sparktri elements)
*/
createTristateChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data: data.map(val => Math.abs(val)),
backgroundColor: data.map(val => {
if (val > 0) return COLORS['light-blue-500'];
if (val < 0) return '#f90';
return '#000';
}),
borderColor: data.map(val => {
if (val > 0) return COLORS['light-blue-500'];
if (val < 0) return '#f90';
return '#000';
}),
borderWidth: 1,
barPercentage: 0.8
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
callbacks: {
label: context => `${context.parsed.y}°Celsius`
}
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create discrete chart (for .sparkdisc elements)
*/
createDiscreteChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
data: data.map((val, index) => ({
x: index,
y: val
})),
backgroundColor: '#9f0',
borderColor: '#9f0',
pointRadius: 2,
showLine: false
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
callbacks: {
label: context => `${context.parsed.y}°Celsius`
}
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create bullet chart (for .sparkbull elements)
*/
createBulletChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
// Simplified bullet chart as horizontal bar
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: [''],
datasets: [{
data: [Math.max(...data)],
backgroundColor: COLORS['amber-500'],
borderColor: COLORS['amber-500'],
borderWidth: 1,
barPercentage: 0.6
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
callbacks: {
label: context => `${context.parsed.x}°Celsius`
}
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create box chart (for .sparkbox elements)
*/
createBoxChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
// Box plot simplified as bar chart showing quartiles
const sortedData = [...data].sort((a, b) => a - b);
const q1 = sortedData[Math.floor(sortedData.length * 0.25)];
const median = sortedData[Math.floor(sortedData.length * 0.5)];
const q3 = sortedData[Math.floor(sortedData.length * 0.75)];
const chart = new chart_Chart(ctx, {
type: 'bar',
data: {
labels: ['Q1', 'Med', 'Q3'],
datasets: [{
data: [q1, median, q3],
backgroundColor: '#9f0',
borderColor: '#9f0',
borderWidth: 1,
barPercentage: 0.8
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
callbacks: {
label: context => `${context.parsed.y}°Celsius`
}
}
}
}
});
this.charts.set(id, chart);
}
/**
* Create Easy Pie Charts (replaces jQuery Easy Pie Chart)
*/
createEasyPieCharts() {
const easyPieElements = document.querySelectorAll('.easy-pie-chart');
easyPieElements.forEach((element, index) => {
const size = parseInt(element.dataset.size) || 80;
const percent = parseInt(element.dataset.percent) || 0;
const barColor = element.dataset.barColor || '#f44336';
// Create canvas for the pie chart
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
canvas.style.width = `${size}px`;
canvas.style.height = `${size}px`;
element.appendChild(canvas);
}
// Create percentage display
const percentDisplay = element.querySelector('span');
if (percentDisplay) {
percentDisplay.textContent = `${percent}%`;
percentDisplay.style.position = 'absolute';
percentDisplay.style.top = '50%';
percentDisplay.style.left = '50%';
percentDisplay.style.transform = 'translate(-50%, -50%)';
percentDisplay.style.fontSize = '14px';
percentDisplay.style.fontWeight = 'bold';
}
// Set element position to relative for absolute positioning of text
element.style.position = 'relative';
element.style.display = 'inline-block';
const ctx = canvas.getContext('2d');
const chart = new chart_Chart(ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [percent, 100 - percent],
backgroundColor: [barColor, '#f0f0f0'],
borderWidth: 0,
cutout: '70%'
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
}
}
});
this.charts.set(`easy-pie-${index}`, chart);
});
}
/**
* Destroy all charts
*/
destroy() {
this.charts.forEach(chart => {
chart.destroy();
});
this.charts.clear();
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
}
/* harmony default export */ const components_Chart = (ChartComponent);
;// ../../node_modules/preact/dist/preact.module.js
var n,l,u,i,t,r,o,f,e,c={},s=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function h(n,l){for(var u in l)n[u]=l[u];return n}function v(n){var l=n.parentNode;l&&l.removeChild(n)}function y(l,u,i){var t,r,o,f={};for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===f[o]&&(f[o]=l.defaultProps[o]);return p(l,f,t,r,null)}function p(n,i,t,r,o){var f={type:n,props:i,key:t,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++u:o};return null==o&&null!=l.vnode&&l.vnode(f),f}function d(){return{current:null}}function _(n){return n.children}function k(n,l,u,i,t){var r;for(r in u)"children"===r||"key"===r||r in l||g(n,r,null,u[r],i);for(r in l)t&&"function"!=typeof l[r]||"children"===r||"key"===r||"value"===r||"checked"===r||u[r]===l[r]||g(n,r,l[r],u[r],i)}function b(n,l,u){"-"===l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||a.test(l)?u:u+"px"}function g(n,l,u,i,t){var r;n:if("style"===l)if("string"==typeof u)n.style.cssText=u;else{if("string"==typeof i&&(n.style.cssText=i=""),i)for(l in i)u&&l in u||b(n.style,l,"");if(u)for(l in u)i&&u[l]===i[l]||b(n.style,l,u[l])}else if("o"===l[0]&&"n"===l[1])r=l!==(l=l.replace(/Capture$/,"")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?i||n.addEventListener(l,r?w:m,r):n.removeEventListener(l,r?w:m,r);else if("dangerouslySetInnerHTML"!==l){if(t)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==l&&"height"!==l&&"href"!==l&&"list"!==l&&"form"!==l&&"tabIndex"!==l&&"download"!==l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&-1==l.indexOf("-")?n.removeAttribute(l):n.setAttribute(l,u))}}function m(n){t=!0;try{return this.l[n.type+!1](l.event?l.event(n):n)}finally{t=!1}}function w(n){t=!0;try{return this.l[n.type+!0](l.event?l.event(n):n)}finally{t=!1}}function x(n,l){this.props=n,this.context=l}function A(n,l){if(null==l)return n.__?A(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?A(n):null}function P(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return P(n)}}function C(n){t?setTimeout(n):f(n)}function T(n){(!n.__d&&(n.__d=!0)&&r.push(n)&&!$.__r++||o!==l.debounceRendering)&&((o=l.debounceRendering)||C)($)}function $(){var n,l,u,i,t,o,f,e;for(r.sort(function(n,l){return n.__v.__b-l.__v.__b});n=r.shift();)n.__d&&(l=r.length,i=void 0,t=void 0,f=(o=(u=n).__v).__e,(e=u.__P)&&(i=[],(t=h({},o)).__v=o.__v+1,M(e,o,t,u.__n,void 0!==e.ownerSVGElement,null!=o.__h?[f]:null,i,null==f?A(o):f,o.__h),N(i,o),o.__e!=f&&P(o)),r.length>l&&r.sort(function(n,l){return n.__v.__b-l.__v.__b}));$.__r=0}function H(n,l,u,i,t,r,o,f,e,a){var h,v,y,d,k,b,g,m=i&&i.__k||s,w=m.length;for(u.__k=[],h=0;h<l.length;h++)if(null!=(d=u.__k[h]=null==(d=l[h])||"boolean"==typeof d?null:"string"==typeof d||"number"==typeof d||"bigint"==typeof d?p(null,d,null,null,d):Array.isArray(d)?p(_,{children:d},null,null,null):d.__b>0?p(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d)){if(d.__=u,d.__b=u.__b+1,null===(y=m[h])||y&&d.key==y.key&&d.type===y.type)m[h]=void 0;else for(v=0;v<w;v++){if((y=m[v])&&d.key==y.key&&d.type===y.type){m[v]=void 0;break}y=null}M(n,d,y=y||c,t,r,o,f,e,a),k=d.__e,(v=d.ref)&&y.ref!=v&&(g||(g=[]),y.ref&&g.push(y.ref,null,d),g.push(v,d.__c||k,d)),null!=k?(null==b&&(b=k),"function"==typeof d.type&&d.__k===y.__k?d.__d=e=I(d,e,n):e=z(n,d,y,m,k,e),"function"==typeof u.type&&(u.__d=e)):e&&y.__e==e&&e.parentNode!=n&&(e=A(y))}for(u.__e=b,h=w;h--;)null!=m[h]&&("function"==typeof u.type&&null!=m[h].__e&&m[h].__e==u.__d&&(u.__d=L(i).nextSibling),q(m[h],m[h]));if(g)for(h=0;h<g.length;h++)S(g[h],g[++h],g[++h])}function I(n,l,u){for(var i,t=n.__k,r=0;t&&r<t.length;r++)(i=t[r])&&(i.__=n,l="function"==typeof i.type?I(i,l,u):z(u,i,i,t,i.__e,l));return l}function j(n,l){return l=l||[],null==n||"boolean"==typeof n||(Array.isArray(n)?n.some(function(n){j(n,l)}):l.push(n)),l}function z(n,l,u,i,t,r){var o,f,e;if(void 0!==l.__d)o=l.__d,l.__d=void 0;else if(null==u||t!=r||null==t.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(t),o=null;else{for(f=r,e=0;(f=f.nextSibling)&&e<i.length;e+=1)if(f==t)break n;n.insertBefore(t,r),o=r}return void 0!==o?o:t.nextSibling}function L(n){var l,u,i;if(null==n.type||"string"==typeof n.type)return n.__e;if(n.__k)for(l=n.__k.length-1;l>=0;l--)if((u=n.__k[l])&&(i=L(u)))return i;return null}function M(n,u,i,t,r,o,f,e,c){var s,a,v,y,p,d,k,b,g,m,w,A,P,C,T,$=u.type;if(void 0!==u.constructor)return null;null!=i.__h&&(c=i.__h,e=u.__e=i.__e,u.__h=null,o=[e]),(s=l.__b)&&s(u);try{n:if("function"==typeof $){if(b=u.props,g=(s=$.contextType)&&t[s.__c],m=s?g?g.props.value:s.__:t,i.__c?k=(a=u.__c=i.__c).__=a.__E:("prototype"in $&&$.prototype.render?u.__c=a=new $(b,m):(u.__c=a=new x(b,m),a.constructor=$,a.render=B),g&&g.sub(a),a.props=b,a.state||(a.state={}),a.context=m,a.__n=t,v=a.__d=!0,a.__h=[],a._sb=[]),null==a.__s&&(a.__s=a.state),null!=$.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=h({},a.__s)),h(a.__s,$.getDerivedStateFromProps(b,a.__s))),y=a.props,p=a.state,a.__v=u,v)null==$.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==$.getDerivedStateFromProps&&b!==y&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,m),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldComponentUpdate(b,a.__s,m)||u.__v===i.__v){for(u.__v!==i.__v&&(a.props=b,a.state=a.__s,a.__d=!1),u.__e=i.__e,u.__k=i.__k,u.__k.forEach(function(n){n&&(n.__=u)}),w=0;w<a._sb.length;w++)a.__h.push(a._sb[w]);a._sb=[],a.__h.length&&f.push(a);break n}null!=a.componentWillUpdate&&a.componentWillUpdate(b,a.__s,m),null!=a.componentDidUpdate&&a.__h.push(function(){a.componentDidUpdate(y,p,d)})}if(a.context=m,a.props=b,a.__P=n,A=l.__r,P=0,"prototype"in $&&$.prototype.render){for(a.state=a.__s,a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),C=0;C<a._sb.length;C++)a.__h.push(a._sb[C]);a._sb=[]}else do{a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),a.state=a.__s}while(a.__d&&++P<25);a.state=a.__s,null!=a.getChildContext&&(t=h(h({},t),a.getChildContext())),v||null==a.getSnapshotBeforeUpdate||(d=a.getSnapshotBeforeUpdate(y,p)),T=null!=s&&s.type===_&&null==s.key?s.props.children:s,H(n,Array.isArray(T)?T:[T],u,i,t,r,o,f,e,c),a.base=u.__e,u.__h=null,a.__h.length&&f.push(a),k&&(a.__E=a.__=null),a.__e=!1}else null==o&&u.__v===i.__v?(u.__k=i.__k,u.__e=i.__e):u.__e=O(i.__e,u,i,t,r,o,f,c);(s=l.diffed)&&s(u)}catch(n){u.__v=null,(c||null!=o)&&(u.__e=e,u.__h=!!c,o[o.indexOf(e)]=null),l.__e(n,u,i)}}function N(n,u){l.__c&&l.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u)})}catch(n){l.__e(n,u.__v)}})}function O(l,u,i,t,r,o,f,e){var s,a,h,y=i.props,p=u.props,d=u.type,_=0;if("svg"===d&&(r=!0),null!=o)for(;_<o.length;_++)if((s=o[_])&&"setAttribute"in s==!!d&&(d?s.localName===d:3===s.nodeType)){l=s,o[_]=null;break}if(null==l){if(null===d)return document.createTextNode(p);l=r?document.createElementNS("http://www.w3.org/2000/svg",d):document.createElement(d,p.is&&p),o=null,e=!1}if(null===d)y===p||e&&l.data===p||(l.data=p);else{if(o=o&&n.call(l.childNodes),a=(y=i.props||c).dangerouslySetInnerHTML,h=p.dangerouslySetInnerHTML,!e){if(null!=o)for(y={},_=0;_<l.attributes.length;_++)y[l.attributes[_].name]=l.attributes[_].value;(h||a)&&(h&&(a&&h.__html==a.__html||h.__html===l.innerHTML)||(l.innerHTML=h&&h.__html||""))}if(k(l,p,y,r,e),h)u.__k=[];else if(_=u.props.children,H(l,Array.isArray(_)?_:[_],u,i,t,r&&"foreignObject"!==d,o,f,o?o[0]:i.__k&&A(i,0),e),null!=o)for(_=o.length;_--;)null!=o[_]&&v(o[_]);e||("value"in p&&void 0!==(_=p.value)&&(_!==l.value||"progress"===d&&!_||"option"===d&&_!==y.value)&&g(l,"value",_,y.value,!1),"checked"in p&&void 0!==(_=p.checked)&&_!==l.checked&&g(l,"checked",_,y.checked,!1))}return l}function S(n,u,i){try{"function"==typeof n?n(u):n.current=u}catch(n){l.__e(n,i)}}function q(n,u,i){var t,r;if(l.unmount&&l.unmount(n),(t=n.ref)&&(t.current&&t.current!==n.__e||S(t,null,u)),null!=(t=n.__c)){if(t.componentWillUnmount)try{t.componentWillUnmount()}catch(n){l.__e(n,u)}t.base=t.__P=null,n.__c=void 0}if(t=n.__k)for(r=0;r<t.length;r++)t[r]&&q(t[r],u,i||"function"!=typeof n.type);i||null==n.__e||v(n.__e),n.__=n.__e=n.__d=void 0}function B(n,l,u){return this.constructor(n,u)}function D(u,i,t){var r,o,f;l.__&&l.__(u,i),o=(r="function"==typeof t)?null:t&&t.__k||i.__k,f=[],M(i,u=(!r&&t||i).__k=y(_,null,[u]),o||c,c,void 0!==i.ownerSVGElement,!r&&t?[t]:o?null:i.firstChild?n.call(i.childNodes):null,f,!r&&t?t:o?o.__e:i.firstChild,r),N(f,u)}function E(n,l){D(n,l,E)}function F(l,u,i){var t,r,o,f=h({},l.props);for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];return arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),p(l.type,f,t||l.key,r||l.ref,null)}function G(n,l){var u={__c:l="__cC"+e++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,i;return this.getChildContext||(u=[],(i={})[l]=this,this.getChildContext=function(){return i},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(function(n){n.__e=!0,T(n)})},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Provider.__=u.Consumer.contextType=u}n=s.slice,l={__e:function(n,l,u,i){for(var t,r,o;l=l.__;)if((t=l.__c)&&!t.__)try{if((r=t.constructor)&&null!=r.getDerivedStateFromError&&(t.setState(r.getDerivedStateFromError(n)),o=t.__d),null!=t.componentDidCatch&&(t.componentDidCatch(n,i||{}),o=t.__d),o)return t.__E=t}catch(l){n=l}throw n}},u=0,i=function(n){return null!=n&&void 0===n.constructor},t=!1,x.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=h({},this.state),"function"==typeof n&&(n=n(h({},u),this.props)),n&&h(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),T(this))},x.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),T(this))},x.prototype.render=_,r=[],f="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,$.__r=0,e=0;
//# sourceMappingURL=preact.module.js.map
;// ../../node_modules/preact/hooks/dist/hooks.module.js
var hooks_module_t,hooks_module_r,hooks_module_u,hooks_module_i,hooks_module_o=0,hooks_module_f=[],hooks_module_c=[],hooks_module_e=l.__b,hooks_module_a=l.__r,hooks_module_v=l.diffed,hooks_module_l=l.__c,hooks_module_m=l.unmount;function hooks_module_d(t,u){l.__h&&l.__h(hooks_module_r,t,hooks_module_o||u),hooks_module_o=0;var i=hooks_module_r.__H||(hooks_module_r.__H={__:[],__h:[]});return t>=i.__.length&&i.__.push({__V:hooks_module_c}),i.__[t]}function hooks_module_p(n){return hooks_module_o=1,hooks_module_y(hooks_module_B,n)}function hooks_module_y(n,u,i){var o=hooks_module_d(hooks_module_t++,2);if(o.t=n,!o.__c&&(o.__=[i?i(u):hooks_module_B(void 0,u),function(n){var t=o.__N?o.__N[0]:o.__[0],r=o.t(t,n);t!==r&&(o.__N=[r,o.__[1]],o.__c.setState({}))}],o.__c=hooks_module_r,!hooks_module_r.u)){hooks_module_r.u=!0;var f=hooks_module_r.shouldComponentUpdate;hooks_module_r.shouldComponentUpdate=function(n,t,r){if(!o.__c.__H)return!0;var u=o.__c.__H.__.filter(function(n){return n.__c});if(u.every(function(n){return!n.__N}))return!f||f.call(this,n,t,r);var i=!1;return u.forEach(function(n){if(n.__N){var t=n.__[0];n.__=n.__N,n.__N=void 0,t!==n.__[0]&&(i=!0)}}),!(!i&&o.__c.props===n)&&(!f||f.call(this,n,t,r))}}return o.__N||o.__}function hooks_module_h(u,i){var o=hooks_module_d(hooks_module_t++,3);!l.__s&&hooks_module_z(o.__H,i)&&(o.__=u,o.i=i,hooks_module_r.__H.__h.push(o))}function hooks_module_s(u,i){var o=hooks_module_d(hooks_module_t++,4);!l.__s&&hooks_module_z(o.__H,i)&&(o.__=u,o.i=i,hooks_module_r.__h.push(o))}function hooks_module_(n){return hooks_module_o=5,hooks_module_F(function(){return{current:n}},[])}function hooks_module_A(n,t,r){hooks_module_o=6,hooks_module_s(function(){return"function"==typeof n?(n(t()),function(){return n(null)}):n?(n.current=t(),function(){return n.current=null}):void 0},null==r?r:r.concat(n))}function hooks_module_F(n,r){var u=hooks_module_d(hooks_module_t++,7);return hooks_module_z(u.__H,r)?(u.__V=n(),u.i=r,u.__h=n,u.__V):u.__}function hooks_module_T(n,t){return hooks_module_o=8,hooks_module_F(function(){return n},t)}function hooks_module_q(n){var u=hooks_module_r.context[n.__c],i=hooks_module_d(hooks_module_t++,9);return i.c=n,u?(null==i.__&&(i.__=!0,u.sub(hooks_module_r)),u.props.value):n.__}function hooks_module_x(t,r){l.useDebugValue&&l.useDebugValue(r?r(t):t)}function hooks_module_P(n){var u=hooks_module_d(hooks_module_t++,10),i=hooks_module_p();return u.__=n,hooks_module_r.componentDidCatch||(hooks_module_r.componentDidCatch=function(n,t){u.__&&u.__(n,t),i[1](n)}),[i[0],function(){i[1](void 0)}]}function V(){var n=hooks_module_d(hooks_module_t++,11);if(!n.__){for(var u=hooks_module_r.__v;null!==u&&!u.__m&&null!==u.__;)u=u.__;var i=u.__m||(u.__m=[0,0]);n.__="P"+i[0]+"-"+i[1]++}return n.__}function hooks_module_b(){for(var t;t=hooks_module_f.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(hooks_module_k),t.__H.__h.forEach(hooks_module_w),t.__H.__h=[]}catch(r){t.__H.__h=[],l.__e(r,t.__v)}}l.__b=function(n){hooks_module_r=null,hooks_module_e&&hooks_module_e(n)},l.__r=function(n){hooks_module_a&&hooks_module_a(n),hooks_module_t=0;var i=(hooks_module_r=n.__c).__H;i&&(hooks_module_u===hooks_module_r?(i.__h=[],hooks_module_r.__h=[],i.__.forEach(function(n){n.__N&&(n.__=n.__N),n.__V=hooks_module_c,n.__N=n.i=void 0})):(i.__h.forEach(hooks_module_k),i.__h.forEach(hooks_module_w),i.__h=[])),hooks_module_u=hooks_module_r},l.diffed=function(t){hooks_module_v&&hooks_module_v(t);var o=t.__c;o&&o.__H&&(o.__H.__h.length&&(1!==hooks_module_f.push(o)&&hooks_module_i===l.requestAnimationFrame||((hooks_module_i=l.requestAnimationFrame)||hooks_module_j)(hooks_module_b)),o.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.__V!==hooks_module_c&&(n.__=n.__V),n.i=void 0,n.__V=hooks_module_c})),hooks_module_u=hooks_module_r=null},l.__c=function(t,r){r.some(function(t){try{t.__h.forEach(hooks_module_k),t.__h=t.__h.filter(function(n){return!n.__||hooks_module_w(n)})}catch(u){r.some(function(n){n.__h&&(n.__h=[])}),r=[],l.__e(u,t.__v)}}),hooks_module_l&&hooks_module_l(t,r)},l.unmount=function(t){hooks_module_m&&hooks_module_m(t);var r,u=t.__c;u&&u.__H&&(u.__H.__.forEach(function(n){try{hooks_module_k(n)}catch(n){r=n}}),u.__H=void 0,r&&l.__e(r,u.__v))};var hooks_module_g="function"==typeof requestAnimationFrame;function hooks_module_j(n){var t,r=function(){clearTimeout(u),hooks_module_g&&cancelAnimationFrame(t),setTimeout(n)},u=setTimeout(r,100);hooks_module_g&&(t=requestAnimationFrame(r))}function hooks_module_k(n){var t=hooks_module_r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),hooks_module_r=t}function hooks_module_w(n){var t=hooks_module_r;n.__c=n.__(),hooks_module_r=t}function hooks_module_z(n,t){return!n||n.length!==t.length||t.some(function(t,r){return t!==n[r]})}function hooks_module_B(n,t){return"function"==typeof t?t(n):t}
//# sourceMappingURL=hooks.module.js.map
;// ../../node_modules/preact/compat/dist/compat.module.js
function compat_module_g(n,t){for(var e in t)n[e]=t[e];return n}function compat_module_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 compat_module_E(n,t){return n===t&&(0!==n||1/n==1/t)||n!=n&&t!=t}function compat_module_w(n){this.props=n}function R(n,e){function r(n){var t=this.props.ref,r=t==n.ref;return!r&&t&&(t.call?t(null):t.current=null),e?!e(this.props,n)||!r:compat_module_C(this.props,n)}function u(e){return this.shouldComponentUpdate=r,y(n,e)}return u.displayName="Memo("+(n.displayName||n.name)+")",u.prototype.isReactComponent=!0,u.__f=!0,u}(compat_module_w.prototype=new x).isPureReactComponent=!0,compat_module_w.prototype.shouldComponentUpdate=function(n,t){return compat_module_C(this.props,n)||compat_module_C(this.state,t)};var compat_module_x=l.__b;l.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),compat_module_x&&compat_module_x(n)};var compat_module_N="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.forward_ref")||3911;function compat_module_k(n){function t(t){var e=compat_module_g({},t);return delete e.ref,n(e,t.ref||null)}return t.$$typeof=compat_module_N,t.render=t,t.prototype.isReactComponent=t.__f=!0,t.displayName="ForwardRef("+(n.displayName||n.name)+")",t}var compat_module_A=function(n,t){return null==n?null:j(j(n).map(t))},compat_module_O={map:compat_module_A,forEach:compat_module_A,count:function(n){return n?j(n).length:0},only:function(n){var t=j(n);if(1!==t.length)throw"Children.only";return t[0]},toArray:j},compat_module_T=l.__e;l.__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);compat_module_T(n,t,e,r)};var compat_module_I=l.unmount;function compat_module_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=compat_module_g({},n)).__c&&(n.__c.__P===e&&(n.__c.__P=t),n.__c=null),n.__k=n.__k&&n.__k.map(function(n){return compat_module_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 compat_module_D(){this.__u=0,this.t=null,this.__b=null}function compat_module_F(n){var t=n.__.__c;return t&&t.__a&&t.__a(n)}function compat_module_M(n){var e,r,u;function o(o){if(e||(e=n()).then(function(n){r=n.default||n},function(n){u=n}),u)throw u;if(!r)throw e;return y(r,o)}return o.displayName="Lazy",o.__f=!0,o}function compat_module_V(){this.u=null,this.o=null}l.unmount=function(n){var t=n.__c;t&&t.__R&&t.__R(),t&&!0===n.__h&&(n.type=null),compat_module_I&&compat_module_I(n)},(compat_module_D.prototype=new x).__c=function(n,t){var e=t.__c,r=this;null==r.t&&(r.t=[]),r.t.push(e);var u=compat_module_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)},compat_module_D.prototype.componentWillUnmount=function(){this.t=[]},compat_module_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]=compat_module_L(this.__b,r,o.__O=o.__P)}this.__b=null}var i=e.__a&&y(_,null,n.fallback);return i&&(i.__h=null),[y(_,null,e.__a?null:n.children),i]};var W=function(n,t,e){if(++e[1]===e[0]&&n.o.delete(t),n.props.revealOrder&&("t"!==n.props.revealOrder[0]||!n.o.size))for(e=n.u;e;){for(;e.length>3;)e.pop()();if(e[1]<e[0])break;n.u=e=e[2]}};function compat_module_P(n){return this.getChildContext=function(){return n.context},n.children}function compat_module_$(n){var e=this,r=n.i;e.componentWillUnmount=function(){D(null,e.l),e.l=null,e.i=null},e.i&&e.i!==r&&e.componentWillUnmount(),n.__v?(e.l||(e.i=r,e.l={nodeType:1,parentNode:r,childNodes:[],appendChild:function(n){this.childNodes.push(n),e.i.appendChild(n)},insertBefore:function(n,t){this.childNodes.push(n),e.i.appendChild(n)},removeChild:function(n){this.childNodes.splice(this.childNodes.indexOf(n)>>>1,1),e.i.removeChild(n)}}),D(y(compat_module_P,{context:e.context},n.__v),e.l)):e.l&&e.componentWillUnmount()}function compat_module_j(n,e){var r=y(compat_module_$,{__v:n,i:e});return r.containerInfo=e,r}(compat_module_V.prototype=new x).__a=function(n){var t=this,e=compat_module_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()}},compat_module_V.prototype.render=function(n){this.u=null,this.o=new Map;var t=j(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},compat_module_V.prototype.componentDidUpdate=compat_module_V.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){W(n,e,t)})};var compat_module_z="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,compat_module_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]/,compat_module_H="undefined"!=typeof document,Z=function(n){return("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};function Y(n,t,e){return null==t.__k&&(t.textContent=""),D(n,t),"function"==typeof e&&e(),n?n.__c:null}function compat_module_q(n,t,e){return E(n,t),"function"==typeof e&&e(),n?n.__c:null}x.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(x.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(n){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:n})}})});var compat_module_G=l.event;function J(){}function K(){return this.cancelBubble}function Q(){return this.defaultPrevented}l.event=function(n){return compat_module_G&&(n=compat_module_G(n)),n.persist=J,n.isPropagationStopped=K,n.isDefaultPrevented=Q,n.nativeEvent=n};var X,nn={configurable:!0,get:function(){return this.class}},tn=l.vnode;l.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];compat_module_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&&compat_module_B.test(i)?i=i.replace(/[A-Z0-9]/g,"-$&").toLowerCase():null===l&&(l=void 0),/^oninput$/i.test(i)&&(i=i.toLowerCase(),u[i]&&(i="oninputCapture")),u[i]=l)}"select"==t&&u.multiple&&Array.isArray(u.value)&&(u.value=j(e.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value)})),"select"==t&&null!=u.defaultValue&&(u.value=j(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=compat_module_z,tn&&tn(n)};var en=l.__r;l.__r=function(n){en&&en(n),X=n.__c};var rn={ReactCurrentDispatcher:{current:{readContext:function(n){return X.__n[n.__c].props.value}}}},un="17.0.2";function on(n){return y.bind(null,n)}function ln(n){return!!n&&n.$$typeof===compat_module_z}function cn(n){return ln(n)?F.apply(null,arguments):n}function fn(n){return!!n.__k&&(D(null,n),!0)}function an(n){return n&&(n.base||1===n.nodeType&&n)||null}var sn=function(n,t){return n(t)},hn=function(n,t){return n(t)},vn=_;function dn(n){n()}function pn(n){return n}function mn(){return[!1,dn]}var yn=hooks_module_s;function _n(n,t){var e=t(),r=hooks_module_p({h:{__:e,v:t}}),u=r[0].h,o=r[1];return hooks_module_s(function(){u.__=e,u.v=t,compat_module_E(u.__,t())||o({h:u})},[n,e,t]),hooks_module_h(function(){return compat_module_E(u.__,u.v())||o({h:u}),n(function(){compat_module_E(u.__,u.v())||o({h:u})})},[n]),e}var bn={useState:hooks_module_p,useId:V,useReducer:hooks_module_y,useEffect:hooks_module_h,useLayoutEffect:hooks_module_s,useInsertionEffect:yn,useTransition:mn,useDeferredValue:pn,useSyncExternalStore:_n,startTransition:dn,useRef:hooks_module_,useImperativeHandle:hooks_module_A,useMemo:hooks_module_F,useCallback:hooks_module_T,useContext:hooks_module_q,useDebugValue:hooks_module_x,version:"17.0.2",Children:compat_module_O,render:Y,hydrate:compat_module_q,unmountComponentAtNode:fn,createPortal:compat_module_j,createElement:y,createContext:G,createFactory:on,cloneElement:cn,createRef:d,Fragment:_,isValidElement:ln,findDOMNode:an,Component:x,PureComponent:compat_module_w,memo:R,forwardRef:compat_module_k,flushSync:hn,unstable_batchedUpdates:sn,StrictMode:vn,Suspense:compat_module_D,SuspenseList:compat_module_V,lazy:compat_module_M,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:rn};
//# sourceMappingURL=compat.module.js.map
;// ../../node_modules/@fullcalendar/core/internal-common.js
const styleTexts = [];
const styleEls = new Map();
function injectStyles(styleText) {
styleTexts.push(styleText);
styleEls.forEach((styleEl) => {
appendStylesTo(styleEl, styleText);
});
}
function ensureElHasStyles(el) {
if (el.isConnected && // sometimes true if SSR system simulates DOM
el.getRootNode // sometimes undefined if SSR system simulates DOM
) {
registerStylesRoot(el.getRootNode());
}
}
function registerStylesRoot(rootNode) {
let styleEl = styleEls.get(rootNode);
if (!styleEl || !styleEl.isConnected) {
styleEl = rootNode.querySelector('style[data-fullcalendar]');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.setAttribute('data-fullcalendar', '');
const nonce = getNonceValue();
if (nonce) {
styleEl.nonce = nonce;
}
const parentEl = rootNode === document ? document.head : rootNode;
const insertBefore = rootNode === document
? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style')
: parentEl.firstChild;
parentEl.insertBefore(styleEl, insertBefore);
}
styleEls.set(rootNode, styleEl);
hydrateStylesRoot(styleEl);
}
}
function hydrateStylesRoot(styleEl) {
for (const styleText of styleTexts) {
appendStylesTo(styleEl, styleText);
}
}
function appendStylesTo(styleEl, styleText) {
const { sheet } = styleEl;
const ruleCnt = sheet.cssRules.length;
styleText.split('}').forEach((styleStr, i) => {
styleStr = styleStr.trim();
if (styleStr) {
sheet.insertRule(styleStr + '}', ruleCnt + i);
}
});
}
// nonce
// -------------------------------------------------------------------------------------------------
let queriedNonceValue;
function getNonceValue() {
if (queriedNonceValue === undefined) {
queriedNonceValue = queryNonceValue();
}
return queriedNonceValue;
}
/*
TODO: discourage meta tag and instead put nonce attribute on placeholder <style> tag
*/
function queryNonceValue() {
const metaWithNonce = document.querySelector('meta[name="csp-nonce"]');
if (metaWithNonce && metaWithNonce.hasAttribute('content')) {
return metaWithNonce.getAttribute('content');
}
const elWithNonce = document.querySelector('script[nonce]');
if (elWithNonce) {
return elWithNonce.nonce || '';
}
return '';
}
// main
// -------------------------------------------------------------------------------------------------
if (typeof document !== 'undefined') {
registerStylesRoot(document);
}
var css_248z = ":root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url(\"data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}";
injectStyles(css_248z);
class DelayedRunner {
constructor(drainedOption) {
this.drainedOption = drainedOption;
this.isRunning = false;
this.isDirty = false;
this.pauseDepths = {};
this.timeoutId = 0;
}
request(delay) {
this.isDirty = true;
if (!this.isPaused()) {
this.clearTimeout();
if (delay == null) {
this.tryDrain();
}
else {
this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce
this.tryDrain.bind(this), delay);
}
}
}
pause(scope = '') {
let { pauseDepths } = this;
pauseDepths[scope] = (pauseDepths[scope] || 0) + 1;
this.clearTimeout();
}
resume(scope = '', force) {
let { pauseDepths } = this;
if (scope in pauseDepths) {
if (force) {
delete pauseDepths[scope];
}
else {
pauseDepths[scope] -= 1;
let depth = pauseDepths[scope];
if (depth <= 0) {
delete pauseDepths[scope];
}
}
this.tryDrain();
}
}
isPaused() {
return Object.keys(this.pauseDepths).length;
}
tryDrain() {
if (!this.isRunning && !this.isPaused()) {
this.isRunning = true;
while (this.isDirty) {
this.isDirty = false;
this.drained(); // might set isDirty to true again
}
this.isRunning = false;
}
}
clear() {
this.clearTimeout();
this.isDirty = false;
this.pauseDepths = {};
}
clearTimeout() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = 0;
}
}
drained() {
if (this.drainedOption) {
this.drainedOption();
}
}
}
function removeElement(el) {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
}
// Querying
// ----------------------------------------------------------------------------------------------------------------
function elementClosest(el, selector) {
if (el.closest) {
return el.closest(selector);
// really bad fallback for IE
// from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
}
if (!document.documentElement.contains(el)) {
return null;
}
do {
if (elementMatches(el, selector)) {
return el;
}
el = (el.parentElement || el.parentNode);
} while (el !== null && el.nodeType === 1);
return null;
}
function elementMatches(el, selector) {
let method = el.matches || el.matchesSelector || el.msMatchesSelector;
return method.call(el, selector);
}
// accepts multiple subject els
// returns a real array. good for methods like forEach
// TODO: accept the document
function findElements(container, selector) {
let containers = container instanceof HTMLElement ? [container] : container;
let allMatches = [];
for (let i = 0; i < containers.length; i += 1) {
let matches = containers[i].querySelectorAll(selector);
for (let j = 0; j < matches.length; j += 1) {
allMatches.push(matches[j]);
}
}
return allMatches;
}
// accepts multiple subject els
// only queries direct child elements // TODO: rename to findDirectChildren!
function findDirectChildren(parent, selector) {
let parents = parent instanceof HTMLElement ? [parent] : parent;
let allMatches = [];
for (let i = 0; i < parents.length; i += 1) {
let childNodes = parents[i].children; // only ever elements
for (let j = 0; j < childNodes.length; j += 1) {
let childNode = childNodes[j];
if (!selector || elementMatches(childNode, selector)) {
allMatches.push(childNode);
}
}
}
return allMatches;
}
// Style
// ----------------------------------------------------------------------------------------------------------------
const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i;
function applyStyle(el, props) {
for (let propName in props) {
applyStyleProp(el, propName, props[propName]);
}
}
function applyStyleProp(el, name, val) {
if (val == null) {
el.style[name] = '';
}
else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) {
el.style[name] = `${val}px`;
}
else {
el.style[name] = val;
}
}
// Event Handling
// ----------------------------------------------------------------------------------------------------------------
// if intercepting bubbled events at the document/window/body level,
// and want to see originating element (the 'target'), use this util instead
// of `ev.target` because it goes within web-component boundaries.
function getEventTargetViaRoot(ev) {
var _a, _b;
return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target;
}
// Unique ID for DOM attribute
let guid$1 = 0;
function getUniqueDomId() {
guid$1 += 1;
return 'fc-dom-' + guid$1;
}
// Stops a mouse/touch event from doing it's native browser action
function preventDefault(ev) {
ev.preventDefault();
}
// Event Delegation
// ----------------------------------------------------------------------------------------------------------------
function buildDelegationHandler(selector, handler) {
return (ev) => {
let matchedChild = elementClosest(ev.target, selector);
if (matchedChild) {
handler.call(matchedChild, ev, matchedChild);
}
};
}
function listenBySelector(container, eventType, selector, handler) {
let attachedHandler = buildDelegationHandler(selector, handler);
container.addEventListener(eventType, attachedHandler);
return () => {
container.removeEventListener(eventType, attachedHandler);
};
}
function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) {
let currentMatchedChild;
return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => {
if (matchedChild !== currentMatchedChild) {
currentMatchedChild = matchedChild;
onMouseEnter(mouseOverEv, matchedChild);
let realOnMouseLeave = (mouseLeaveEv) => {
currentMatchedChild = null;
onMouseLeave(mouseLeaveEv, matchedChild);
matchedChild.removeEventListener('mouseleave', realOnMouseLeave);
};
// listen to the next mouseleave, and then unattach
matchedChild.addEventListener('mouseleave', realOnMouseLeave);
}
});
}
// Animation
// ----------------------------------------------------------------------------------------------------------------
const transitionEventNames = [
'webkitTransitionEnd',
'otransitionend',
'oTransitionEnd',
'msTransitionEnd',
'transitionend',
];
// triggered only when the next single subsequent transition finishes
function whenTransitionDone(el, callback) {
let realCallback = (ev) => {
callback(ev);
transitionEventNames.forEach((eventName) => {
el.removeEventListener(eventName, realCallback);
});
};
transitionEventNames.forEach((eventName) => {
el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes
});
}
// ARIA workarounds
// ----------------------------------------------------------------------------------------------------------------
function createAriaClickAttrs(handler) {
return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler));
}
function createAriaKeyboardAttrs(handler) {
return {
tabIndex: 0,
onKeyDown(ev) {
if (ev.key === 'Enter' || ev.key === ' ') {
handler(ev);
ev.preventDefault(); // if space, don't scroll down page
}
},
};
}
let guidNumber = 0;
function guid() {
guidNumber += 1;
return String(guidNumber);
}
/* FullCalendar-specific DOM Utilities
----------------------------------------------------------------------------------------------------------------------*/
// Make the mouse cursor express that an event is not allowed in the current area
function disableCursor() {
document.body.classList.add('fc-not-allowed');
}
// Returns the mouse cursor to its original look
function enableCursor() {
document.body.classList.remove('fc-not-allowed');
}
/* Selection
----------------------------------------------------------------------------------------------------------------------*/
function preventSelection(el) {
el.style.userSelect = 'none';
el.style.webkitUserSelect = 'none';
el.addEventListener('selectstart', preventDefault);
}
function allowSelection(el) {
el.style.userSelect = '';
el.style.webkitUserSelect = '';
el.removeEventListener('selectstart', preventDefault);
}
/* Context Menu
----------------------------------------------------------------------------------------------------------------------*/
function preventContextMenu(el) {
el.addEventListener('contextmenu', preventDefault);
}
function allowContextMenu(el) {
el.removeEventListener('contextmenu', preventDefault);
}
function parseFieldSpecs(input) {
let specs = [];
let tokens = [];
let i;
let token;
if (typeof input === 'string') {
tokens = input.split(/\s*,\s*/);
}
else if (typeof input === 'function') {
tokens = [input];
}
else if (Array.isArray(input)) {
tokens = input;
}
for (i = 0; i < tokens.length; i += 1) {
token = tokens[i];
if (typeof token === 'string') {
specs.push(token.charAt(0) === '-' ?
{ field: token.substring(1), order: -1 } :
{ field: token, order: 1 });
}
else if (typeof token === 'function') {
specs.push({ func: token });
}
}
return specs;
}
function compareByFieldSpecs(obj0, obj1, fieldSpecs) {
let i;
let cmp;
for (i = 0; i < fieldSpecs.length; i += 1) {
cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]);
if (cmp) {
return cmp;
}
}
return 0;
}
function compareByFieldSpec(obj0, obj1, fieldSpec) {
if (fieldSpec.func) {
return fieldSpec.func(obj0, obj1);
}
return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field])
* (fieldSpec.order || 1);
}
function flexibleCompare(a, b) {
if (!a && !b) {
return 0;
}
if (b == null) {
return -1;
}
if (a == null) {
return 1;
}
if (typeof a === 'string' || typeof b === 'string') {
return String(a).localeCompare(String(b));
}
return a - b;
}
/* String Utilities
----------------------------------------------------------------------------------------------------------------------*/
function padStart(val, len) {
let s = String(val);
return '000'.substr(0, len - s.length) + s;
}
function formatWithOrdinals(formatter, args, fallbackText) {
if (typeof formatter === 'function') {
return formatter(...args);
}
if (typeof formatter === 'string') { // non-blank string
return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter);
}
return fallbackText;
}
/* Number Utilities
----------------------------------------------------------------------------------------------------------------------*/
function compareNumbers(a, b) {
return a - b;
}
function isInt(n) {
return n % 1 === 0;
}
/* FC-specific DOM dimension stuff
----------------------------------------------------------------------------------------------------------------------*/
function computeSmallestCellWidth(cellEl) {
let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame');
let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion');
if (!allWidthEl) {
throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const
}
if (!contentWidthEl) {
throw new Error('needs fc-scrollgrid-shrink-cushion className');
}
return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border
contentWidthEl.getBoundingClientRect().width;
}
const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds'];
const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/;
// Parsing and Creation
function createDuration(input, unit) {
if (typeof input === 'string') {
return parseString(input);
}
if (typeof input === 'object' && input) { // non-null object
return parseObject(input);
}
if (typeof input === 'number') {
return parseObject({ [unit || 'milliseconds']: input });
}
return null;
}
function parseString(s) {
let m = PARSE_RE.exec(s);
if (m) {
let sign = m[1] ? -1 : 1;
return {
years: 0,
months: 0,
days: sign * (m[2] ? parseInt(m[2], 10) : 0),
milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours
(m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes
(m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds
(m[6] ? parseInt(m[6], 10) : 0) // ms
),
};
}
return null;
}
function parseObject(obj) {
let duration = {
years: obj.years || obj.year || 0,
months: obj.months || obj.month || 0,
days: obj.days || obj.day || 0,
milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours
(obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes
(obj.seconds || obj.second || 0) * 1000 + // seconds
(obj.milliseconds || obj.millisecond || obj.ms || 0), // ms
};
let weeks = obj.weeks || obj.week;
if (weeks) {
duration.days += weeks * 7;
duration.specifiedWeeks = true;
}
return duration;
}
// Equality
function durationsEqual(d0, d1) {
return d0.years === d1.years &&
d0.months === d1.months &&
d0.days === d1.days &&
d0.milliseconds === d1.milliseconds;
}
function asCleanDays(dur) {
if (!dur.years && !dur.months && !dur.milliseconds) {
return dur.days;
}
return 0;
}
// Simple Math
function addDurations(d0, d1) {
return {
years: d0.years + d1.years,
months: d0.months + d1.months,
days: d0.days + d1.days,
milliseconds: d0.milliseconds + d1.milliseconds,
};
}
function subtractDurations(d1, d0) {
return {
years: d1.years - d0.years,
months: d1.months - d0.months,
days: d1.days - d0.days,
milliseconds: d1.milliseconds - d0.milliseconds,
};
}
function multiplyDuration(d, n) {
return {
years: d.years * n,
months: d.months * n,
days: d.days * n,
milliseconds: d.milliseconds * n,
};
}
// Conversions
// "Rough" because they are based on average-case Gregorian months/years
function asRoughYears(dur) {
return asRoughDays(dur) / 365;
}
function asRoughMonths(dur) {
return asRoughDays(dur) / 30;
}
function asRoughDays(dur) {
return asRoughMs(dur) / 864e5;
}
function asRoughMinutes(dur) {
return asRoughMs(dur) / (1000 * 60);
}
function asRoughSeconds(dur) {
return asRoughMs(dur) / 1000;
}
function asRoughMs(dur) {
return dur.years * (365 * 864e5) +
dur.months * (30 * 864e5) +
dur.days * 864e5 +
dur.milliseconds;
}
// Advanced Math
function wholeDivideDurations(numerator, denominator) {
let res = null;
for (let i = 0; i < INTERNAL_UNITS.length; i += 1) {
let unit = INTERNAL_UNITS[i];
if (denominator[unit]) {
let localRes = numerator[unit] / denominator[unit];
if (!isInt(localRes) || (res !== null && res !== localRes)) {
return null;
}
res = localRes;
}
else if (numerator[unit]) {
// needs to divide by something but can't!
return null;
}
}
return res;
}
function greatestDurationDenominator(dur) {
let ms = dur.milliseconds;
if (ms) {
if (ms % 1000 !== 0) {
return { unit: 'millisecond', value: ms };
}
if (ms % (1000 * 60) !== 0) {
return { unit: 'second', value: ms / 1000 };
}
if (ms % (1000 * 60 * 60) !== 0) {
return { unit: 'minute', value: ms / (1000 * 60) };
}
if (ms) {
return { unit: 'hour', value: ms / (1000 * 60 * 60) };
}
}
if (dur.days) {
if (dur.specifiedWeeks && dur.days % 7 === 0) {
return { unit: 'week', value: dur.days / 7 };
}
return { unit: 'day', value: dur.days };
}
if (dur.months) {
return { unit: 'month', value: dur.months };
}
if (dur.years) {
return { unit: 'year', value: dur.years };
}
return { unit: 'millisecond', value: 0 };
}
// TODO: new util arrayify?
function removeExact(array, exactVal) {
let removeCnt = 0;
let i = 0;
while (i < array.length) {
if (array[i] === exactVal) {
array.splice(i, 1);
removeCnt += 1;
}
else {
i += 1;
}
}
return removeCnt;
}
function isArraysEqual(a0, a1, equalityFunc) {
if (a0 === a1) {
return true;
}
let len = a0.length;
let i;
if (len !== a1.length) { // not array? or not same length?
return false;
}
for (i = 0; i < len; i += 1) {
if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) {
return false;
}
}
return true;
}
const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
// Adding
function addWeeks(m, n) {
let a = dateToUtcArray(m);
a[2] += n * 7;
return arrayToUtcDate(a);
}
function addDays(m, n) {
let a = dateToUtcArray(m);
a[2] += n;
return arrayToUtcDate(a);
}
function addMs(m, n) {
let a = dateToUtcArray(m);
a[6] += n;
return arrayToUtcDate(a);
}
// Diffing (all return floats)
// TODO: why not use ranges?
function diffWeeks(m0, m1) {
return diffDays(m0, m1) / 7;
}
function diffDays(m0, m1) {
return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24);
}
function diffHours(m0, m1) {
return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60);
}
function diffMinutes(m0, m1) {
return (m1.valueOf() - m0.valueOf()) / (1000 * 60);
}
function diffSeconds(m0, m1) {
return (m1.valueOf() - m0.valueOf()) / 1000;
}
function diffDayAndTime(m0, m1) {
let m0day = startOfDay(m0);
let m1day = startOfDay(m1);
return {
years: 0,
months: 0,
days: Math.round(diffDays(m0day, m1day)),
milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()),
};
}
// Diffing Whole Units
function diffWholeWeeks(m0, m1) {
let d = diffWholeDays(m0, m1);
if (d !== null && d % 7 === 0) {
return d / 7;
}
return null;
}
function diffWholeDays(m0, m1) {
if (timeAsMs(m0) === timeAsMs(m1)) {
return Math.round(diffDays(m0, m1));
}
return null;
}
// Start-Of
function startOfDay(m) {
return arrayToUtcDate([
m.getUTCFullYear(),
m.getUTCMonth(),
m.getUTCDate(),
]);
}
function startOfHour(m) {
return arrayToUtcDate([
m.getUTCFullYear(),
m.getUTCMonth(),
m.getUTCDate(),
m.getUTCHours(),
]);
}
function startOfMinute(m) {
return arrayToUtcDate([
m.getUTCFullYear(),
m.getUTCMonth(),
m.getUTCDate(),
m.getUTCHours(),
m.getUTCMinutes(),
]);
}
function startOfSecond(m) {
return arrayToUtcDate([
m.getUTCFullYear(),
m.getUTCMonth(),
m.getUTCDate(),
m.getUTCHours(),
m.getUTCMinutes(),
m.getUTCSeconds(),
]);
}
// Week Computation
function weekOfYear(marker, dow, doy) {
let y = marker.getUTCFullYear();
let w = weekOfGivenYear(marker, y, dow, doy);
if (w < 1) {
return weekOfGivenYear(marker, y - 1, dow, doy);
}
let nextW = weekOfGivenYear(marker, y + 1, dow, doy);
if (nextW >= 1) {
return Math.min(w, nextW);
}
return w;
}
function weekOfGivenYear(marker, year, dow, doy) {
let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]);
let dayStart = startOfDay(marker);
let days = Math.round(diffDays(firstWeekStart, dayStart));
return Math.floor(days / 7) + 1; // zero-indexed
}
// start-of-first-week - start-of-year
function firstWeekOffset(year, dow, doy) {
// first-week day -- which january is always in the first week (4 for iso, 1 for other)
let fwd = 7 + dow - doy;
// first-week day local weekday -- which local weekday is fwd
let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7;
return -fwdlw + fwd - 1;
}
// Array Conversion
function dateToLocalArray(date) {
return [
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
];
}
function arrayToLocalDate(a) {
return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month
a[3] || 0, a[4] || 0, a[5] || 0);
}
function dateToUtcArray(date) {
return [
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds(),
];
}
function arrayToUtcDate(a) {
// according to web standards (and Safari), a month index is required.
// massage if only given a year.
if (a.length === 1) {
a = a.concat([0]);
}
return new Date(Date.UTC(...a));
}
// Other Utils
function isValidDate(m) {
return !isNaN(m.valueOf());
}
function timeAsMs(m) {
return m.getUTCHours() * 1000 * 60 * 60 +
m.getUTCMinutes() * 1000 * 60 +
m.getUTCSeconds() * 1000 +
m.getUTCMilliseconds();
}
// timeZoneOffset is in minutes
function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) {
let s = marker.toISOString();
s = s.replace('.000', '');
if (stripZeroTime) {
s = s.replace('T00:00:00Z', '');
}
if (s.length > 10) { // time part wasn't stripped, can add timezone info
if (timeZoneOffset == null) {
s = s.replace('Z', '');
}
else if (timeZoneOffset !== 0) {
s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true));
}
// otherwise, its UTC-0 and we want to keep the Z
}
return s;
}
// formats the date, but with no time part
// TODO: somehow merge with buildIsoString and stripZeroTime
// TODO: rename. omit "string"
function formatDayString(marker) {
return marker.toISOString().replace(/T.*$/, '');
}
function formatIsoMonthStr(marker) {
return marker.toISOString().match(/^\d{4}-\d{2}/)[0];
}
// TODO: use Date::toISOString and use everything after the T?
function formatIsoTimeString(marker) {
return padStart(marker.getUTCHours(), 2) + ':' +
padStart(marker.getUTCMinutes(), 2) + ':' +
padStart(marker.getUTCSeconds(), 2);
}
function formatTimeZoneOffset(minutes, doIso = false) {
let sign = minutes < 0 ? '-' : '+';
let abs = Math.abs(minutes);
let hours = Math.floor(abs / 60);
let mins = Math.round(abs % 60);
if (doIso) {
return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`;
}
return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`;
}
function memoize(workerFunc, resEquality, teardownFunc) {
let currentArgs;
let currentRes;
return function (...newArgs) {
if (!currentArgs) {
currentRes = workerFunc.apply(this, newArgs);
}
else if (!isArraysEqual(currentArgs, newArgs)) {
if (teardownFunc) {
teardownFunc(currentRes);
}
let res = workerFunc.apply(this, newArgs);
if (!resEquality || !resEquality(res, currentRes)) {
currentRes = res;
}
}
currentArgs = newArgs;
return currentRes;
};
}
function memoizeObjArg(workerFunc, resEquality, teardownFunc) {
let currentArg;
let currentRes;
return (newArg) => {
if (!currentArg) {
currentRes = workerFunc.call(this, newArg);
}
else if (!isPropsEqual(currentArg, newArg)) {
if (teardownFunc) {
teardownFunc(currentRes);
}
let res = workerFunc.call(this, newArg);
if (!resEquality || !resEquality(res, currentRes)) {
currentRes = res;
}
}
currentArg = newArg;
return currentRes;
};
}
function memoizeArraylike(// used at all?
workerFunc, resEquality, teardownFunc) {
let currentArgSets = [];
let currentResults = [];
return (newArgSets) => {
let currentLen = currentArgSets.length;
let newLen = newArgSets.length;
let i = 0;
for (; i < currentLen; i += 1) {
if (!newArgSets[i]) { // one of the old sets no longer exists
if (teardownFunc) {
teardownFunc(currentResults[i]);
}
}
else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) {
if (teardownFunc) {
teardownFunc(currentResults[i]);
}
let res = workerFunc.apply(this, newArgSets[i]);
if (!resEquality || !resEquality(res, currentResults[i])) {
currentResults[i] = res;
}
}
}
for (; i < newLen; i += 1) {
currentResults[i] = workerFunc.apply(this, newArgSets[i]);
}
currentArgSets = newArgSets;
currentResults.splice(newLen); // remove excess
return currentResults;
};
}
function memoizeHashlike(workerFunc, resEquality, teardownFunc) {
let currentArgHash = {};
let currentResHash = {};
return (newArgHash) => {
let newResHash = {};
for (let key in newArgHash) {
if (!currentResHash[key]) {
newResHash[key] = workerFunc.apply(this, newArgHash[key]);
}
else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) {
if (teardownFunc) {
teardownFunc(currentResHash[key]);
}
let res = workerFunc.apply(this, newArgHash[key]);
newResHash[key] = (resEquality && resEquality(res, currentResHash[key]))
? currentResHash[key]
: res;
}
else {
newResHash[key] = currentResHash[key];
}
}
currentArgHash = newArgHash;
currentResHash = newResHash;
return newResHash;
};
}
const EXTENDED_SETTINGS_AND_SEVERITIES = {
week: 3,
separator: 9,
omitZeroMinute: 9,
meridiem: 9,
omitCommas: 9,
};
const STANDARD_DATE_PROP_SEVERITIES = {
timeZoneName: 7,
era: 6,
year: 5,
month: 4,
day: 2,
weekday: 2,
hour: 1,
minute: 1,
second: 1,
};
const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too
const COMMA_RE = /,/g; // we need re for globalness
const MULTI_SPACE_RE = /\s+/g;
const LTR_RE = /\u200e/g; // control character
const UTC_RE = /UTC|GMT/;
class NativeFormatter {
constructor(formatSettings) {
let standardDateProps = {};
let extendedSettings = {};
let smallestUnitNum = 9; // the smallest unit in the formatter (9 is a sentinel, beyond max)
for (let name in formatSettings) {
if (name in EXTENDED_SETTINGS_AND_SEVERITIES) {
extendedSettings[name] = formatSettings[name];
const severity = EXTENDED_SETTINGS_AND_SEVERITIES[name];
if (severity < 9) {
smallestUnitNum = Math.min(EXTENDED_SETTINGS_AND_SEVERITIES[name], smallestUnitNum);
}
}
else {
standardDateProps[name] = formatSettings[name];
if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity
smallestUnitNum = Math.min(STANDARD_DATE_PROP_SEVERITIES[name], smallestUnitNum);
}
}
}
this.standardDateProps = standardDateProps;
this.extendedSettings = extendedSettings;
this.smallestUnitNum = smallestUnitNum;
this.buildFormattingFunc = memoize(buildFormattingFunc);
}
format(date, context) {
return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date);
}
formatRange(start, end, context, betterDefaultSeparator) {
let { standardDateProps, extendedSettings } = this;
let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem);
if (!diffSeverity) {
return this.format(start, context);
}
let biggestUnitForPartial = diffSeverity;
if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time
(standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') &&
(standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') &&
(standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) {
biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time
}
let full0 = this.format(start, context);
let full1 = this.format(end, context);
if (full0 === full1) {
return full0;
}
let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial);
let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context);
let partial0 = partialFormattingFunc(start);
let partial1 = partialFormattingFunc(end);
let insertion = findCommonInsertion(full0, partial0, full1, partial1);
let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || '';
if (insertion) {
return insertion.before + partial0 + separator + partial1 + insertion.after;
}
return full0 + separator + full1;
}
getSmallestUnit() {
switch (this.smallestUnitNum) {
case 7:
case 6:
case 5:
return 'year';
case 4:
return 'month';
case 3:
return 'week';
case 2:
return 'day';
default:
return 'time'; // really?
}
}
}
function buildFormattingFunc(standardDateProps, extendedSettings, context) {
let standardDatePropCnt = Object.keys(standardDateProps).length;
if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') {
return (date) => (formatTimeZoneOffset(date.timeZoneOffset));
}
if (standardDatePropCnt === 0 && extendedSettings.week) {
return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week));
}
return buildNativeFormattingFunc(standardDateProps, extendedSettings, context);
}
function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) {
standardDateProps = Object.assign({}, standardDateProps); // copy
extendedSettings = Object.assign({}, extendedSettings); // copy
sanitizeSettings(standardDateProps, extendedSettings);
standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers
let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps);
let zeroFormat; // needed?
if (extendedSettings.omitZeroMinute) {
let zeroProps = Object.assign({}, standardDateProps);
delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings
zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps);
}
return (date) => {
let { marker } = date;
let format;
if (zeroFormat && !marker.getUTCMinutes()) {
format = zeroFormat;
}
else {
format = normalFormat;
}
let s = format.format(marker);
return postProcess(s, date, standardDateProps, extendedSettings, context);
};
}
function sanitizeSettings(standardDateProps, extendedSettings) {
// deal with a browser inconsistency where formatting the timezone
// requires that the hour/minute be present.
if (standardDateProps.timeZoneName) {
if (!standardDateProps.hour) {
standardDateProps.hour = '2-digit';
}
if (!standardDateProps.minute) {
standardDateProps.minute = '2-digit';
}
}
// only support short timezone names
if (standardDateProps.timeZoneName === 'long') {
standardDateProps.timeZoneName = 'short';
}
// if requesting to display seconds, MUST display minutes
if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) {
delete extendedSettings.omitZeroMinute;
}
}
function postProcess(s, date, standardDateProps, extendedSettings, context) {
s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes
if (standardDateProps.timeZoneName === 'short') {
s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ?
'UTC' : // important to normalize for IE, which does "GMT"
formatTimeZoneOffset(date.timeZoneOffset));
}
if (extendedSettings.omitCommas) {
s = s.replace(COMMA_RE, '').trim();
}
if (extendedSettings.omitZeroMinute) {
s = s.replace(':00', ''); // zeroFormat doesn't always achieve this
}
// ^ do anything that might create adjacent spaces before this point,
// because MERIDIEM_RE likes to eat up loading spaces
if (extendedSettings.meridiem === false) {
s = s.replace(MERIDIEM_RE, '').trim();
}
else if (extendedSettings.meridiem === 'narrow') { // a/p
s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase());
}
else if (extendedSettings.meridiem === 'short') { // am/pm
s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`);
}
else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase
s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase());
}
s = s.replace(MULTI_SPACE_RE, ' ');
s = s.trim();
return s;
}
function injectTzoStr(s, tzoStr) {
let replaced = false;
s = s.replace(UTC_RE, () => {
replaced = true;
return tzoStr;
});
// IE11 doesn't include UTC/GMT in the original string, so append to end
if (!replaced) {
s += ` ${tzoStr}`;
}
return s;
}
function formatWeekNumber(num, weekText, weekTextLong, locale, display) {
let parts = [];
if (display === 'long') {
parts.push(weekTextLong);
}
else if (display === 'short' || display === 'narrow') {
parts.push(weekText);
}
if (display === 'long' || display === 'short') {
parts.push(' ');
}
parts.push(locale.simpleNumberFormat.format(num));
if (locale.options.direction === 'rtl') { // TODO: use control characters instead?
parts.reverse();
}
return parts.join('');
}
// Range Formatting Utils
// 0 = exactly the same
// 1 = different by time
// and bigger
function computeMarkerDiffSeverity(d0, d1, ca) {
if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) {
return 5;
}
if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) {
return 4;
}
if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) {
return 2;
}
if (timeAsMs(d0) !== timeAsMs(d1)) {
return 1;
}
return 0;
}
function computePartialFormattingOptions(options, biggestUnit) {
let partialOptions = {};
for (let name in options) {
if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone)
STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) {
partialOptions[name] = options[name];
}
}
return partialOptions;
}
function findCommonInsertion(full0, partial0, full1, partial1) {
let i0 = 0;
while (i0 < full0.length) {
let found0 = full0.indexOf(partial0, i0);
if (found0 === -1) {
break;
}
let before0 = full0.substr(0, found0);
i0 = found0 + partial0.length;
let after0 = full0.substr(i0);
let i1 = 0;
while (i1 < full1.length) {
let found1 = full1.indexOf(partial1, i1);
if (found1 === -1) {
break;
}
let before1 = full1.substr(0, found1);
i1 = found1 + partial1.length;
let after1 = full1.substr(i1);
if (before0 === before1 && after0 === after1) {
return {
before: before0,
after: after0,
};
}
}
}
return null;
}
function expandZonedMarker(dateInfo, calendarSystem) {
let a = calendarSystem.markerToArray(dateInfo.marker);
return {
marker: dateInfo.marker,
timeZoneOffset: dateInfo.timeZoneOffset,
array: a,
year: a[0],
month: a[1],
day: a[2],
hour: a[3],
minute: a[4],
second: a[5],
millisecond: a[6],
};
}
function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) {
let startInfo = expandZonedMarker(start, context.calendarSystem);
let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null;
return {
date: startInfo,
start: startInfo,
end: endInfo,
timeZone: context.timeZone,
localeCodes: context.locale.codes,
defaultSeparator: betterDefaultSeparator || context.defaultSeparator,
};
}
/*
TODO: fix the terminology of "formatter" vs "formatting func"
*/
/*
At the time of instantiation, this object does not know which cmd-formatting system it will use.
It receives this at the time of formatting, as a setting.
*/
class CmdFormatter {
constructor(cmdStr) {
this.cmdStr = cmdStr;
}
format(date, context, betterDefaultSeparator) {
return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
}
formatRange(start, end, context, betterDefaultSeparator) {
return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
}
}
class FuncFormatter {
constructor(func) {
this.func = func;
}
format(date, context, betterDefaultSeparator) {
return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
}
formatRange(start, end, context, betterDefaultSeparator) {
return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
}
}
function createFormatter(input) {
if (typeof input === 'object' && input) { // non-null object
return new NativeFormatter(input);
}
if (typeof input === 'string') {
return new CmdFormatter(input);
}
if (typeof input === 'function') {
return new FuncFormatter(input);
}
return null;
}
// base options
// ------------
const BASE_OPTION_REFINERS = {
navLinkDayClick: identity,
navLinkWeekClick: identity,
duration: createDuration,
bootstrapFontAwesome: identity,
buttonIcons: identity,
customButtons: identity,
defaultAllDayEventDuration: createDuration,
defaultTimedEventDuration: createDuration,
nextDayThreshold: createDuration,
scrollTime: createDuration,
scrollTimeReset: Boolean,
slotMinTime: createDuration,
slotMaxTime: createDuration,
dayPopoverFormat: createFormatter,
slotDuration: createDuration,
snapDuration: createDuration,
headerToolbar: identity,
footerToolbar: identity,
defaultRangeSeparator: String,
titleRangeSeparator: String,
forceEventDuration: Boolean,
dayHeaders: Boolean,
dayHeaderFormat: createFormatter,
dayHeaderClassNames: identity,
dayHeaderContent: identity,
dayHeaderDidMount: identity,
dayHeaderWillUnmount: identity,
dayCellClassNames: identity,
dayCellContent: identity,
dayCellDidMount: identity,
dayCellWillUnmount: identity,
initialView: String,
aspectRatio: Number,
weekends: Boolean,
weekNumberCalculation: identity,
weekNumbers: Boolean,
weekNumberClassNames: identity,
weekNumberContent: identity,
weekNumberDidMount: identity,
weekNumberWillUnmount: identity,
editable: Boolean,
viewClassNames: identity,
viewDidMount: identity,
viewWillUnmount: identity,
nowIndicator: Boolean,
nowIndicatorSnap: identity,
nowIndicatorClassNames: identity,
nowIndicatorContent: identity,
nowIndicatorDidMount: identity,
nowIndicatorWillUnmount: identity,
showNonCurrentDates: Boolean,
lazyFetching: Boolean,
startParam: String,
endParam: String,
timeZoneParam: String,
timeZone: String,
locales: identity,
locale: identity,
themeSystem: String,
dragRevertDuration: Number,
dragScroll: Boolean,
allDayMaintainDuration: Boolean,
unselectAuto: Boolean,
dropAccept: identity,
eventOrder: parseFieldSpecs,
eventOrderStrict: Boolean,
handleWindowResize: Boolean,
windowResizeDelay: Number,
longPressDelay: Number,
eventDragMinDistance: Number,
expandRows: Boolean,
height: identity,
contentHeight: identity,
direction: String,
weekNumberFormat: createFormatter,
eventResizableFromStart: Boolean,
displayEventTime: Boolean,
displayEventEnd: Boolean,
weekText: String,
weekTextLong: String,
progressiveEventRendering: Boolean,
businessHours: identity,
initialDate: identity,
now: identity,
eventDataTransform: identity,
stickyHeaderDates: identity,
stickyFooterScrollbar: identity,
viewHeight: identity,
defaultAllDay: Boolean,
eventSourceFailure: identity,
eventSourceSuccess: identity,
eventDisplay: String,
eventStartEditable: Boolean,
eventDurationEditable: Boolean,
eventOverlap: identity,
eventConstraint: identity,
eventAllow: identity,
eventBackgroundColor: String,
eventBorderColor: String,
eventTextColor: String,
eventColor: String,
eventClassNames: identity,
eventContent: identity,
eventDidMount: identity,
eventWillUnmount: identity,
selectConstraint: identity,
selectOverlap: identity,
selectAllow: identity,
droppable: Boolean,
unselectCancel: String,
slotLabelFormat: identity,
slotLaneClassNames: identity,
slotLaneContent: identity,
slotLaneDidMount: identity,
slotLaneWillUnmount: identity,
slotLabelClassNames: identity,
slotLabelContent: identity,
slotLabelDidMount: identity,
slotLabelWillUnmount: identity,
dayMaxEvents: identity,
dayMaxEventRows: identity,
dayMinWidth: Number,
slotLabelInterval: createDuration,
allDayText: String,
allDayClassNames: identity,
allDayContent: identity,
allDayDidMount: identity,
allDayWillUnmount: identity,
slotMinWidth: Number,
navLinks: Boolean,
eventTimeFormat: createFormatter,
rerenderDelay: Number,
moreLinkText: identity,
moreLinkHint: identity,
selectMinDistance: Number,
selectable: Boolean,
selectLongPressDelay: Number,
eventLongPressDelay: Number,
selectMirror: Boolean,
eventMaxStack: Number,
eventMinHeight: Number,
eventMinWidth: Number,
eventShortHeight: Number,
slotEventOverlap: Boolean,
plugins: identity,
firstDay: Number,
dayCount: Number,
dateAlignment: String,
dateIncrement: createDuration,
hiddenDays: identity,
fixedWeekCount: Boolean,
validRange: identity,
visibleRange: identity,
titleFormat: identity,
eventInteractive: Boolean,
// only used by list-view, but languages define the value, so we need it in base options
noEventsText: String,
viewHint: identity,
navLinkHint: identity,
closeHint: String,
timeHint: String,
eventHint: String,
moreLinkClick: identity,
moreLinkClassNames: identity,
moreLinkContent: identity,
moreLinkDidMount: identity,
moreLinkWillUnmount: identity,
monthStartFormat: createFormatter,
// for connectors
// (can't be part of plugin system b/c must be provided at runtime)
handleCustomRendering: identity,
customRenderingMetaMap: identity,
customRenderingReplaces: Boolean,
};
// do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results.
// raw values.
const BASE_OPTION_DEFAULTS = {
eventDisplay: 'auto',
defaultRangeSeparator: ' - ',
titleRangeSeparator: ' \u2013 ',
defaultTimedEventDuration: '01:00:00',
defaultAllDayEventDuration: { day: 1 },
forceEventDuration: false,
nextDayThreshold: '00:00:00',
dayHeaders: true,
initialView: '',
aspectRatio: 1.35,
headerToolbar: {
start: 'title',
center: '',
end: 'today prev,next',
},
weekends: true,
weekNumbers: false,
weekNumberCalculation: 'local',
editable: false,
nowIndicator: false,
scrollTime: '06:00:00',
scrollTimeReset: true,
slotMinTime: '00:00:00',
slotMaxTime: '24:00:00',
showNonCurrentDates: true,
lazyFetching: true,
startParam: 'start',
endParam: 'end',
timeZoneParam: 'timeZone',
timeZone: 'local',
locales: [],
locale: '',
themeSystem: 'standard',
dragRevertDuration: 500,
dragScroll: true,
allDayMaintainDuration: false,
unselectAuto: true,
dropAccept: '*',
eventOrder: 'start,-duration,allDay,title',
dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' },
handleWindowResize: true,
windowResizeDelay: 100,
longPressDelay: 1000,
eventDragMinDistance: 5,
expandRows: false,
navLinks: false,
selectable: false,
eventMinHeight: 15,
eventMinWidth: 30,
eventShortHeight: 30,
monthStartFormat: { month: 'long', day: 'numeric' },
nowIndicatorSnap: 'auto',
};
// calendar listeners
// ------------------
const CALENDAR_LISTENER_REFINERS = {
datesSet: identity,
eventsSet: identity,
eventAdd: identity,
eventChange: identity,
eventRemove: identity,
windowResize: identity,
eventClick: identity,
eventMouseEnter: identity,
eventMouseLeave: identity,
select: identity,
unselect: identity,
loading: identity,
// internal
_unmount: identity,
_beforeprint: identity,
_afterprint: identity,
_noEventDrop: identity,
_noEventResize: identity,
_resize: identity,
_scrollRequest: identity,
};
// calendar-specific options
// -------------------------
const CALENDAR_OPTION_REFINERS = {
buttonText: identity,
buttonHints: identity,
views: identity,
plugins: identity,
initialEvents: identity,
events: identity,
eventSources: identity,
};
const COMPLEX_OPTION_COMPARATORS = {
headerToolbar: isMaybeObjectsEqual,
footerToolbar: isMaybeObjectsEqual,
buttonText: isMaybeObjectsEqual,
buttonHints: isMaybeObjectsEqual,
buttonIcons: isMaybeObjectsEqual,
dateIncrement: isMaybeObjectsEqual,
plugins: isMaybeArraysEqual,
events: isMaybeArraysEqual,
eventSources: isMaybeArraysEqual,
['resources']: isMaybeArraysEqual,
};
function isMaybeObjectsEqual(a, b) {
if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects
return isPropsEqual(a, b);
}
return a === b;
}
function isMaybeArraysEqual(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
return isArraysEqual(a, b);
}
return a === b;
}
// view-specific options
// ---------------------
const VIEW_OPTION_REFINERS = {
type: String,
component: identity,
buttonText: String,
buttonTextKey: String,
dateProfileGeneratorClass: identity,
usesMinMaxTime: Boolean,
classNames: identity,
content: identity,
didMount: identity,
willUnmount: identity,
};
// util funcs
// ----------------------------------------------------------------------------------------------------
function mergeRawOptions(optionSets) {
return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS);
}
function refineProps(input, refiners) {
let refined = {};
let extra = {};
for (let propName in refiners) {
if (propName in input) {
refined[propName] = refiners[propName](input[propName]);
}
}
for (let propName in input) {
if (!(propName in refiners)) {
extra[propName] = input[propName];
}
}
return { refined, extra };
}
function identity(raw) {
return raw;
}
const { hasOwnProperty: internal_common_hasOwnProperty } = Object.prototype;
// Merges an array of objects into a single object.
// The second argument allows for an array of property names who's object values will be merged together.
function mergeProps(propObjs, complexPropsMap) {
let dest = {};
if (complexPropsMap) {
for (let name in complexPropsMap) {
if (complexPropsMap[name] === isMaybeObjectsEqual) { // implies that it's object-mergeable
let complexObjs = [];
// collect the trailing object values, stopping when a non-object is discovered
for (let i = propObjs.length - 1; i >= 0; i -= 1) {
let val = propObjs[i][name];
if (typeof val === 'object' && val) { // non-null object
complexObjs.unshift(val);
}
else if (val !== undefined) {
dest[name] = val; // if there were no objects, this value will be used
break;
}
}
// if the trailing values were objects, use the merged value
if (complexObjs.length) {
dest[name] = mergeProps(complexObjs);
}
}
}
}
// copy values into the destination, going from last to first
for (let i = propObjs.length - 1; i >= 0; i -= 1) {
let props = propObjs[i];
for (let name in props) {
if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
dest[name] = props[name];
}
}
}
return dest;
}
function filterHash(hash, func) {
let filtered = {};
for (let key in hash) {
if (func(hash[key], key)) {
filtered[key] = hash[key];
}
}
return filtered;
}
function mapHash(hash, func) {
let newHash = {};
for (let key in hash) {
newHash[key] = func(hash[key], key);
}
return newHash;
}
function arrayToHash(a) {
let hash = {};
for (let item of a) {
hash[item] = true;
}
return hash;
}
// TODO: reassess browser support
// https://caniuse.com/?search=object.values
function hashValuesToArray(obj) {
let a = [];
for (let key in obj) {
a.push(obj[key]);
}
return a;
}
function isPropsEqual(obj0, obj1) {
if (obj0 === obj1) {
return true;
}
for (let key in obj0) {
if (internal_common_hasOwnProperty.call(obj0, key)) {
if (!(key in obj1)) {
return false;
}
}
}
for (let key in obj1) {
if (internal_common_hasOwnProperty.call(obj1, key)) {
if (obj0[key] !== obj1[key]) {
return false;
}
}
}
return true;
}
const HANDLER_RE = /^on[A-Z]/;
function isNonHandlerPropsEqual(obj0, obj1) {
const keys = getUnequalProps(obj0, obj1);
for (let key of keys) {
if (!HANDLER_RE.test(key)) {
return false;
}
}
return true;
}
function getUnequalProps(obj0, obj1) {
let keys = [];
for (let key in obj0) {
if (internal_common_hasOwnProperty.call(obj0, key)) {
if (!(key in obj1)) {
keys.push(key);
}
}
}
for (let key in obj1) {
if (internal_common_hasOwnProperty.call(obj1, key)) {
if (obj0[key] !== obj1[key]) {
keys.push(key);
}
}
}
return keys;
}
function compareObjs(oldProps, newProps, equalityFuncs = {}) {
if (oldProps === newProps) {
return true;
}
// if (debug) {
// for (let key in newProps) {
// if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) {
// // equal
// } else {
// if (debug) {
// console.log('prop difference', key, oldProps[key], newProps[key])
// }
// }
// }
// // check for props that were omitted in the new
// for (let key in oldProps) {
// if (!(key in newProps)) {
// if (debug) {
// console.log('prop absent', key)
// }
// }
// }
// }
for (let key in newProps) {
if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ;
else {
return false;
}
}
// check for props that were omitted in the new
for (let key in oldProps) {
if (!(key in newProps)) {
return false;
}
}
return true;
}
/*
assumed "true" equality for handler names like "onReceiveSomething"
*/
function isObjValsEqual(val0, val1, comparator) {
if (val0 === val1 || comparator === true) {
return true;
}
if (comparator) {
return comparator(val0, val1);
}
return false;
}
function collectFromHash(hash, startIndex = 0, endIndex, step = 1) {
let res = [];
if (endIndex == null) {
endIndex = Object.keys(hash).length;
}
for (let i = startIndex; i < endIndex; i += step) {
let val = hash[i];
if (val !== undefined) { // will disregard undefined for sparse arrays
res.push(val);
}
}
return res;
}
let calendarSystemClassMap = {};
function registerCalendarSystem(name, theClass) {
calendarSystemClassMap[name] = theClass;
}
function createCalendarSystem(name) {
return new calendarSystemClassMap[name]();
}
class GregorianCalendarSystem {
getMarkerYear(d) {
return d.getUTCFullYear();
}
getMarkerMonth(d) {
return d.getUTCMonth();
}
getMarkerDay(d) {
return d.getUTCDate();
}
arrayToMarker(arr) {
return arrayToUtcDate(arr);
}
markerToArray(marker) {
return dateToUtcArray(marker);
}
}
registerCalendarSystem('gregory', GregorianCalendarSystem);
const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/;
function internal_common_parse(str) {
let m = ISO_RE.exec(str);
if (m) {
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));
if (isValidDate(marker)) {
let timeZoneOffset = null;
if (m[13]) {
timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 +
Number(m[18] || 0));
}
return {
marker,
isTimeUnspecified: !m[6],
timeZoneOffset,
};
}
}
return null;
}
class DateEnv {
constructor(settings) {
let timeZone = this.timeZone = settings.timeZone;
let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC';
if (settings.namedTimeZoneImpl && isNamedTimeZone) {
this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone);
}
this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl);
this.calendarSystem = createCalendarSystem(settings.calendarSystem);
this.locale = settings.locale;
this.weekDow = settings.locale.week.dow;
this.weekDoy = settings.locale.week.doy;
if (settings.weekNumberCalculation === 'ISO') {
this.weekDow = 1;
this.weekDoy = 4;
}
if (typeof settings.firstDay === 'number') {
this.weekDow = settings.firstDay;
}
if (typeof settings.weekNumberCalculation === 'function') {
this.weekNumberFunc = settings.weekNumberCalculation;
}
this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText;
this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText;
this.cmdFormatter = settings.cmdFormatter;
this.defaultSeparator = settings.defaultSeparator;
}
// Creating / Parsing
createMarker(input) {
let meta = this.createMarkerMeta(input);
if (meta === null) {
return null;
}
return meta.marker;
}
createNowMarker() {
if (this.canComputeOffset) {
return this.timestampToMarker(new Date().valueOf());
}
// if we can't compute the current date val for a timezone,
// better to give the current local date vals than UTC
return arrayToUtcDate(dateToLocalArray(new Date()));
}
createMarkerMeta(input) {
if (typeof input === 'string') {
return this.parse(input);
}
let marker = null;
if (typeof input === 'number') {
marker = this.timestampToMarker(input);
}
else if (input instanceof Date) {
input = input.valueOf();
if (!isNaN(input)) {
marker = this.timestampToMarker(input);
}
}
else if (Array.isArray(input)) {
marker = arrayToUtcDate(input);
}
if (marker === null || !isValidDate(marker)) {
return null;
}
return { marker, isTimeUnspecified: false, forcedTzo: null };
}
parse(s) {
let parts = internal_common_parse(s);
if (parts === null) {
return null;
}
let { marker } = parts;
let forcedTzo = null;
if (parts.timeZoneOffset !== null) {
if (this.canComputeOffset) {
marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000);
}
else {
forcedTzo = parts.timeZoneOffset;
}
}
return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo };
}
// Accessors
getYear(marker) {
return this.calendarSystem.getMarkerYear(marker);
}
getMonth(marker) {
return this.calendarSystem.getMarkerMonth(marker);
}
getDay(marker) {
return this.calendarSystem.getMarkerDay(marker);
}
// Adding / Subtracting
add(marker, dur) {
let a = this.calendarSystem.markerToArray(marker);
a[0] += dur.years;
a[1] += dur.months;
a[2] += dur.days;
a[6] += dur.milliseconds;
return this.calendarSystem.arrayToMarker(a);
}
subtract(marker, dur) {
let a = this.calendarSystem.markerToArray(marker);
a[0] -= dur.years;
a[1] -= dur.months;
a[2] -= dur.days;
a[6] -= dur.milliseconds;
return this.calendarSystem.arrayToMarker(a);
}
addYears(marker, n) {
let a = this.calendarSystem.markerToArray(marker);
a[0] += n;
return this.calendarSystem.arrayToMarker(a);
}
addMonths(marker, n) {
let a = this.calendarSystem.markerToArray(marker);
a[1] += n;
return this.calendarSystem.arrayToMarker(a);
}
// Diffing Whole Units
diffWholeYears(m0, m1) {
let { calendarSystem } = this;
if (timeAsMs(m0) === timeAsMs(m1) &&
calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) &&
calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) {
return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0);
}
return null;
}
diffWholeMonths(m0, m1) {
let { calendarSystem } = this;
if (timeAsMs(m0) === timeAsMs(m1) &&
calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) {
return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) +
(calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12;
}
return null;
}
// Range / Duration
greatestWholeUnit(m0, m1) {
let n = this.diffWholeYears(m0, m1);
if (n !== null) {
return { unit: 'year', value: n };
}
n = this.diffWholeMonths(m0, m1);
if (n !== null) {
return { unit: 'month', value: n };
}
n = diffWholeWeeks(m0, m1);
if (n !== null) {
return { unit: 'week', value: n };
}
n = diffWholeDays(m0, m1);
if (n !== null) {
return { unit: 'day', value: n };
}
n = diffHours(m0, m1);
if (isInt(n)) {
return { unit: 'hour', value: n };
}
n = diffMinutes(m0, m1);
if (isInt(n)) {
return { unit: 'minute', value: n };
}
n = diffSeconds(m0, m1);
if (isInt(n)) {
return { unit: 'second', value: n };
}
return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() };
}
countDurationsBetween(m0, m1, d) {
// TODO: can use greatestWholeUnit
let diff;
if (d.years) {
diff = this.diffWholeYears(m0, m1);
if (diff !== null) {
return diff / asRoughYears(d);
}
}
if (d.months) {
diff = this.diffWholeMonths(m0, m1);
if (diff !== null) {
return diff / asRoughMonths(d);
}
}
if (d.days) {
diff = diffWholeDays(m0, m1);
if (diff !== null) {
return diff / asRoughDays(d);
}
}
return (m1.valueOf() - m0.valueOf()) / asRoughMs(d);
}
// Start-Of
// these DON'T return zoned-dates. only UTC start-of dates
startOf(m, unit) {
if (unit === 'year') {
return this.startOfYear(m);
}
if (unit === 'month') {
return this.startOfMonth(m);
}
if (unit === 'week') {
return this.startOfWeek(m);
}
if (unit === 'day') {
return startOfDay(m);
}
if (unit === 'hour') {
return startOfHour(m);
}
if (unit === 'minute') {
return startOfMinute(m);
}
if (unit === 'second') {
return startOfSecond(m);
}
return null;
}
startOfYear(m) {
return this.calendarSystem.arrayToMarker([
this.calendarSystem.getMarkerYear(m),
]);
}
startOfMonth(m) {
return this.calendarSystem.arrayToMarker([
this.calendarSystem.getMarkerYear(m),
this.calendarSystem.getMarkerMonth(m),
]);
}
startOfWeek(m) {
return this.calendarSystem.arrayToMarker([
this.calendarSystem.getMarkerYear(m),
this.calendarSystem.getMarkerMonth(m),
m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7),
]);
}
// Week Number
computeWeekNumber(marker) {
if (this.weekNumberFunc) {
return this.weekNumberFunc(this.toDate(marker));
}
return weekOfYear(marker, this.weekDow, this.weekDoy);
}
// TODO: choke on timeZoneName: long
format(marker, formatter, dateOptions = {}) {
return formatter.format({
marker,
timeZoneOffset: dateOptions.forcedTzo != null ?
dateOptions.forcedTzo :
this.offsetForMarker(marker),
}, this);
}
formatRange(start, end, formatter, dateOptions = {}) {
if (dateOptions.isEndExclusive) {
end = addMs(end, -1);
}
return formatter.formatRange({
marker: start,
timeZoneOffset: dateOptions.forcedStartTzo != null ?
dateOptions.forcedStartTzo :
this.offsetForMarker(start),
}, {
marker: end,
timeZoneOffset: dateOptions.forcedEndTzo != null ?
dateOptions.forcedEndTzo :
this.offsetForMarker(end),
}, this, dateOptions.defaultSeparator);
}
/*
DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that,
might as well use buildIsoString or some other util directly
*/
formatIso(marker, extraOptions = {}) {
let timeZoneOffset = null;
if (!extraOptions.omitTimeZoneOffset) {
if (extraOptions.forcedTzo != null) {
timeZoneOffset = extraOptions.forcedTzo;
}
else {
timeZoneOffset = this.offsetForMarker(marker);
}
}
return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime);
}
// TimeZone
timestampToMarker(ms) {
if (this.timeZone === 'local') {
return arrayToUtcDate(dateToLocalArray(new Date(ms)));
}
if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) {
return new Date(ms);
}
return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms));
}
offsetForMarker(m) {
if (this.timeZone === 'local') {
return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset
}
if (this.timeZone === 'UTC') {
return 0;
}
if (this.namedTimeZoneImpl) {
return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m));
}
return null;
}
// Conversion
toDate(m, forcedTzo) {
if (this.timeZone === 'local') {
return arrayToLocalDate(dateToUtcArray(m));
}
if (this.timeZone === 'UTC') {
return new Date(m.valueOf()); // make sure it's a copy
}
if (!this.namedTimeZoneImpl) {
return new Date(m.valueOf() - (forcedTzo || 0));
}
return new Date(m.valueOf() -
this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60);
}
}
class internal_common_Theme {
constructor(calendarOptions) {
if (this.iconOverrideOption) {
this.setIconOverride(calendarOptions[this.iconOverrideOption]);
}
}
setIconOverride(iconOverrideHash) {
let iconClassesCopy;
let buttonName;
if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object
iconClassesCopy = Object.assign({}, this.iconClasses);
for (buttonName in iconOverrideHash) {
iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]);
}
this.iconClasses = iconClassesCopy;
}
else if (iconOverrideHash === false) {
this.iconClasses = {};
}
}
applyIconOverridePrefix(className) {
let prefix = this.iconOverridePrefix;
if (prefix && className.indexOf(prefix) !== 0) { // if not already present
className = prefix + className;
}
return className;
}
getClass(key) {
return this.classes[key] || '';
}
getIconClass(buttonName, isRtl) {
let className;
if (isRtl && this.rtlIconClasses) {
className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName];
}
else {
className = this.iconClasses[buttonName];
}
if (className) {
return `${this.baseIconClass} ${className}`;
}
return '';
}
getCustomButtonIconClass(customButtonProps) {
let className;
if (this.iconOverrideCustomButtonOption) {
className = customButtonProps[this.iconOverrideCustomButtonOption];
if (className) {
return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`;
}
}
return '';
}
}
internal_common_Theme.prototype.classes = {};
internal_common_Theme.prototype.iconClasses = {};
internal_common_Theme.prototype.baseIconClass = '';
internal_common_Theme.prototype.iconOverridePrefix = '';
/*
NOTE: this can be a public API, especially createElement for hooks.
See examples/typescript-scheduler/src/index.ts
*/
function flushSync(runBeforeFlush) {
runBeforeFlush();
let oldDebounceRendering = l.debounceRendering; // orig
let callbackQ = [];
function execCallbackSync(callback) {
callbackQ.push(callback);
}
l.debounceRendering = execCallbackSync;
D(y(FakeComponent, {}), document.createElement('div'));
while (callbackQ.length) {
callbackQ.shift()();
}
l.debounceRendering = oldDebounceRendering;
}
class FakeComponent extends x {
render() { return y('div', {}); }
componentDidMount() { this.setState({}); }
}
// TODO: use preact/compat instead?
function internal_common_createContext(defaultValue) {
let ContextType = G(defaultValue);
let origProvider = ContextType.Provider;
ContextType.Provider = function () {
let isNew = !this.getChildContext;
let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params
if (isNew) {
let subs = [];
this.shouldComponentUpdate = (_props) => {
if (this.props.value !== _props.value) {
subs.forEach((c) => {
c.context = _props.value;
c.forceUpdate();
});
}
};
this.sub = (c) => {
subs.push(c);
let old = c.componentWillUnmount;
c.componentWillUnmount = () => {
subs.splice(subs.indexOf(c), 1);
old && old.call(c);
};
};
}
return children;
};
return ContextType;
}
class ScrollResponder {
constructor(execFunc, emitter, scrollTime, scrollTimeReset) {
this.execFunc = execFunc;
this.emitter = emitter;
this.scrollTime = scrollTime;
this.scrollTimeReset = scrollTimeReset;
this.handleScrollRequest = (request) => {
this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request);
this.drain();
};
emitter.on('_scrollRequest', this.handleScrollRequest);
this.fireInitialScroll();
}
detach() {
this.emitter.off('_scrollRequest', this.handleScrollRequest);
}
update(isDatesNew) {
if (isDatesNew && this.scrollTimeReset) {
this.fireInitialScroll(); // will drain
}
else {
this.drain();
}
}
fireInitialScroll() {
this.handleScrollRequest({
time: this.scrollTime,
});
}
drain() {
if (this.queuedRequest && this.execFunc(this.queuedRequest)) {
this.queuedRequest = null;
}
}
}
const ViewContextType = internal_common_createContext({}); // for Components
function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, nowManager, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) {
return {
dateEnv,
nowManager,
options: viewOptions,
pluginHooks,
emitter,
dispatch,
getCurrentData,
calendarApi,
viewSpec,
viewApi,
dateProfileGenerator,
theme,
isRtl: viewOptions.direction === 'rtl',
addResizeHandler(handler) {
emitter.on('_resize', handler);
},
removeResizeHandler(handler) {
emitter.off('_resize', handler);
},
createScrollResponder(execFunc) {
return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset);
},
registerInteractiveComponent,
unregisterInteractiveComponent,
};
}
/* eslint max-classes-per-file: off */
class PureComponent extends x {
// debug: boolean
shouldComponentUpdate(nextProps, nextState) {
const shouldUpdate = !compareObjs(this.props, nextProps, this.propEquality /*, this.debug */) ||
!compareObjs(this.state, nextState, this.stateEquality /*, this.debug */);
// if (this.debug && shouldUpdate) {
// console.log('shouldUpdate!')
// }
return shouldUpdate;
}
// HACK for freakin' React StrictMode
safeSetState(newState) {
if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) {
this.setState(newState);
}
}
}
PureComponent.addPropsEquality = addPropsEquality;
PureComponent.addStateEquality = addStateEquality;
PureComponent.contextType = ViewContextType;
PureComponent.prototype.propEquality = {};
PureComponent.prototype.stateEquality = {};
class BaseComponent extends PureComponent {
}
BaseComponent.contextType = ViewContextType;
function addPropsEquality(propEquality) {
let hash = Object.create(this.prototype.propEquality);
Object.assign(hash, propEquality);
this.prototype.propEquality = hash;
}
function addStateEquality(stateEquality) {
let hash = Object.create(this.prototype.stateEquality);
Object.assign(hash, stateEquality);
this.prototype.stateEquality = hash;
}
// use other one
function setRef(ref, current) {
if (typeof ref === 'function') {
ref(current);
}
else if (ref) {
// see https://github.com/facebook/react/issues/13029
ref.current = current;
}
}
class ContentInjector extends BaseComponent {
constructor() {
super(...arguments);
this.id = guid();
this.queuedDomNodes = [];
this.currentDomNodes = [];
this.handleEl = (el) => {
const { options } = this.context;
const { generatorName } = this.props;
if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) {
this.updateElRef(el);
}
};
this.updateElRef = (el) => {
if (this.props.elRef) {
setRef(this.props.elRef, el);
}
};
}
render() {
const { props, context } = this;
const { options } = context;
const { customGenerator, defaultGenerator, renderProps } = props;
const attrs = buildElAttrs(props, [], this.handleEl);
let useDefault = false;
let innerContent;
let queuedDomNodes = [];
let currentGeneratorMeta;
if (customGenerator != null) {
const customGeneratorRes = typeof customGenerator === 'function' ?
customGenerator(renderProps, y) :
customGenerator;
if (customGeneratorRes === true) {
useDefault = true;
}
else {
const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null
if (isObject && ('html' in customGeneratorRes)) {
attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html };
}
else if (isObject && ('domNodes' in customGeneratorRes)) {
queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes);
}
else if (isObject
? i(customGeneratorRes) // vdom node
: typeof customGeneratorRes !== 'function' // primitive value (like string or number)
) {
// use in vdom
innerContent = customGeneratorRes;
}
else {
// an exotic object for handleCustomRendering
currentGeneratorMeta = customGeneratorRes;
}
}
}
else {
useDefault = !hasCustomRenderingHandler(props.generatorName, options);
}
if (useDefault && defaultGenerator) {
innerContent = defaultGenerator(renderProps);
}
this.queuedDomNodes = queuedDomNodes;
this.currentGeneratorMeta = currentGeneratorMeta;
return y(props.elTag, attrs, innerContent);
}
componentDidMount() {
this.applyQueueudDomNodes();
this.triggerCustomRendering(true);
}
componentDidUpdate() {
this.applyQueueudDomNodes();
this.triggerCustomRendering(true);
}
componentWillUnmount() {
this.triggerCustomRendering(false); // TODO: different API for removal?
}
triggerCustomRendering(isActive) {
var _a;
const { props, context } = this;
const { handleCustomRendering, customRenderingMetaMap } = context.options;
if (handleCustomRendering) {
const generatorMeta = (_a = this.currentGeneratorMeta) !== null && _a !== void 0 ? _a : customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName];
if (generatorMeta) {
handleCustomRendering(Object.assign(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els
generatorMeta }, props), { elClasses: (props.elClasses || []).filter(isTruthy) }));
}
}
}
applyQueueudDomNodes() {
const { queuedDomNodes, currentDomNodes } = this;
const el = this.base;
if (!isArraysEqual(queuedDomNodes, currentDomNodes)) {
currentDomNodes.forEach(removeElement);
for (let newNode of queuedDomNodes) {
el.appendChild(newNode);
}
this.currentDomNodes = queuedDomNodes;
}
}
}
ContentInjector.addPropsEquality({
elClasses: isArraysEqual,
elStyle: isPropsEqual,
elAttrs: isNonHandlerPropsEqual,
renderProps: isPropsEqual,
});
// Util
/*
Does UI-framework provide custom way of rendering that does not use Preact VDOM
AND does the calendar's options define custom rendering?
AKA. Should we NOT render the default content?
*/
function hasCustomRenderingHandler(generatorName, options) {
var _a;
return Boolean(options.handleCustomRendering &&
generatorName &&
((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName]));
}
function buildElAttrs(props, extraClassNames, elRef) {
const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: elRef });
if (props.elClasses || extraClassNames) {
attrs.className = (props.elClasses || [])
.concat(extraClassNames || [])
.concat(attrs.className || [])
.filter(Boolean)
.join(' ');
}
if (props.elStyle) {
attrs.style = props.elStyle;
}
return attrs;
}
function isTruthy(val) {
return Boolean(val);
}
const RenderId = internal_common_createContext(0);
class ContentContainer extends x {
constructor() {
super(...arguments);
this.InnerContent = InnerContentInjector.bind(undefined, this);
this.handleEl = (el) => {
this.el = el;
if (this.props.elRef) {
setRef(this.props.elRef, el);
if (el && this.didMountMisfire) {
this.componentDidMount();
}
}
};
}
render() {
const { props } = this;
const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps);
if (props.children) {
const elAttrs = buildElAttrs(props, generatedClassNames, this.handleEl);
const children = props.children(this.InnerContent, props.renderProps, elAttrs);
if (props.elTag) {
return y(props.elTag, elAttrs, children);
}
else {
return children;
}
}
else {
return y((ContentInjector), Object.assign(Object.assign({}, props), { elRef: this.handleEl, elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context }));
}
}
componentDidMount() {
var _a, _b;
if (this.el) {
(_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
}
else {
this.didMountMisfire = true;
}
}
componentWillUnmount() {
var _a, _b;
(_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
}
}
ContentContainer.contextType = RenderId;
function InnerContentInjector(containerComponent, props) {
const parentProps = containerComponent.props;
return y((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, customGenerator: parentProps.customGenerator, defaultGenerator: parentProps.defaultGenerator, renderId: containerComponent.context }, props));
}
// Utils
function generateClassNames(classNameGenerator, renderProps) {
const classNames = typeof classNameGenerator === 'function' ?
classNameGenerator(renderProps) :
classNameGenerator || [];
return typeof classNames === 'string' ? [classNames] : classNames;
}
class ViewContainer extends BaseComponent {
render() {
let { props, context } = this;
let { options } = context;
let renderProps = { view: context.viewApi };
return (y(ContentContainer, { elRef: props.elRef, elTag: props.elTag || 'div', elAttrs: props.elAttrs, elClasses: [
...buildViewClassNames(props.viewSpec),
...(props.elClasses || []),
], elStyle: props.elStyle, renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }, () => props.children));
}
}
function buildViewClassNames(viewSpec) {
return [
`fc-${viewSpec.type}-view`,
'fc-view',
];
}
function parseRange(input, dateEnv) {
let start = null;
let end = null;
if (input.start) {
start = dateEnv.createMarker(input.start);
}
if (input.end) {
end = dateEnv.createMarker(input.end);
}
if (!start && !end) {
return null;
}
if (start && end && end < start) {
return null;
}
return { start, end };
}
// SIDE-EFFECT: will mutate ranges.
// Will return a new array result.
function invertRanges(ranges, constraintRange) {
let invertedRanges = [];
let { start } = constraintRange; // the end of the previous range. the start of the new range
let i;
let dateRange;
// ranges need to be in order. required for our date-walking algorithm
ranges.sort(compareRanges);
for (i = 0; i < ranges.length; i += 1) {
dateRange = ranges[i];
// add the span of time before the event (if there is any)
if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)
invertedRanges.push({ start, end: dateRange.start });
}
if (dateRange.end > start) {
start = dateRange.end;
}
}
// add the span of time after the last event (if there is any)
if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic)
invertedRanges.push({ start, end: constraintRange.end });
}
return invertedRanges;
}
function compareRanges(range0, range1) {
return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first
}
function intersectRanges(range0, range1) {
let { start, end } = range0;
let newRange = null;
if (range1.start !== null) {
if (start === null) {
start = range1.start;
}
else {
start = new Date(Math.max(start.valueOf(), range1.start.valueOf()));
}
}
if (range1.end != null) {
if (end === null) {
end = range1.end;
}
else {
end = new Date(Math.min(end.valueOf(), range1.end.valueOf()));
}
}
if (start === null || end === null || start < end) {
newRange = { start, end };
}
return newRange;
}
function rangesEqual(range0, range1) {
return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
(range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf());
}
function rangesIntersect(range0, range1) {
return (range0.end === null || range1.start === null || range0.end > range1.start) &&
(range0.start === null || range1.end === null || range0.start < range1.end);
}
function rangeContainsRange(outerRange, innerRange) {
return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) &&
(outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end));
}
function rangeContainsMarker(range, date) {
return (range.start === null || date >= range.start) &&
(range.end === null || date < range.end);
}
// If the given date is not within the given range, move it inside.
// (If it's past the end, make it one millisecond before the end).
function constrainMarkerToRange(date, range) {
if (range.start != null && date < range.start) {
return range.start;
}
if (range.end != null && date >= range.end) {
return new Date(range.end.valueOf() - 1);
}
return date;
}
/* Date stuff that doesn't belong in datelib core
----------------------------------------------------------------------------------------------------------------------*/
// given a timed range, computes an all-day range that has the same exact duration,
// but whose start time is aligned with the start of the day.
function computeAlignedDayRange(timedRange) {
let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1;
let start = startOfDay(timedRange.start);
let end = addDays(start, dayCnt);
return { start, end };
}
// given a timed range, computes an all-day range based on how for the end date bleeds into the next day
// TODO: give nextDayThreshold a default arg
function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) {
let startDay = null;
let endDay = null;
if (timedRange.end) {
endDay = startOfDay(timedRange.end);
let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay`
// If the end time is actually inclusively part of the next day and is equal to or
// beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
// Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
endDay = addDays(endDay, 1);
}
}
if (timedRange.start) {
startDay = startOfDay(timedRange.start); // the beginning of the day the range starts
// If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
if (endDay && endDay <= startDay) {
endDay = addDays(startDay, 1);
}
}
return { start: startDay, end: endDay };
}
// spans from one day into another?
function isMultiDayRange(range) {
let visibleRange = computeVisibleDayRange(range);
return diffDays(visibleRange.start, visibleRange.end) > 1;
}
function diffDates(date0, date1, dateEnv, largeUnit) {
if (largeUnit === 'year') {
return createDuration(dateEnv.diffWholeYears(date0, date1), 'year');
}
if (largeUnit === 'month') {
return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month');
}
return diffDayAndTime(date0, date1); // returns a duration
}
class DateProfileGenerator {
constructor(props) {
this.props = props;
this.initHiddenDays();
}
/* Date Range Computation
------------------------------------------------------------------------------------------------------------------*/
// Builds a structure with info about what the dates/ranges will be for the "prev" view.
buildPrev(currentDateProfile, currentDate, forceToValid) {
let { dateEnv } = this.props;
let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
currentDateProfile.dateIncrement);
return this.build(prevDate, -1, forceToValid);
}
// Builds a structure with info about what the dates/ranges will be for the "next" view.
buildNext(currentDateProfile, currentDate, forceToValid) {
let { dateEnv } = this.props;
let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
currentDateProfile.dateIncrement);
return this.build(nextDate, 1, forceToValid);
}
// Builds a structure holding dates/ranges for rendering around the given date.
// Optional direction param indicates whether the date is being incremented/decremented
// from its previous value. decremented = -1, incremented = 1 (default).
build(currentDate, direction, forceToValid = true) {
let { props } = this;
let validRange;
let currentInfo;
let isRangeAllDay;
let renderRange;
let activeRange;
let isValid;
validRange = this.buildValidRange();
validRange = this.trimHiddenDays(validRange);
if (forceToValid) {
currentDate = constrainMarkerToRange(currentDate, validRange);
}
currentInfo = this.buildCurrentRangeInfo(currentDate, direction);
isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit);
renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay);
renderRange = this.trimHiddenDays(renderRange);
activeRange = renderRange;
if (!props.showNonCurrentDates) {
activeRange = intersectRanges(activeRange, currentInfo.range);
}
activeRange = this.adjustActiveRange(activeRange);
activeRange = intersectRanges(activeRange, validRange); // might return null
// it's invalid if the originally requested date is not contained,
// or if the range is completely outside of the valid range.
isValid = rangesIntersect(currentInfo.range, validRange);
// HACK: constrain to render-range so `currentDate` is more useful to view rendering
if (!rangeContainsMarker(renderRange, currentDate)) {
currentDate = renderRange.start;
}
return {
currentDate,
// constraint for where prev/next operations can go and where events can be dragged/resized to.
// an object with optional start and end properties.
validRange,
// range the view is formally responsible for.
// for example, a month view might have 1st-31st, excluding padded dates
currentRange: currentInfo.range,
// name of largest unit being displayed, like "month" or "week"
currentRangeUnit: currentInfo.unit,
isRangeAllDay,
// dates that display events and accept drag-n-drop
// will be `null` if no dates accept events
activeRange,
// date range with a rendered skeleton
// includes not-active days that need some sort of DOM
renderRange,
// Duration object that denotes the first visible time of any given day
slotMinTime: props.slotMinTime,
// Duration object that denotes the exclusive visible end time of any given day
slotMaxTime: props.slotMaxTime,
isValid,
// how far the current date will move for a prev/next operation
dateIncrement: this.buildDateIncrement(currentInfo.duration),
// pass a fallback (might be null) ^
};
}
// Builds an object with optional start/end properties.
// Indicates the minimum/maximum dates to display.
// not responsible for trimming hidden days.
buildValidRange() {
let input = this.props.validRangeInput;
let simpleInput = typeof input === 'function'
? input.call(this.props.calendarApi, this.props.dateEnv.toDate(this.props.nowManager.getDateMarker()))
: input;
return this.refineRange(simpleInput) ||
{ start: null, end: null }; // completely open-ended
}
// Builds a structure with info about the "current" range, the range that is
// highlighted as being the current month for example.
// See build() for a description of `direction`.
// Guaranteed to have `range` and `unit` properties. `duration` is optional.
buildCurrentRangeInfo(date, direction) {
let { props } = this;
let duration = null;
let unit = null;
let range = null;
let dayCount;
if (props.duration) {
duration = props.duration;
unit = props.durationUnit;
range = this.buildRangeFromDuration(date, direction, duration, unit);
}
else if ((dayCount = this.props.dayCount)) {
unit = 'day';
range = this.buildRangeFromDayCount(date, direction, dayCount);
}
else if ((range = this.buildCustomVisibleRange(date))) {
unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit;
}
else {
duration = this.getFallbackDuration();
unit = greatestDurationDenominator(duration).unit;
range = this.buildRangeFromDuration(date, direction, duration, unit);
}
return { duration, unit, range };
}
getFallbackDuration() {
return createDuration({ day: 1 });
}
// Returns a new activeRange to have time values (un-ambiguate)
// slotMinTime or slotMaxTime causes the range to expand.
adjustActiveRange(range) {
let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props;
let { start, end } = range;
if (usesMinMaxTime) {
// expand active range if slotMinTime is negative (why not when positive?)
if (asRoughDays(slotMinTime) < 0) {
start = startOfDay(start); // necessary?
start = dateEnv.add(start, slotMinTime);
}
// expand active range if slotMaxTime is beyond one day (why not when negative?)
if (asRoughDays(slotMaxTime) > 1) {
end = startOfDay(end); // necessary?
end = addDays(end, -1);
end = dateEnv.add(end, slotMaxTime);
}
}
return { start, end };
}
// Builds the "current" range when it is specified as an explicit duration.
// `unit` is the already-computed greatestDurationDenominator unit of duration.
buildRangeFromDuration(date, direction, duration, unit) {
let { dateEnv, dateAlignment } = this.props;
let start;
let end;
let res;
// compute what the alignment should be
if (!dateAlignment) {
let { dateIncrement } = this.props;
if (dateIncrement) {
// use the smaller of the two units
if (asRoughMs(dateIncrement) < asRoughMs(duration)) {
dateAlignment = greatestDurationDenominator(dateIncrement).unit;
}
else {
dateAlignment = unit;
}
}
else {
dateAlignment = unit;
}
}
// if the view displays a single day or smaller
if (asRoughDays(duration) <= 1) {
if (this.isHiddenDay(start)) {
start = this.skipHiddenDays(start, direction);
start = startOfDay(start);
}
}
function computeRes() {
start = dateEnv.startOf(date, dateAlignment);
end = dateEnv.add(start, duration);
res = { start, end };
}
computeRes();
// if range is completely enveloped by hidden days, go past the hidden days
if (!this.trimHiddenDays(res)) {
date = this.skipHiddenDays(date, direction);
computeRes();
}
return res;
}
// Builds the "current" range when a dayCount is specified.
buildRangeFromDayCount(date, direction, dayCount) {
let { dateEnv, dateAlignment } = this.props;
let runningCount = 0;
let start = date;
let end;
if (dateAlignment) {
start = dateEnv.startOf(start, dateAlignment);
}
start = startOfDay(start);
start = this.skipHiddenDays(start, direction);
end = start;
do {
end = addDays(end, 1);
if (!this.isHiddenDay(end)) {
runningCount += 1;
}
} while (runningCount < dayCount);
return { start, end };
}
// Builds a normalized range object for the "visible" range,
// which is a way to define the currentRange and activeRange at the same time.
buildCustomVisibleRange(date) {
let { props } = this;
let input = props.visibleRangeInput;
let simpleInput = typeof input === 'function'
? input.call(props.calendarApi, props.dateEnv.toDate(date))
: input;
let range = this.refineRange(simpleInput);
if (range && (range.start == null || range.end == null)) {
return null;
}
return range;
}
// Computes the range that will represent the element/cells for *rendering*,
// but which may have voided days/times.
// not responsible for trimming hidden days.
buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
return currentRange;
}
// Compute the duration value that should be added/substracted to the current date
// when a prev/next operation happens.
buildDateIncrement(fallback) {
let { dateIncrement } = this.props;
let customAlignment;
if (dateIncrement) {
return dateIncrement;
}
if ((customAlignment = this.props.dateAlignment)) {
return createDuration(1, customAlignment);
}
if (fallback) {
return fallback;
}
return createDuration({ days: 1 });
}
refineRange(rangeInput) {
if (rangeInput) {
let range = parseRange(rangeInput, this.props.dateEnv);
if (range) {
range = computeVisibleDayRange(range);
}
return range;
}
return null;
}
/* Hidden Days
------------------------------------------------------------------------------------------------------------------*/
// Initializes internal variables related to calculating hidden days-of-week
initHiddenDays() {
let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden
let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
let dayCnt = 0;
let i;
if (this.props.weekends === false) {
hiddenDays.push(0, 6); // 0=sunday, 6=saturday
}
for (i = 0; i < 7; i += 1) {
if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) {
dayCnt += 1;
}
}
if (!dayCnt) {
throw new Error('invalid hiddenDays'); // all days were hidden? bad.
}
this.isHiddenDayHash = isHiddenDayHash;
}
// Remove days from the beginning and end of the range that are computed as hidden.
// If the whole range is trimmed off, returns null
trimHiddenDays(range) {
let { start, end } = range;
if (start) {
start = this.skipHiddenDays(start);
}
if (end) {
end = this.skipHiddenDays(end, -1, true);
}
if (start == null || end == null || start < end) {
return { start, end };
}
return null;
}
// Is the current day hidden?
// `day` is a day-of-week index (0-6), or a Date (used for UTC)
isHiddenDay(day) {
if (day instanceof Date) {
day = day.getUTCDay();
}
return this.isHiddenDayHash[day];
}
// Incrementing the current day until it is no longer a hidden day, returning a copy.
// DOES NOT CONSIDER validRange!
// If the initial value of `date` is not a hidden day, don't do anything.
// Pass `isExclusive` as `true` if you are dealing with an end date.
// `inc` defaults to `1` (increment one day forward each time)
skipHiddenDays(date, inc = 1, isExclusive = false) {
while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) {
date = addDays(date, inc);
}
return date;
}
}
function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) {
return {
instanceId: guid(),
defId,
range,
forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo,
};
}
function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) {
for (let i = 0; i < recurringTypes.length; i += 1) {
let parsed = recurringTypes[i].parse(refined, dateEnv);
if (parsed) {
let { allDay } = refined;
if (allDay == null) {
allDay = defaultAllDay;
if (allDay == null) {
allDay = parsed.allDayGuess;
if (allDay == null) {
allDay = false;
}
}
}
return {
allDay,
duration: parsed.duration,
typeData: parsed.typeData,
typeId: i,
};
}
}
return null;
}
function expandRecurring(eventStore, framingRange, context) {
let { dateEnv, pluginHooks, options } = context;
let { defs, instances } = eventStore;
// remove existing recurring instances
// TODO: bad. always expand events as a second step
instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef);
for (let defId in defs) {
let def = defs[defId];
if (def.recurringDef) {
let { duration } = def.recurringDef;
if (!duration) {
duration = def.allDay ?
options.defaultAllDayEventDuration :
options.defaultTimedEventDuration;
}
let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes);
for (let start of starts) {
let instance = createEventInstance(defId, {
start,
end: dateEnv.add(start, duration),
});
instances[instance.instanceId] = instance;
}
}
}
return { defs, instances };
}
/*
Event MUST have a recurringDef
*/
function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) {
let typeDef = recurringTypes[eventDef.recurringDef.typeId];
let markers = typeDef.expand(eventDef.recurringDef.typeData, {
start: dateEnv.subtract(framingRange.start, duration),
end: framingRange.end,
}, dateEnv);
// the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
if (eventDef.allDay) {
markers = markers.map(startOfDay);
}
return markers;
}
const EVENT_NON_DATE_REFINERS = {
id: String,
groupId: String,
title: String,
url: String,
interactive: Boolean,
};
const EVENT_DATE_REFINERS = {
start: identity,
end: identity,
date: identity,
allDay: Boolean,
};
const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity });
function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) {
let { refined, extra } = refineEventDef(raw, context, refiners);
let defaultAllDay = computeIsDefaultAllDay(eventSource, context);
let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes);
if (recurringRes) {
let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap);
def.recurringDef = {
typeId: recurringRes.typeId,
typeData: recurringRes.typeData,
duration: recurringRes.duration,
};
return { def, instance: null };
}
let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange);
if (singleRes) {
let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap);
let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo);
if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) {
instance.instanceId = instanceIdMap[def.publicId];
}
return { def, instance };
}
return null;
}
function refineEventDef(raw, context, refiners = buildEventRefiners(context)) {
return refineProps(raw, refiners);
}
function buildEventRefiners(context) {
return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners);
}
/*
Will NOT populate extendedProps with the leftover properties.
Will NOT populate date-related props.
*/
function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) {
let def = {
title: refined.title || '',
groupId: refined.groupId || '',
publicId: refined.id || '',
url: refined.url || '',
recurringDef: null,
defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(),
sourceId,
allDay,
hasEnd,
interactive: refined.interactive,
ui: createEventUi(refined, context),
extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra),
};
for (let memberAdder of context.pluginHooks.eventDefMemberAdders) {
Object.assign(def, memberAdder(refined));
}
// help out EventImpl from having user modify props
Object.freeze(def.ui.classNames);
Object.freeze(def.extendedProps);
return def;
}
function parseSingle(refined, defaultAllDay, context, allowOpenRange) {
let { allDay } = refined;
let startMeta;
let startMarker = null;
let hasEnd = false;
let endMeta;
let endMarker = null;
let startInput = refined.start != null ? refined.start : refined.date;
startMeta = context.dateEnv.createMarkerMeta(startInput);
if (startMeta) {
startMarker = startMeta.marker;
}
else if (!allowOpenRange) {
return null;
}
if (refined.end != null) {
endMeta = context.dateEnv.createMarkerMeta(refined.end);
}
if (allDay == null) {
if (defaultAllDay != null) {
allDay = defaultAllDay;
}
else {
// fall back to the date props LAST
allDay = (!startMeta || startMeta.isTimeUnspecified) &&
(!endMeta || endMeta.isTimeUnspecified);
}
}
if (allDay && startMarker) {
startMarker = startOfDay(startMarker);
}
if (endMeta) {
endMarker = endMeta.marker;
if (allDay) {
endMarker = startOfDay(endMarker);
}
if (startMarker && endMarker <= startMarker) {
endMarker = null;
}
}
if (endMarker) {
hasEnd = true;
}
else if (!allowOpenRange) {
hasEnd = context.options.forceEventDuration || false;
endMarker = context.dateEnv.add(startMarker, allDay ?
context.options.defaultAllDayEventDuration :
context.options.defaultTimedEventDuration);
}
return {
allDay,
hasEnd,
range: { start: startMarker, end: endMarker },
forcedStartTzo: startMeta ? startMeta.forcedTzo : null,
forcedEndTzo: endMeta ? endMeta.forcedTzo : null,
};
}
function computeIsDefaultAllDay(eventSource, context) {
let res = null;
if (eventSource) {
res = eventSource.defaultAllDay;
}
if (res == null) {
res = context.options.defaultAllDay;
}
return res;
}
function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) {
let eventStore = createEmptyEventStore();
let eventRefiners = buildEventRefiners(context);
for (let rawEvent of rawEvents) {
let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap);
if (tuple) {
eventTupleToStore(tuple, eventStore);
}
}
return eventStore;
}
function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) {
eventStore.defs[tuple.def.defId] = tuple.def;
if (tuple.instance) {
eventStore.instances[tuple.instance.instanceId] = tuple.instance;
}
return eventStore;
}
// retrieves events that have the same groupId as the instance specified by `instanceId`
// or they are the same as the instance.
// why might instanceId not be in the store? an event from another calendar?
function getRelevantEvents(eventStore, instanceId) {
let instance = eventStore.instances[instanceId];
if (instance) {
let def = eventStore.defs[instance.defId];
// get events/instances with same group
let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef));
// add the original
// TODO: wish we could use eventTupleToStore or something like it
newStore.defs[def.defId] = def;
newStore.instances[instance.instanceId] = instance;
return newStore;
}
return createEmptyEventStore();
}
function isEventDefsGrouped(def0, def1) {
return Boolean(def0.groupId && def0.groupId === def1.groupId);
}
function createEmptyEventStore() {
return { defs: {}, instances: {} };
}
function mergeEventStores(store0, store1) {
return {
defs: Object.assign(Object.assign({}, store0.defs), store1.defs),
instances: Object.assign(Object.assign({}, store0.instances), store1.instances),
};
}
function filterEventStoreDefs(eventStore, filterFunc) {
let defs = filterHash(eventStore.defs, filterFunc);
let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists?
));
return { defs, instances };
}
function excludeSubEventStore(master, sub) {
let { defs, instances } = master;
let filteredDefs = {};
let filteredInstances = {};
for (let defId in defs) {
if (!sub.defs[defId]) { // not explicitly excluded
filteredDefs[defId] = defs[defId];
}
}
for (let instanceId in instances) {
if (!sub.instances[instanceId] && // not explicitly excluded
filteredDefs[instances[instanceId].defId] // def wasn't filtered away
) {
filteredInstances[instanceId] = instances[instanceId];
}
}
return {
defs: filteredDefs,
instances: filteredInstances,
};
}
function normalizeConstraint(input, context) {
if (Array.isArray(input)) {
return parseEvents(input, null, context, true); // allowOpenRange=true
}
if (typeof input === 'object' && input) { // non-null object
return parseEvents([input], null, context, true); // allowOpenRange=true
}
if (input != null) {
return String(input);
}
return null;
}
function parseClassNames(raw) {
if (Array.isArray(raw)) {
return raw;
}
if (typeof raw === 'string') {
return raw.split(/\s+/);
}
return [];
}
// TODO: better called "EventSettings" or "EventConfig"
// TODO: move this file into structs
// TODO: separate constraint/overlap/allow, because selection uses only that, not other props
const EVENT_UI_REFINERS = {
display: String,
editable: Boolean,
startEditable: Boolean,
durationEditable: Boolean,
constraint: identity,
overlap: identity,
allow: identity,
className: parseClassNames,
classNames: parseClassNames,
color: String,
backgroundColor: String,
borderColor: String,
textColor: String,
};
const EMPTY_EVENT_UI = {
display: null,
startEditable: null,
durationEditable: null,
constraints: [],
overlap: null,
allows: [],
backgroundColor: '',
borderColor: '',
textColor: '',
classNames: [],
};
function createEventUi(refined, context) {
let constraint = normalizeConstraint(refined.constraint, context);
return {
display: refined.display || null,
startEditable: refined.startEditable != null ? refined.startEditable : refined.editable,
durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable,
constraints: constraint != null ? [constraint] : [],
overlap: refined.overlap != null ? refined.overlap : null,
allows: refined.allow != null ? [refined.allow] : [],
backgroundColor: refined.backgroundColor || refined.color || '',
borderColor: refined.borderColor || refined.color || '',
textColor: refined.textColor || '',
classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural
};
}
// TODO: prevent against problems with <2 args!
function combineEventUis(uis) {
return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI);
}
function combineTwoEventUis(item0, item1) {
return {
display: item1.display != null ? item1.display : item0.display,
startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
constraints: item0.constraints.concat(item1.constraints),
overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
allows: item0.allows.concat(item1.allows),
backgroundColor: item1.backgroundColor || item0.backgroundColor,
borderColor: item1.borderColor || item0.borderColor,
textColor: item1.textColor || item0.textColor,
classNames: item0.classNames.concat(item1.classNames),
};
}
const EVENT_SOURCE_REFINERS = {
id: String,
defaultAllDay: Boolean,
url: String,
format: String,
events: identity,
eventDataTransform: identity,
// for any network-related sources
success: identity,
failure: identity,
};
function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) {
let rawObj;
if (typeof raw === 'string') {
rawObj = { url: raw };
}
else if (typeof raw === 'function' || Array.isArray(raw)) {
rawObj = { events: raw };
}
else if (typeof raw === 'object' && raw) { // not null
rawObj = raw;
}
if (rawObj) {
let { refined, extra } = refineProps(rawObj, refiners);
let metaRes = buildEventSourceMeta(refined, context);
if (metaRes) {
return {
_raw: raw,
isFetching: false,
latestFetchId: '',
fetchRange: null,
defaultAllDay: refined.defaultAllDay,
eventDataTransform: refined.eventDataTransform,
success: refined.success,
failure: refined.failure,
publicId: refined.id || '',
sourceId: guid(),
sourceDefId: metaRes.sourceDefId,
meta: metaRes.meta,
ui: createEventUi(refined, context),
extendedProps: extra,
};
}
}
return null;
}
function buildEventSourceRefiners(context) {
return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners);
}
function buildEventSourceMeta(raw, context) {
let defs = context.pluginHooks.eventSourceDefs;
for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence
let def = defs[i];
let meta = def.parseMeta(raw);
if (meta) {
return { sourceDefId: i, meta };
}
}
return null;
}
function reduceEventStore(eventStore, action, eventSources, dateProfile, context) {
switch (action.type) {
case 'RECEIVE_EVENTS': // raw
return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context);
case 'RESET_RAW_EVENTS':
return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context);
case 'ADD_EVENTS': // already parsed, but not expanded
return addEvent(eventStore, action.eventStore, // new ones
dateProfile ? dateProfile.activeRange : null, context);
case 'RESET_EVENTS':
return action.eventStore;
case 'MERGE_EVENTS': // already parsed and expanded
return mergeEventStores(eventStore, action.eventStore);
case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
case 'NEXT':
case 'CHANGE_DATE':
case 'CHANGE_VIEW_TYPE':
if (dateProfile) {
return expandRecurring(eventStore, dateProfile.activeRange, context);
}
return eventStore;
case 'REMOVE_EVENTS':
return excludeSubEventStore(eventStore, action.eventStore);
case 'REMOVE_EVENT_SOURCE':
return excludeEventsBySourceId(eventStore, action.sourceId);
case 'REMOVE_ALL_EVENT_SOURCES':
return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id
));
case 'REMOVE_ALL_EVENTS':
return createEmptyEventStore();
default:
return eventStore;
}
}
function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) {
if (eventSource && // not already removed
fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources
) {
let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context);
if (fetchRange) {
subset = expandRecurring(subset, fetchRange, context);
}
return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset);
}
return eventStore;
}
function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) {
const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore);
let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap);
return expandRecurring(newEventStore, activeRange, context);
}
function transformRawEvents(rawEvents, eventSource, context) {
let calEachTransform = context.options.eventDataTransform;
let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null;
if (sourceEachTransform) {
rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform);
}
if (calEachTransform) {
rawEvents = transformEachRawEvent(rawEvents, calEachTransform);
}
return rawEvents;
}
function transformEachRawEvent(rawEvents, func) {
let refinedEvents;
if (!func) {
refinedEvents = rawEvents;
}
else {
refinedEvents = [];
for (let rawEvent of rawEvents) {
let refinedEvent = func(rawEvent);
if (refinedEvent) {
refinedEvents.push(refinedEvent);
}
else if (refinedEvent == null) {
refinedEvents.push(rawEvent);
} // if a different falsy value, do nothing
}
}
return refinedEvents;
}
function addEvent(eventStore, subset, expandRange, context) {
if (expandRange) {
subset = expandRecurring(subset, expandRange, context);
}
return mergeEventStores(eventStore, subset);
}
function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) {
let { defs } = eventStore;
let instances = mapHash(eventStore.instances, (instance) => {
let def = defs[instance.defId];
if (def.allDay) {
return instance; // isn't dependent on timezone
}
return Object.assign(Object.assign({}, instance), { range: {
start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)),
end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)),
}, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo });
});
return { defs, instances };
}
function excludeEventsBySourceId(eventStore, sourceId) {
return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId);
}
// QUESTION: why not just return instances? do a general object-property-exclusion util
function excludeInstances(eventStore, removals) {
return {
defs: eventStore.defs,
instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]),
};
}
function buildPublicIdMaps(eventStore) {
const { defs, instances } = eventStore;
const defIdMap = {};
const instanceIdMap = {};
for (let defId in defs) {
const def = defs[defId];
const { publicId } = def;
if (publicId) {
defIdMap[publicId] = defId;
}
}
for (let instanceId in instances) {
const instance = instances[instanceId];
const def = defs[instance.defId];
const { publicId } = def;
if (publicId) {
instanceIdMap[publicId] = instanceId;
}
}
return { defIdMap, instanceIdMap };
}
class Emitter {
constructor() {
this.handlers = {};
this.thisContext = null;
}
setThisContext(thisContext) {
this.thisContext = thisContext;
}
setOptions(options) {
this.options = options;
}
on(type, handler) {
addToHash(this.handlers, type, handler);
}
off(type, handler) {
removeFromHash(this.handlers, type, handler);
}
trigger(type, ...args) {
let attachedHandlers = this.handlers[type] || [];
let optionHandler = this.options && this.options[type];
let handlers = [].concat(optionHandler || [], attachedHandlers);
for (let handler of handlers) {
handler.apply(this.thisContext, args);
}
}
hasHandlers(type) {
return Boolean((this.handlers[type] && this.handlers[type].length) ||
(this.options && this.options[type]));
}
}
function addToHash(hash, type, handler) {
(hash[type] || (hash[type] = []))
.push(handler);
}
function removeFromHash(hash, type, handler) {
if (handler) {
if (hash[type]) {
hash[type] = hash[type].filter((func) => func !== handler);
}
}
else {
delete hash[type]; // remove all handler funcs for this type
}
}
const DEF_DEFAULTS = {
startTime: '09:00',
endTime: '17:00',
daysOfWeek: [1, 2, 3, 4, 5],
display: 'inverse-background',
classNames: 'fc-non-business',
groupId: '_businessHours', // so multiple defs get grouped
};
/*
TODO: pass around as EventDefHash!!!
*/
function parseBusinessHours(input, context) {
return parseEvents(refineInputs(input), null, context);
}
function refineInputs(input) {
let rawDefs;
if (input === true) {
rawDefs = [{}]; // will get DEF_DEFAULTS verbatim
}
else if (Array.isArray(input)) {
// if specifying an array, every sub-definition NEEDS a day-of-week
rawDefs = input.filter((rawDef) => rawDef.daysOfWeek);
}
else if (typeof input === 'object' && input) { // non-null object
rawDefs = [input];
}
else { // is probably false
rawDefs = [];
}
rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef)));
return rawDefs;
}
function triggerDateSelect(selection, pev, context) {
context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view }));
}
function triggerDateUnselect(pev, context) {
context.emitter.trigger('unselect', {
jsEvent: pev ? pev.origEvent : null,
view: context.viewApi || context.calendarApi.view,
});
}
function buildDateSpanApiWithContext(dateSpan, context) {
let props = {};
for (let transform of context.pluginHooks.dateSpanTransforms) {
Object.assign(props, transform(dateSpan, context));
}
Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv));
return props;
}
// Given an event's allDay status and start date, return what its fallback end date should be.
// TODO: rename to computeDefaultEventEnd
function getDefaultEventEnd(allDay, marker, context) {
let { dateEnv, options } = context;
let end = marker;
if (allDay) {
end = startOfDay(end);
end = dateEnv.add(end, options.defaultAllDayEventDuration);
}
else {
end = dateEnv.add(end, options.defaultTimedEventDuration);
}
return end;
}
// applies the mutation to ALL defs/instances within the event store
function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) {
let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase);
let dest = createEmptyEventStore();
for (let defId in eventStore.defs) {
let def = eventStore.defs[defId];
dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context);
}
for (let instanceId in eventStore.instances) {
let instance = eventStore.instances[instanceId];
let def = dest.defs[instance.defId]; // important to grab the newly modified def
dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context);
}
return dest;
}
function applyMutationToEventDef(eventDef, eventConfig, mutation, context) {
let standardProps = mutation.standardProps || {};
// if hasEnd has not been specified, guess a good value based on deltas.
// if duration will change, there's no way the default duration will persist,
// and thus, we need to mark the event as having a real end
if (standardProps.hasEnd == null &&
eventConfig.durationEditable &&
(mutation.startDelta || mutation.endDelta)) {
standardProps.hasEnd = true; // TODO: is this mutation okay?
}
let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) });
if (mutation.extendedProps) {
copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps);
}
for (let applier of context.pluginHooks.eventDefMutationAppliers) {
applier(copy, mutation, context);
}
if (!copy.hasEnd && context.options.forceEventDuration) {
copy.hasEnd = true;
}
return copy;
}
function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef
eventConfig, mutation, context) {
let { dateEnv } = context;
let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true;
let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false;
let copy = Object.assign({}, eventInstance);
if (forceAllDay) {
copy.range = computeAlignedDayRange(copy.range);
}
if (mutation.datesDelta && eventConfig.startEditable) {
copy.range = {
start: dateEnv.add(copy.range.start, mutation.datesDelta),
end: dateEnv.add(copy.range.end, mutation.datesDelta),
};
}
if (mutation.startDelta && eventConfig.durationEditable) {
copy.range = {
start: dateEnv.add(copy.range.start, mutation.startDelta),
end: copy.range.end,
};
}
if (mutation.endDelta && eventConfig.durationEditable) {
copy.range = {
start: copy.range.start,
end: dateEnv.add(copy.range.end, mutation.endDelta),
};
}
if (clearEnd) {
copy.range = {
start: copy.range.start,
end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context),
};
}
// in case event was all-day but the supplied deltas were not
// better util for this?
if (eventDef.allDay) {
copy.range = {
start: startOfDay(copy.range.start),
end: startOfDay(copy.range.end),
};
}
// handle invalid durations
if (copy.range.end < copy.range.start) {
copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context);
}
return copy;
}
class EventSourceImpl {
constructor(context, internalEventSource) {
this.context = context;
this.internalEventSource = internalEventSource;
}
remove() {
this.context.dispatch({
type: 'REMOVE_EVENT_SOURCE',
sourceId: this.internalEventSource.sourceId,
});
}
refetch() {
this.context.dispatch({
type: 'FETCH_EVENT_SOURCES',
sourceIds: [this.internalEventSource.sourceId],
isRefetch: true,
});
}
get id() {
return this.internalEventSource.publicId;
}
get url() {
return this.internalEventSource.meta.url;
}
get format() {
return this.internalEventSource.meta.format; // TODO: bad. not guaranteed
}
}
class EventImpl {
// instance will be null if expressing a recurring event that has no current instances,
// OR if trying to validate an incoming external event that has no dates assigned
constructor(context, def, instance) {
this._context = context;
this._def = def;
this._instance = instance || null;
}
/*
TODO: make event struct more responsible for this
*/
setProp(name, val) {
if (name in EVENT_DATE_REFINERS) {
console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.');
// TODO: make proper aliasing system?
}
else if (name === 'id') {
val = EVENT_NON_DATE_REFINERS[name](val);
this.mutate({
standardProps: { publicId: val }, // hardcoded internal name
});
}
else if (name in EVENT_NON_DATE_REFINERS) {
val = EVENT_NON_DATE_REFINERS[name](val);
this.mutate({
standardProps: { [name]: val },
});
}
else if (name in EVENT_UI_REFINERS) {
let ui = EVENT_UI_REFINERS[name](val);
if (name === 'color') {
ui = { backgroundColor: val, borderColor: val };
}
else if (name === 'editable') {
ui = { startEditable: val, durationEditable: val };
}
else {
ui = { [name]: val };
}
this.mutate({
standardProps: { ui },
});
}
else {
console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`);
}
}
setExtendedProp(name, val) {
this.mutate({
extendedProps: { [name]: val },
});
}
setStart(startInput, options = {}) {
let { dateEnv } = this._context;
let start = dateEnv.createMarker(startInput);
if (start && this._instance) { // TODO: warning if parsed bad
let instanceRange = this._instance.range;
let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!?
if (options.maintainDuration) {
this.mutate({ datesDelta: startDelta });
}
else {
this.mutate({ startDelta });
}
}
}
setEnd(endInput, options = {}) {
let { dateEnv } = this._context;
let end;
if (endInput != null) {
end = dateEnv.createMarker(endInput);
if (!end) {
return; // TODO: warning if parsed bad
}
}
if (this._instance) {
if (end) {
let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity);
this.mutate({ endDelta });
}
else {
this.mutate({ standardProps: { hasEnd: false } });
}
}
}
setDates(startInput, endInput, options = {}) {
let { dateEnv } = this._context;
let standardProps = { allDay: options.allDay };
let start = dateEnv.createMarker(startInput);
let end;
if (!start) {
return; // TODO: warning if parsed bad
}
if (endInput != null) {
end = dateEnv.createMarker(endInput);
if (!end) { // TODO: warning if parsed bad
return;
}
}
if (this._instance) {
let instanceRange = this._instance.range;
// when computing the diff for an event being converted to all-day,
// compute diff off of the all-day values the way event-mutation does.
if (options.allDay === true) {
instanceRange = computeAlignedDayRange(instanceRange);
}
let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity);
if (end) {
let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity);
if (durationsEqual(startDelta, endDelta)) {
this.mutate({ datesDelta: startDelta, standardProps });
}
else {
this.mutate({ startDelta, endDelta, standardProps });
}
}
else { // means "clear the end"
standardProps.hasEnd = false;
this.mutate({ datesDelta: startDelta, standardProps });
}
}
}
moveStart(deltaInput) {
let delta = createDuration(deltaInput);
if (delta) { // TODO: warning if parsed bad
this.mutate({ startDelta: delta });
}
}
moveEnd(deltaInput) {
let delta = createDuration(deltaInput);
if (delta) { // TODO: warning if parsed bad
this.mutate({ endDelta: delta });
}
}
moveDates(deltaInput) {
let delta = createDuration(deltaInput);
if (delta) { // TODO: warning if parsed bad
this.mutate({ datesDelta: delta });
}
}
setAllDay(allDay, options = {}) {
let standardProps = { allDay };
let { maintainDuration } = options;
if (maintainDuration == null) {
maintainDuration = this._context.options.allDayMaintainDuration;
}
if (this._def.allDay !== allDay) {
standardProps.hasEnd = maintainDuration;
}
this.mutate({ standardProps });
}
formatRange(formatInput) {
let { dateEnv } = this._context;
let instance = this._instance;
let formatter = createFormatter(formatInput);
if (this._def.hasEnd) {
return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {
forcedStartTzo: instance.forcedStartTzo,
forcedEndTzo: instance.forcedEndTzo,
});
}
return dateEnv.format(instance.range.start, formatter, {
forcedTzo: instance.forcedStartTzo,
});
}
mutate(mutation) {
let instance = this._instance;
if (instance) {
let def = this._def;
let context = this._context;
let { eventStore } = context.getCurrentData();
let relevantEvents = getRelevantEvents(eventStore, instance.instanceId);
let eventConfigBase = {
'': {
display: '',
startEditable: true,
durationEditable: true,
constraints: [],
overlap: null,
allows: [],
backgroundColor: '',
borderColor: '',
textColor: '',
classNames: [],
},
};
relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context);
let oldEvent = new EventImpl(context, def, instance); // snapshot
this._def = relevantEvents.defs[def.defId];
this._instance = relevantEvents.instances[instance.instanceId];
context.dispatch({
type: 'MERGE_EVENTS',
eventStore: relevantEvents,
});
context.emitter.trigger('eventChange', {
oldEvent,
event: this,
relatedEvents: buildEventApis(relevantEvents, context, instance),
revert() {
context.dispatch({
type: 'RESET_EVENTS',
eventStore, // the ORIGINAL store
});
},
});
}
}
remove() {
let context = this._context;
let asStore = eventApiToStore(this);
context.dispatch({
type: 'REMOVE_EVENTS',
eventStore: asStore,
});
context.emitter.trigger('eventRemove', {
event: this,
relatedEvents: [],
revert() {
context.dispatch({
type: 'MERGE_EVENTS',
eventStore: asStore,
});
},
});
}
get source() {
let { sourceId } = this._def;
if (sourceId) {
return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]);
}
return null;
}
get start() {
return this._instance ?
this._context.dateEnv.toDate(this._instance.range.start) :
null;
}
get end() {
return (this._instance && this._def.hasEnd) ?
this._context.dateEnv.toDate(this._instance.range.end) :
null;
}
get startStr() {
let instance = this._instance;
if (instance) {
return this._context.dateEnv.formatIso(instance.range.start, {
omitTime: this._def.allDay,
forcedTzo: instance.forcedStartTzo,
});
}
return '';
}
get endStr() {
let instance = this._instance;
if (instance && this._def.hasEnd) {
return this._context.dateEnv.formatIso(instance.range.end, {
omitTime: this._def.allDay,
forcedTzo: instance.forcedEndTzo,
});
}
return '';
}
// computable props that all access the def
// TODO: find a TypeScript-compatible way to do this at scale
get id() { return this._def.publicId; }
get groupId() { return this._def.groupId; }
get allDay() { return this._def.allDay; }
get title() { return this._def.title; }
get url() { return this._def.url; }
get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier
get startEditable() { return this._def.ui.startEditable; }
get durationEditable() { return this._def.ui.durationEditable; }
get constraint() { return this._def.ui.constraints[0] || null; }
get overlap() { return this._def.ui.overlap; }
get allow() { return this._def.ui.allows[0] || null; }
get backgroundColor() { return this._def.ui.backgroundColor; }
get borderColor() { return this._def.ui.borderColor; }
get textColor() { return this._def.ui.textColor; }
// NOTE: user can't modify these because Object.freeze was called in event-def parsing
get classNames() { return this._def.ui.classNames; }
get extendedProps() { return this._def.extendedProps; }
toPlainObject(settings = {}) {
let def = this._def;
let { ui } = def;
let { startStr, endStr } = this;
let res = {
allDay: def.allDay,
};
if (def.title) {
res.title = def.title;
}
if (startStr) {
res.start = startStr;
}
if (endStr) {
res.end = endStr;
}
if (def.publicId) {
res.id = def.publicId;
}
if (def.groupId) {
res.groupId = def.groupId;
}
if (def.url) {
res.url = def.url;
}
if (ui.display && ui.display !== 'auto') {
res.display = ui.display;
}
// TODO: what about recurring-event properties???
// TODO: include startEditable/durationEditable/constraint/overlap/allow
if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) {
res.color = ui.backgroundColor;
}
else {
if (ui.backgroundColor) {
res.backgroundColor = ui.backgroundColor;
}
if (ui.borderColor) {
res.borderColor = ui.borderColor;
}
}
if (ui.textColor) {
res.textColor = ui.textColor;
}
if (ui.classNames.length) {
res.classNames = ui.classNames;
}
if (Object.keys(def.extendedProps).length) {
if (settings.collapseExtendedProps) {
Object.assign(res, def.extendedProps);
}
else {
res.extendedProps = def.extendedProps;
}
}
return res;
}
toJSON() {
return this.toPlainObject();
}
}
function eventApiToStore(eventApi) {
let def = eventApi._def;
let instance = eventApi._instance;
return {
defs: { [def.defId]: def },
instances: instance
? { [instance.instanceId]: instance }
: {},
};
}
function buildEventApis(eventStore, context, excludeInstance) {
let { defs, instances } = eventStore;
let eventApis = [];
let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : '';
for (let id in instances) {
let instance = instances[id];
let def = defs[instance.defId];
if (instance.instanceId !== excludeInstanceId) {
eventApis.push(new EventImpl(context, def, instance));
}
}
return eventApis;
}
/*
Specifying nextDayThreshold signals that all-day ranges should be sliced.
*/
function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) {
let inverseBgByGroupId = {};
let inverseBgByDefId = {};
let defByGroupId = {};
let bgRanges = [];
let fgRanges = [];
let eventUis = compileEventUis(eventStore.defs, eventUiBases);
for (let defId in eventStore.defs) {
let def = eventStore.defs[defId];
let ui = eventUis[def.defId];
if (ui.display === 'inverse-background') {
if (def.groupId) {
inverseBgByGroupId[def.groupId] = [];
if (!defByGroupId[def.groupId]) {
defByGroupId[def.groupId] = def;
}
}
else {
inverseBgByDefId[defId] = [];
}
}
}
for (let instanceId in eventStore.instances) {
let instance = eventStore.instances[instanceId];
let def = eventStore.defs[instance.defId];
let ui = eventUis[def.defId];
let origRange = instance.range;
let normalRange = (!def.allDay && nextDayThreshold) ?
computeVisibleDayRange(origRange, nextDayThreshold) :
origRange;
let slicedRange = intersectRanges(normalRange, framingRange);
if (slicedRange) {
if (ui.display === 'inverse-background') {
if (def.groupId) {
inverseBgByGroupId[def.groupId].push(slicedRange);
}
else {
inverseBgByDefId[instance.defId].push(slicedRange);
}
}
else if (ui.display !== 'none') {
(ui.display === 'background' ? bgRanges : fgRanges).push({
def,
ui,
instance,
range: slicedRange,
isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(),
isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(),
});
}
}
}
for (let groupId in inverseBgByGroupId) { // BY GROUP
let ranges = inverseBgByGroupId[groupId];
let invertedRanges = invertRanges(ranges, framingRange);
for (let invertedRange of invertedRanges) {
let def = defByGroupId[groupId];
let ui = eventUis[def.defId];
bgRanges.push({
def,
ui,
instance: null,
range: invertedRange,
isStart: false,
isEnd: false,
});
}
}
for (let defId in inverseBgByDefId) {
let ranges = inverseBgByDefId[defId];
let invertedRanges = invertRanges(ranges, framingRange);
for (let invertedRange of invertedRanges) {
bgRanges.push({
def: eventStore.defs[defId],
ui: eventUis[defId],
instance: null,
range: invertedRange,
isStart: false,
isEnd: false,
});
}
}
return { bg: bgRanges, fg: fgRanges };
}
function hasBgRendering(def) {
return def.ui.display === 'background' || def.ui.display === 'inverse-background';
}
function setElSeg(el, seg) {
el.fcSeg = seg;
}
function getElSeg(el) {
return el.fcSeg ||
el.parentNode.fcSeg || // for the harness
null;
}
// event ui computation
function compileEventUis(eventDefs, eventUiBases) {
return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases));
}
function compileEventUi(eventDef, eventUiBases) {
let uis = [];
if (eventUiBases['']) {
uis.push(eventUiBases['']);
}
if (eventUiBases[eventDef.defId]) {
uis.push(eventUiBases[eventDef.defId]);
}
uis.push(eventDef.ui);
return combineEventUis(uis);
}
function sortEventSegs(segs, eventOrderSpecs) {
let objs = segs.map(buildSegCompareObj);
objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs));
return objs.map((c) => c._seg);
}
// returns a object with all primitive props that can be compared
function buildSegCompareObj(seg) {
let { eventRange } = seg;
let eventDef = eventRange.def;
let range = eventRange.instance ? eventRange.instance.range : eventRange.range;
let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events
let end = range.end ? range.end.valueOf() : 0; // "
return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start,
end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg });
}
function computeSegDraggable(seg, context) {
let { pluginHooks } = context;
let transformers = pluginHooks.isDraggableTransformers;
let { def, ui } = seg.eventRange;
let val = ui.startEditable;
for (let transformer of transformers) {
val = transformer(val, def, ui, context);
}
return val;
}
function computeSegStartResizable(seg, context) {
return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart;
}
function computeSegEndResizable(seg, context) {
return seg.isEnd && seg.eventRange.ui.durationEditable;
}
function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true
defaultDisplayEventEnd, // defaults to true
startOverride, endOverride) {
let { dateEnv, options } = context;
let { displayEventTime, displayEventEnd } = options;
let eventDef = seg.eventRange.def;
let eventInstance = seg.eventRange.instance;
if (displayEventTime == null) {
displayEventTime = defaultDisplayEventTime !== false;
}
if (displayEventEnd == null) {
displayEventEnd = defaultDisplayEventEnd !== false;
}
let wholeEventStart = eventInstance.range.start;
let wholeEventEnd = eventInstance.range.end;
let segStart = startOverride || seg.start || seg.eventRange.range.start;
let segEnd = endOverride || seg.end || seg.eventRange.range.end;
let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf();
let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf();
if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) {
segStart = isStartDay ? wholeEventStart : segStart;
segEnd = isEndDay ? wholeEventEnd : segEnd;
if (displayEventEnd && eventDef.hasEnd) {
return dateEnv.formatRange(segStart, segEnd, timeFormat, {
forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo,
forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo,
});
}
return dateEnv.format(segStart, timeFormat, {
forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same
});
}
return '';
}
function getSegMeta(seg, todayRange, nowDate) {
let segRange = seg.eventRange.range;
return {
isPast: segRange.end <= (nowDate || todayRange.start),
isFuture: segRange.start >= (nowDate || todayRange.end),
isToday: todayRange && rangeContainsMarker(todayRange, segRange.start),
};
}
function getEventClassNames(props) {
let classNames = ['fc-event'];
if (props.isMirror) {
classNames.push('fc-event-mirror');
}
if (props.isDraggable) {
classNames.push('fc-event-draggable');
}
if (props.isStartResizable || props.isEndResizable) {
classNames.push('fc-event-resizable');
}
if (props.isDragging) {
classNames.push('fc-event-dragging');
}
if (props.isResizing) {
classNames.push('fc-event-resizing');
}
if (props.isSelected) {
classNames.push('fc-event-selected');
}
if (props.isStart) {
classNames.push('fc-event-start');
}
if (props.isEnd) {
classNames.push('fc-event-end');
}
if (props.isPast) {
classNames.push('fc-event-past');
}
if (props.isToday) {
classNames.push('fc-event-today');
}
if (props.isFuture) {
classNames.push('fc-event-future');
}
return classNames;
}
function buildEventRangeKey(eventRange) {
return eventRange.instance
? eventRange.instance.instanceId
: `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`;
// inverse-background events don't have specific instances. TODO: better solution
}
function getSegAnchorAttrs(seg, context) {
let { def, instance } = seg.eventRange;
let { url } = def;
if (url) {
return { href: url };
}
let { emitter, options } = context;
let { eventInteractive } = options;
if (eventInteractive == null) {
eventInteractive = def.interactive;
if (eventInteractive == null) {
eventInteractive = Boolean(emitter.hasHandlers('eventClick'));
}
}
// mock what happens in EventClicking
if (eventInteractive) {
// only attach keyboard-related handlers because click handler is already done in EventClicking
return createAriaKeyboardAttrs((ev) => {
emitter.trigger('eventClick', {
el: ev.target,
event: new EventImpl(context, def, instance),
jsEvent: ev,
view: context.viewApi,
});
});
}
return {};
}
const STANDARD_PROPS = {
start: identity,
end: identity,
allDay: Boolean,
};
function parseDateSpan(raw, dateEnv, defaultDuration) {
let span = parseOpenDateSpan(raw, dateEnv);
let { range } = span;
if (!range.start) {
return null;
}
if (!range.end) {
if (defaultDuration == null) {
return null;
}
range.end = dateEnv.add(range.start, defaultDuration);
}
return span;
}
/*
TODO: somehow combine with parseRange?
Will return null if the start/end props were present but parsed invalidly.
*/
function parseOpenDateSpan(raw, dateEnv) {
let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS);
let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null;
let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null;
let { allDay } = standardProps;
if (allDay == null) {
allDay = (startMeta && startMeta.isTimeUnspecified) &&
(!endMeta || endMeta.isTimeUnspecified);
}
return Object.assign({ range: {
start: startMeta ? startMeta.marker : null,
end: endMeta ? endMeta.marker : null,
}, allDay }, extra);
}
function isDateSpansEqual(span0, span1) {
return rangesEqual(span0.range, span1.range) &&
span0.allDay === span1.allDay &&
isSpanPropsEqual(span0, span1);
}
// the NON-DATE-RELATED props
function isSpanPropsEqual(span0, span1) {
for (let propName in span1) {
if (propName !== 'range' && propName !== 'allDay') {
if (span0[propName] !== span1[propName]) {
return false;
}
}
}
// are there any props that span0 has that span1 DOESN'T have?
// both have range/allDay, so no need to special-case.
for (let propName in span0) {
if (!(propName in span1)) {
return false;
}
}
return true;
}
function buildDateSpanApi(span, dateEnv) {
return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay });
}
function buildRangeApiWithTimeZone(range, dateEnv, omitTime) {
return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone });
}
function buildRangeApi(range, dateEnv, omitTime) {
return {
start: dateEnv.toDate(range.start),
end: dateEnv.toDate(range.end),
startStr: dateEnv.formatIso(range.start, { omitTime }),
endStr: dateEnv.formatIso(range.end, { omitTime }),
};
}
function fabricateEventRange(dateSpan, eventUiBases, context) {
let res = refineEventDef({ editable: false }, context);
let def = parseEventDef(res.refined, res.extra, '', // sourceId
dateSpan.allDay, true, // hasEnd
context);
return {
def,
ui: compileEventUi(def, eventUiBases),
instance: createEventInstance(def.defId, dateSpan.range),
range: dateSpan.range,
isStart: true,
isEnd: true,
};
}
/*
given a function that resolves a result asynchronously.
the function can either call passed-in success and failure callbacks,
or it can return a promise.
if you need to pass additional params to func, bind them first.
*/
function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) {
// guard against success/failure callbacks being called more than once
// and guard against a promise AND callback being used together.
let isResolved = false;
let wrappedSuccess = function (res) {
if (!isResolved) {
isResolved = true;
normalizedSuccessCallback(res);
}
};
let wrappedFailure = function (error) {
if (!isResolved) {
isResolved = true;
normalizedFailureCallback(error);
}
};
let res = func(wrappedSuccess, wrappedFailure);
if (res && typeof res.then === 'function') {
res.then(wrappedSuccess, wrappedFailure);
}
}
class JsonRequestError extends Error {
constructor(message, response) {
super(message);
this.response = response;
}
}
function requestJson(method, url, params) {
method = method.toUpperCase();
const fetchOptions = {
method,
};
if (method === 'GET') {
url += (url.indexOf('?') === -1 ? '?' : '&') +
new URLSearchParams(params);
}
else {
fetchOptions.body = new URLSearchParams(params);
fetchOptions.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
}
return fetch(url, fetchOptions).then((fetchRes) => {
if (fetchRes.ok) {
return fetchRes.json().then((parsedResponse) => {
return [parsedResponse, fetchRes];
}, () => {
throw new JsonRequestError('Failure parsing JSON', fetchRes);
});
}
else {
throw new JsonRequestError('Request failed', fetchRes);
}
});
}
let canVGrowWithinCell;
function getCanVGrowWithinCell() {
if (canVGrowWithinCell == null) {
canVGrowWithinCell = computeCanVGrowWithinCell();
}
return canVGrowWithinCell;
}
function computeCanVGrowWithinCell() {
// for SSR, because this function is call immediately at top-level
// TODO: just make this logic execute top-level, immediately, instead of doing lazily
if (typeof document === 'undefined') {
return true;
}
let el = document.createElement('div');
el.style.position = 'absolute';
el.style.top = '0px';
el.style.left = '0px';
el.innerHTML = '<table><tr><td><div></div></td></tr></table>';
el.querySelector('table').style.height = '100px';
el.querySelector('div').style.height = '100%';
document.body.appendChild(el);
let div = el.querySelector('div');
let possible = div.offsetHeight > 0;
document.body.removeChild(el);
return possible;
}
class CalendarRoot extends BaseComponent {
constructor() {
super(...arguments);
this.state = {
forPrint: false,
};
this.handleBeforePrint = () => {
flushSync(() => {
this.setState({ forPrint: true });
});
};
this.handleAfterPrint = () => {
flushSync(() => {
this.setState({ forPrint: false });
});
};
}
render() {
let { props } = this;
let { options } = props;
let { forPrint } = this.state;
let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto';
let height = (!isHeightAuto && options.height != null) ? options.height : '';
let classNames = [
'fc',
forPrint ? 'fc-media-print' : 'fc-media-screen',
`fc-direction-${options.direction}`,
props.theme.getClass('root'),
];
if (!getCanVGrowWithinCell()) {
classNames.push('fc-liquid-hack');
}
return props.children(classNames, height, isHeightAuto, forPrint);
}
componentDidMount() {
let { emitter } = this.props;
emitter.on('_beforeprint', this.handleBeforePrint);
emitter.on('_afterprint', this.handleAfterPrint);
}
componentWillUnmount() {
let { emitter } = this.props;
emitter.off('_beforeprint', this.handleBeforePrint);
emitter.off('_afterprint', this.handleAfterPrint);
}
}
class internal_common_Interaction {
constructor(settings) {
this.component = settings.component;
this.isHitComboAllowed = settings.isHitComboAllowed || null;
}
destroy() {
}
}
function parseInteractionSettings(component, input) {
return {
component,
el: input.el,
useEventCenter: input.useEventCenter != null ? input.useEventCenter : true,
isHitComboAllowed: input.isHitComboAllowed || null,
};
}
function interactionSettingsToStore(settings) {
return {
[settings.component.uid]: settings,
};
}
// global state
const interactionSettingsStore = {};
class NowTimer extends x {
constructor(props, context) {
super(props, context);
this.handleRefresh = () => {
let timing = this.computeTiming();
if (timing.state.nowDate.valueOf() !== this.state.nowDate.valueOf()) {
this.setState(timing.state);
}
this.clearTimeout();
this.setTimeout(timing.waitMs);
};
this.handleVisibilityChange = () => {
if (!document.hidden) {
this.handleRefresh();
}
};
this.state = this.computeTiming().state;
}
render() {
let { props, state } = this;
return props.children(state.nowDate, state.todayRange);
}
componentDidMount() {
this.setTimeout();
this.context.nowManager.addResetListener(this.handleRefresh);
// fired tab becomes visible after being hidden
document.addEventListener('visibilitychange', this.handleVisibilityChange);
}
componentDidUpdate(prevProps) {
if (prevProps.unit !== this.props.unit) {
this.clearTimeout();
this.setTimeout();
}
}
componentWillUnmount() {
this.clearTimeout();
this.context.nowManager.removeResetListener(this.handleRefresh);
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
computeTiming() {
let { props, context } = this;
let unroundedNow = context.nowManager.getDateMarker();
let { nowIndicatorSnap } = context.options;
if (nowIndicatorSnap === 'auto') {
nowIndicatorSnap =
// large unit?
/year|month|week|day/.test(props.unit) ||
// if slotDuration 30 mins for example, would NOT appear to snap (legacy behavior)
(props.unitValue || 1) === 1;
}
let nowDate;
let waitMs;
if (nowIndicatorSnap) {
nowDate = context.dateEnv.startOf(unroundedNow, props.unit); // aka currentUnitStart
let nextUnitStart = context.dateEnv.add(nowDate, createDuration(1, props.unit));
waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf();
}
else {
nowDate = unroundedNow;
waitMs = 1000 * 60; // 1 minute
}
// there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342)
// ensure no longer than a day
waitMs = Math.min(1000 * 60 * 60 * 24, waitMs);
return {
state: { nowDate, todayRange: buildDayRange(nowDate) },
waitMs,
};
}
setTimeout(waitMs = this.computeTiming().waitMs) {
// NOTE: timeout could take longer than expected if tab sleeps,
// which is why we listen to 'visibilitychange'
this.timeoutId = setTimeout(() => {
// NOTE: timeout could also return *earlier* than expected, and we need to wait 2 ms more
// This is why use use same waitMs from computeTiming, so we don't skip an interval while
// .setState() is executing
const timing = this.computeTiming();
this.setState(timing.state, () => {
this.setTimeout(timing.waitMs);
});
}, waitMs);
}
clearTimeout() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
}
NowTimer.contextType = ViewContextType;
function buildDayRange(date) {
let start = startOfDay(date);
let end = addDays(start, 1);
return { start, end };
}
class CalendarImpl {
getCurrentData() {
return this.currentDataManager.getCurrentData();
}
dispatch(action) {
this.currentDataManager.dispatch(action);
}
get view() { return this.getCurrentData().viewApi; }
batchRendering(callback) {
callback();
}
updateSize() {
this.trigger('_resize', true);
}
// Options
// -----------------------------------------------------------------------------------------------------------------
setOption(name, val) {
this.dispatch({
type: 'SET_OPTION',
optionName: name,
rawOptionValue: val,
});
}
getOption(name) {
return this.currentDataManager.currentCalendarOptionsInput[name];
}
getAvailableLocaleCodes() {
return Object.keys(this.getCurrentData().availableRawLocales);
}
// Trigger
// -----------------------------------------------------------------------------------------------------------------
on(handlerName, handler) {
let { currentDataManager } = this;
if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
currentDataManager.emitter.on(handlerName, handler);
}
else {
console.warn(`Unknown listener name '${handlerName}'`);
}
}
off(handlerName, handler) {
this.currentDataManager.emitter.off(handlerName, handler);
}
// not meant for public use
trigger(handlerName, ...args) {
this.currentDataManager.emitter.trigger(handlerName, ...args);
}
// View
// -----------------------------------------------------------------------------------------------------------------
changeView(viewType, dateOrRange) {
this.batchRendering(() => {
this.unselect();
if (dateOrRange) {
if (dateOrRange.start && dateOrRange.end) { // a range
this.dispatch({
type: 'CHANGE_VIEW_TYPE',
viewType,
});
this.dispatch({
type: 'SET_OPTION',
optionName: 'visibleRange',
rawOptionValue: dateOrRange,
});
}
else {
let { dateEnv } = this.getCurrentData();
this.dispatch({
type: 'CHANGE_VIEW_TYPE',
viewType,
dateMarker: dateEnv.createMarker(dateOrRange),
});
}
}
else {
this.dispatch({
type: 'CHANGE_VIEW_TYPE',
viewType,
});
}
});
}
// Forces navigation to a view for the given date.
// `viewType` can be a specific view name or a generic one like "week" or "day".
// needs to change
zoomTo(dateMarker, viewType) {
let state = this.getCurrentData();
let spec;
viewType = viewType || 'day'; // day is default zoom
spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType);
this.unselect();
if (spec) {
this.dispatch({
type: 'CHANGE_VIEW_TYPE',
viewType: spec.type,
dateMarker,
});
}
else {
this.dispatch({
type: 'CHANGE_DATE',
dateMarker,
});
}
}
// Given a duration singular unit, like "week" or "day", finds a matching view spec.
// Preference is given to views that have corresponding buttons.
getUnitViewSpec(unit) {
let { viewSpecs, toolbarConfig } = this.getCurrentData();
let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []);
let i;
let spec;
for (let viewType in viewSpecs) {
viewTypes.push(viewType);
}
for (i = 0; i < viewTypes.length; i += 1) {
spec = viewSpecs[viewTypes[i]];
if (spec) {
if (spec.singleUnit === unit) {
return spec;
}
}
}
return null;
}
// Current Date
// -----------------------------------------------------------------------------------------------------------------
prev() {
this.unselect();
this.dispatch({ type: 'PREV' });
}
next() {
this.unselect();
this.dispatch({ type: 'NEXT' });
}
prevYear() {
let state = this.getCurrentData();
this.unselect();
this.dispatch({
type: 'CHANGE_DATE',
dateMarker: state.dateEnv.addYears(state.currentDate, -1),
});
}
nextYear() {
let state = this.getCurrentData();
this.unselect();
this.dispatch({
type: 'CHANGE_DATE',
dateMarker: state.dateEnv.addYears(state.currentDate, 1),
});
}
today() {
let state = this.getCurrentData();
this.unselect();
this.dispatch({
type: 'CHANGE_DATE',
dateMarker: state.nowManager.getDateMarker(),
});
}
gotoDate(zonedDateInput) {
let state = this.getCurrentData();
this.unselect();
this.dispatch({
type: 'CHANGE_DATE',
dateMarker: state.dateEnv.createMarker(zonedDateInput),
});
}
incrementDate(deltaInput) {
let state = this.getCurrentData();
let delta = createDuration(deltaInput);
if (delta) { // else, warn about invalid input?
this.unselect();
this.dispatch({
type: 'CHANGE_DATE',
dateMarker: state.dateEnv.add(state.currentDate, delta),
});
}
}
getDate() {
let state = this.getCurrentData();
return state.dateEnv.toDate(state.currentDate);
}
// Date Formatting Utils
// -----------------------------------------------------------------------------------------------------------------
formatDate(d, formatter) {
let { dateEnv } = this.getCurrentData();
return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter));
}
// `settings` is for formatter AND isEndExclusive
formatRange(d0, d1, settings) {
let { dateEnv } = this.getCurrentData();
return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings);
}
formatIso(d, omitTime) {
let { dateEnv } = this.getCurrentData();
return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime });
}
// Date Selection / Event Selection / DayClick
// -----------------------------------------------------------------------------------------------------------------
select(dateOrObj, endDate) {
let selectionInput;
if (endDate == null) {
if (dateOrObj.start != null) {
selectionInput = dateOrObj;
}
else {
selectionInput = {
start: dateOrObj,
end: null,
};
}
}
else {
selectionInput = {
start: dateOrObj,
end: endDate,
};
}
let state = this.getCurrentData();
let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 }));
if (selection) { // throw parse error otherwise?
this.dispatch({ type: 'SELECT_DATES', selection });
triggerDateSelect(selection, null, state);
}
}
unselect(pev) {
let state = this.getCurrentData();
if (state.dateSelection) {
this.dispatch({ type: 'UNSELECT_DATES' });
triggerDateUnselect(pev, state);
}
}
// Public Events API
// -----------------------------------------------------------------------------------------------------------------
addEvent(eventInput, sourceInput) {
if (eventInput instanceof EventImpl) {
let def = eventInput._def;
let instance = eventInput._instance;
let currentData = this.getCurrentData();
// not already present? don't want to add an old snapshot
if (!currentData.eventStore.defs[def.defId]) {
this.dispatch({
type: 'ADD_EVENTS',
eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
});
this.triggerEventAdd(eventInput);
}
return eventInput;
}
let state = this.getCurrentData();
let eventSource;
if (sourceInput instanceof EventSourceImpl) {
eventSource = sourceInput.internalEventSource;
}
else if (typeof sourceInput === 'boolean') {
if (sourceInput) { // true. part of the first event source
[eventSource] = hashValuesToArray(state.eventSources);
}
}
else if (sourceInput != null) { // an ID. accepts a number too
let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function
if (!sourceApi) {
console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test
return null;
}
eventSource = sourceApi.internalEventSource;
}
let tuple = parseEvent(eventInput, eventSource, state, false);
if (tuple) {
let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance);
this.dispatch({
type: 'ADD_EVENTS',
eventStore: eventTupleToStore(tuple),
});
this.triggerEventAdd(newEventApi);
return newEventApi;
}
return null;
}
triggerEventAdd(eventApi) {
let { emitter } = this.getCurrentData();
emitter.trigger('eventAdd', {
event: eventApi,
relatedEvents: [],
revert: () => {
this.dispatch({
type: 'REMOVE_EVENTS',
eventStore: eventApiToStore(eventApi),
});
},
});
}
// TODO: optimize
getEventById(id) {
let state = this.getCurrentData();
let { defs, instances } = state.eventStore;
id = String(id);
for (let defId in defs) {
let def = defs[defId];
if (def.publicId === id) {
if (def.recurringDef) {
return new EventImpl(state, def, null);
}
for (let instanceId in instances) {
let instance = instances[instanceId];
if (instance.defId === def.defId) {
return new EventImpl(state, def, instance);
}
}
}
}
return null;
}
getEvents() {
let currentData = this.getCurrentData();
return buildEventApis(currentData.eventStore, currentData);
}
removeAllEvents() {
this.dispatch({ type: 'REMOVE_ALL_EVENTS' });
}
// Public Event Sources API
// -----------------------------------------------------------------------------------------------------------------
getEventSources() {
let state = this.getCurrentData();
let sourceHash = state.eventSources;
let sourceApis = [];
for (let internalId in sourceHash) {
sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]));
}
return sourceApis;
}
getEventSourceById(id) {
let state = this.getCurrentData();
let sourceHash = state.eventSources;
id = String(id);
for (let sourceId in sourceHash) {
if (sourceHash[sourceId].publicId === id) {
return new EventSourceImpl(state, sourceHash[sourceId]);
}
}
return null;
}
addEventSource(sourceInput) {
let state = this.getCurrentData();
if (sourceInput instanceof EventSourceImpl) {
// not already present? don't want to add an old snapshot
if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
this.dispatch({
type: 'ADD_EVENT_SOURCES',
sources: [sourceInput.internalEventSource],
});
}
return sourceInput;
}
let eventSource = parseEventSource(sourceInput, state);
if (eventSource) { // TODO: error otherwise?
this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] });
return new EventSourceImpl(state, eventSource);
}
return null;
}
removeAllEventSources() {
this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' });
}
refetchEvents() {
this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true });
}
// Scroll
// -----------------------------------------------------------------------------------------------------------------
scrollToTime(timeInput) {
let time = createDuration(timeInput);
if (time) {
this.trigger('_scrollRequest', { time });
}
}
}
function pointInsideRect(point, rect) {
return point.left >= rect.left &&
point.left < rect.right &&
point.top >= rect.top &&
point.top < rect.bottom;
}
// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
function intersectRects(rect1, rect2) {
let res = {
left: Math.max(rect1.left, rect2.left),
right: Math.min(rect1.right, rect2.right),
top: Math.max(rect1.top, rect2.top),
bottom: Math.min(rect1.bottom, rect2.bottom),
};
if (res.left < res.right && res.top < res.bottom) {
return res;
}
return false;
}
function translateRect(rect, deltaX, deltaY) {
return {
left: rect.left + deltaX,
right: rect.right + deltaX,
top: rect.top + deltaY,
bottom: rect.bottom + deltaY,
};
}
// Returns a new point that will have been moved to reside within the given rectangle
function constrainPoint(point, rect) {
return {
left: Math.min(Math.max(point.left, rect.left), rect.right),
top: Math.min(Math.max(point.top, rect.top), rect.bottom),
};
}
// Returns a point that is the center of the given rectangle
function getRectCenter(rect) {
return {
left: (rect.left + rect.right) / 2,
top: (rect.top + rect.bottom) / 2,
};
}
// Subtracts point2's coordinates from point1's coordinates, returning a delta
function diffPoints(point1, point2) {
return {
left: point1.left - point2.left,
top: point1.top - point2.top,
};
}
const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere
class Splitter {
constructor() {
this.getKeysForEventDefs = memoize(this._getKeysForEventDefs);
this.splitDateSelection = memoize(this._splitDateSpan);
this.splitEventStore = memoize(this._splitEventStore);
this.splitIndividualUi = memoize(this._splitIndividualUi);
this.splitEventDrag = memoize(this._splitInteraction);
this.splitEventResize = memoize(this._splitInteraction);
this.eventUiBuilders = {}; // TODO: typescript protection
}
splitProps(props) {
let keyInfos = this.getKeyInfo(props);
let defKeys = this.getKeysForEventDefs(props.eventStore);
let dateSelections = this.splitDateSelection(props.dateSelection);
let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases*
let eventStores = this.splitEventStore(props.eventStore, defKeys);
let eventDrags = this.splitEventDrag(props.eventDrag);
let eventResizes = this.splitEventResize(props.eventResize);
let splitProps = {};
this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey));
for (let key in keyInfos) {
let keyInfo = keyInfos[key];
let eventStore = eventStores[key] || EMPTY_EVENT_STORE;
let buildEventUi = this.eventUiBuilders[key];
splitProps[key] = {
businessHours: keyInfo.businessHours || props.businessHours,
dateSelection: dateSelections[key] || null,
eventStore,
eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]),
eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '',
eventDrag: eventDrags[key] || null,
eventResize: eventResizes[key] || null,
};
}
return splitProps;
}
_splitDateSpan(dateSpan) {
let dateSpans = {};
if (dateSpan) {
let keys = this.getKeysForDateSpan(dateSpan);
for (let key of keys) {
dateSpans[key] = dateSpan;
}
}
return dateSpans;
}
_getKeysForEventDefs(eventStore) {
return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef));
}
_splitEventStore(eventStore, defKeys) {
let { defs, instances } = eventStore;
let splitStores = {};
for (let defId in defs) {
for (let key of defKeys[defId]) {
if (!splitStores[key]) {
splitStores[key] = createEmptyEventStore();
}
splitStores[key].defs[defId] = defs[defId];
}
}
for (let instanceId in instances) {
let instance = instances[instanceId];
for (let key of defKeys[instance.defId]) {
if (splitStores[key]) { // must have already been created
splitStores[key].instances[instanceId] = instance;
}
}
}
return splitStores;
}
_splitIndividualUi(eventUiBases, defKeys) {
let splitHashes = {};
for (let defId in eventUiBases) {
if (defId) { // not the '' key
for (let key of defKeys[defId]) {
if (!splitHashes[key]) {
splitHashes[key] = {};
}
splitHashes[key][defId] = eventUiBases[defId];
}
}
}
return splitHashes;
}
_splitInteraction(interaction) {
let splitStates = {};
if (interaction) {
let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents));
// can't rely on defKeys because event data is mutated
let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents);
let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId);
let populate = (key) => {
if (!splitStates[key]) {
splitStates[key] = {
affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE,
mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE,
isEvent: interaction.isEvent,
};
}
};
for (let key in affectedStores) {
populate(key);
}
for (let key in mutatedStores) {
populate(key);
}
}
return splitStates;
}
}
function buildEventUiForKey(allUi, eventUiForKey, individualUi) {
let baseParts = [];
if (allUi) {
baseParts.push(allUi);
}
if (eventUiForKey) {
baseParts.push(eventUiForKey);
}
let stuff = {
'': combineEventUis(baseParts),
};
if (individualUi) {
Object.assign(stuff, individualUi);
}
return stuff;
}
function getDateMeta(date, todayRange, nowDate, dateProfile) {
return {
dow: date.getUTCDay(),
isDisabled: Boolean(dateProfile && (!dateProfile.activeRange || !rangeContainsMarker(dateProfile.activeRange, date))),
isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)),
isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)),
isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false),
isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false),
};
}
function getDayClassNames(meta, theme) {
let classNames = [
'fc-day',
`fc-day-${DAY_IDS[meta.dow]}`,
];
if (meta.isDisabled) {
classNames.push('fc-day-disabled');
}
else {
if (meta.isToday) {
classNames.push('fc-day-today');
classNames.push(theme.getClass('today'));
}
if (meta.isPast) {
classNames.push('fc-day-past');
}
if (meta.isFuture) {
classNames.push('fc-day-future');
}
if (meta.isOther) {
classNames.push('fc-day-other');
}
}
return classNames;
}
function getSlotClassNames(meta, theme) {
let classNames = [
'fc-slot',
`fc-slot-${DAY_IDS[meta.dow]}`,
];
if (meta.isDisabled) {
classNames.push('fc-slot-disabled');
}
else {
if (meta.isToday) {
classNames.push('fc-slot-today');
classNames.push(theme.getClass('today'));
}
if (meta.isPast) {
classNames.push('fc-slot-past');
}
if (meta.isFuture) {
classNames.push('fc-slot-future');
}
}
return classNames;
}
const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' });
const WEEK_FORMAT = createFormatter({ week: 'long' });
function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) {
const { dateEnv, options, calendarApi } = context;
let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT);
if (options.navLinks) {
let zonedDate = dateEnv.toDate(dateMarker);
const handleInteraction = (ev) => {
let customAction = viewType === 'day' ? options.navLinkDayClick :
viewType === 'week' ? options.navLinkWeekClick : null;
if (typeof customAction === 'function') {
customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev);
}
else {
if (typeof customAction === 'string') {
viewType = customAction;
}
calendarApi.zoomTo(dateMarker, viewType);
}
};
return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable
? createAriaClickAttrs(handleInteraction)
: { onClick: handleInteraction }));
}
return { 'aria-label': dateStr };
}
let _isRtlScrollbarOnLeft = null;
function getIsRtlScrollbarOnLeft() {
if (_isRtlScrollbarOnLeft === null) {
_isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft();
}
return _isRtlScrollbarOnLeft;
}
function computeIsRtlScrollbarOnLeft() {
let outerEl = document.createElement('div');
applyStyle(outerEl, {
position: 'absolute',
top: -1000,
left: 0,
border: 0,
padding: 0,
overflow: 'scroll',
direction: 'rtl',
});
outerEl.innerHTML = '<div></div>';
document.body.appendChild(outerEl);
let innerEl = outerEl.firstChild;
let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left;
removeElement(outerEl);
return res;
}
let _scrollbarWidths;
function getScrollbarWidths() {
if (!_scrollbarWidths) {
_scrollbarWidths = computeScrollbarWidths();
}
return _scrollbarWidths;
}
function computeScrollbarWidths() {
let el = document.createElement('div');
el.style.overflow = 'scroll';
el.style.position = 'absolute';
el.style.top = '-9999px';
el.style.left = '-9999px';
document.body.appendChild(el);
let res = computeScrollbarWidthsForEl(el);
document.body.removeChild(el);
return res;
}
// WARNING: will include border
function computeScrollbarWidthsForEl(el) {
return {
x: el.offsetHeight - el.clientHeight,
y: el.offsetWidth - el.clientWidth,
};
}
function computeEdges(el, getPadding = false) {
let computedStyle = window.getComputedStyle(el);
let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0;
let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0;
let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0;
let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border!
let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight;
let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom;
let res = {
borderLeft,
borderRight,
borderTop,
borderBottom,
scrollbarBottom,
scrollbarLeft: 0,
scrollbarRight: 0,
};
if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side?
res.scrollbarLeft = scrollbarLeftRight;
}
else {
res.scrollbarRight = scrollbarLeftRight;
}
if (getPadding) {
res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0;
res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0;
res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;
}
return res;
}
function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) {
let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el);
let edges = computeEdges(el, goWithinPadding);
let res = {
left: outerRect.left + edges.borderLeft + edges.scrollbarLeft,
right: outerRect.right - edges.borderRight - edges.scrollbarRight,
top: outerRect.top + edges.borderTop,
bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom,
};
if (goWithinPadding) {
res.left += edges.paddingLeft;
res.right -= edges.paddingRight;
res.top += edges.paddingTop;
res.bottom -= edges.paddingBottom;
}
return res;
}
function computeRect(el) {
let rect = el.getBoundingClientRect();
return {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY,
right: rect.right + window.scrollX,
bottom: rect.bottom + window.scrollY,
};
}
function computeClippedClientRect(el) {
let clippingParents = getClippingParents(el);
let rect = el.getBoundingClientRect();
for (let clippingParent of clippingParents) {
let intersection = intersectRects(rect, clippingParent.getBoundingClientRect());
if (intersection) {
rect = intersection;
}
else {
return null;
}
}
return rect;
}
// does not return window
function getClippingParents(el) {
let parents = [];
while (el instanceof HTMLElement) { // will stop when gets to document or null
let computedStyle = window.getComputedStyle(el);
if (computedStyle.position === 'fixed') {
break;
}
if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) {
parents.push(el);
}
el = el.parentNode;
}
return parents;
}
/*
Records offset information for a set of elements, relative to an origin element.
Can record the left/right OR the top/bottom OR both.
Provides methods for querying the cache by position.
*/
class PositionCache {
constructor(originEl, els, isHorizontal, isVertical) {
this.els = els;
let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left
if (isHorizontal) {
this.buildElHorizontals(originClientRect.left);
}
if (isVertical) {
this.buildElVerticals(originClientRect.top);
}
}
// Populates the left/right internal coordinate arrays
buildElHorizontals(originClientLeft) {
let lefts = [];
let rights = [];
for (let el of this.els) {
let rect = el.getBoundingClientRect();
lefts.push(rect.left - originClientLeft);
rights.push(rect.right - originClientLeft);
}
this.lefts = lefts;
this.rights = rights;
}
// Populates the top/bottom internal coordinate arrays
buildElVerticals(originClientTop) {
let tops = [];
let bottoms = [];
for (let el of this.els) {
let rect = el.getBoundingClientRect();
tops.push(rect.top - originClientTop);
bottoms.push(rect.bottom - originClientTop);
}
this.tops = tops;
this.bottoms = bottoms;
}
// Given a left offset (from document left), returns the index of the el that it horizontally intersects.
// If no intersection is made, returns undefined.
leftToIndex(leftPosition) {
let { lefts, rights } = this;
let len = lefts.length;
let i;
for (i = 0; i < len; i += 1) {
if (leftPosition >= lefts[i] && leftPosition < rights[i]) {
return i;
}
}
return undefined; // TODO: better
}
// Given a top offset (from document top), returns the index of the el that it vertically intersects.
// If no intersection is made, returns undefined.
topToIndex(topPosition) {
let { tops, bottoms } = this;
let len = tops.length;
let i;
for (i = 0; i < len; i += 1) {
if (topPosition >= tops[i] && topPosition < bottoms[i]) {
return i;
}
}
return undefined; // TODO: better
}
// Gets the width of the element at the given index
getWidth(leftIndex) {
return this.rights[leftIndex] - this.lefts[leftIndex];
}
// Gets the height of the element at the given index
getHeight(topIndex) {
return this.bottoms[topIndex] - this.tops[topIndex];
}
similarTo(otherCache) {
return similarNumArrays(this.tops || [], otherCache.tops || []) &&
similarNumArrays(this.bottoms || [], otherCache.bottoms || []) &&
similarNumArrays(this.lefts || [], otherCache.lefts || []) &&
similarNumArrays(this.rights || [], otherCache.rights || []);
}
}
function similarNumArrays(a, b) {
const len = a.length;
if (len !== b.length) {
return false;
}
for (let i = 0; i < len; i++) {
if (Math.round(a[i]) !== Math.round(b[i])) {
return false;
}
}
return true;
}
/* eslint max-classes-per-file: "off" */
/*
An object for getting/setting scroll-related information for an element.
Internally, this is done very differently for window versus DOM element,
so this object serves as a common interface.
*/
class ScrollController {
getMaxScrollTop() {
return this.getScrollHeight() - this.getClientHeight();
}
getMaxScrollLeft() {
return this.getScrollWidth() - this.getClientWidth();
}
canScrollVertically() {
return this.getMaxScrollTop() > 0;
}
canScrollHorizontally() {
return this.getMaxScrollLeft() > 0;
}
canScrollUp() {
return this.getScrollTop() > 0;
}
canScrollDown() {
return this.getScrollTop() < this.getMaxScrollTop();
}
canScrollLeft() {
return this.getScrollLeft() > 0;
}
canScrollRight() {
return this.getScrollLeft() < this.getMaxScrollLeft();
}
}
class ElementScrollController extends ScrollController {
constructor(el) {
super();
this.el = el;
}
getScrollTop() {
return this.el.scrollTop;
}
getScrollLeft() {
return this.el.scrollLeft;
}
setScrollTop(top) {
this.el.scrollTop = top;
}
setScrollLeft(left) {
this.el.scrollLeft = left;
}
getScrollWidth() {
return this.el.scrollWidth;
}
getScrollHeight() {
return this.el.scrollHeight;
}
getClientHeight() {
return this.el.clientHeight;
}
getClientWidth() {
return this.el.clientWidth;
}
}
class WindowScrollController extends ScrollController {
getScrollTop() {
return window.scrollY;
}
getScrollLeft() {
return window.scrollX;
}
setScrollTop(n) {
window.scroll(window.scrollX, n);
}
setScrollLeft(n) {
window.scroll(n, window.scrollY);
}
getScrollWidth() {
return document.documentElement.scrollWidth;
}
getScrollHeight() {
return document.documentElement.scrollHeight;
}
getClientHeight() {
return document.documentElement.clientHeight;
}
getClientWidth() {
return document.documentElement.clientWidth;
}
}
/*
an INTERACTABLE date component
PURPOSES:
- hook up to fg, fill, and mirror renderers
- interface for dragging and hits
*/
class DateComponent extends BaseComponent {
constructor() {
super(...arguments);
this.uid = guid();
}
// Hit System
// -----------------------------------------------------------------------------------------------------------------
prepareHits() {
}
queryHit(positionLeft, positionTop, elWidth, elHeight) {
return null; // this should be abstract
}
// Pointer Interaction Utils
// -----------------------------------------------------------------------------------------------------------------
isValidSegDownEl(el) {
return !this.props.eventDrag && // HACK
!this.props.eventResize && // HACK
!elementClosest(el, '.fc-event-mirror');
}
isValidDateDownEl(el) {
return !elementClosest(el, '.fc-event:not(.fc-bg-event)') &&
!elementClosest(el, '.fc-more-link') && // a "more.." link
!elementClosest(el, 'a[data-navlink]') && // a clickable nav link
!elementClosest(el, '.fc-popover'); // hack
}
}
class NamedTimeZoneImpl {
constructor(timeZoneName) {
this.timeZoneName = timeZoneName;
}
}
class SegHierarchy {
constructor(getEntryThickness = (entry) => {
// if no thickness known, assume 1 (if 0, so small it always fits)
return entry.thickness || 1;
}) {
this.getEntryThickness = getEntryThickness;
// settings
this.strictOrder = false;
this.allowReslicing = false;
this.maxCoord = -1; // -1 means no max
this.maxStackCnt = -1; // -1 means no max
this.levelCoords = []; // ordered
this.entriesByLevel = []; // parallel with levelCoords
this.stackCnts = {}; // TODO: use better technique!?
}
addSegs(inputs) {
let hiddenEntries = [];
for (let input of inputs) {
this.insertEntry(input, hiddenEntries);
}
return hiddenEntries;
}
insertEntry(entry, hiddenEntries) {
let insertion = this.findInsertion(entry);
if (this.isInsertionValid(insertion, entry)) {
this.insertEntryAt(entry, insertion);
}
else {
this.handleInvalidInsertion(insertion, entry, hiddenEntries);
}
}
isInsertionValid(insertion, entry) {
return (this.maxCoord === -1 || insertion.levelCoord + this.getEntryThickness(entry) <= this.maxCoord) &&
(this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt);
}
handleInvalidInsertion(insertion, entry, hiddenEntries) {
if (this.allowReslicing && insertion.touchingEntry) {
const hiddenEntry = Object.assign(Object.assign({}, entry), { span: intersectSpans(entry.span, insertion.touchingEntry.span) });
hiddenEntries.push(hiddenEntry);
this.splitEntry(entry, insertion.touchingEntry, hiddenEntries);
}
else {
hiddenEntries.push(entry);
}
}
/*
Does NOT add what hit the `barrier` into hiddenEntries. Should already be done.
*/
splitEntry(entry, barrier, hiddenEntries) {
let entrySpan = entry.span;
let barrierSpan = barrier.span;
if (entrySpan.start < barrierSpan.start) {
this.insertEntry({
index: entry.index,
thickness: entry.thickness,
span: { start: entrySpan.start, end: barrierSpan.start },
}, hiddenEntries);
}
if (entrySpan.end > barrierSpan.end) {
this.insertEntry({
index: entry.index,
thickness: entry.thickness,
span: { start: barrierSpan.end, end: entrySpan.end },
}, hiddenEntries);
}
}
insertEntryAt(entry, insertion) {
let { entriesByLevel, levelCoords } = this;
if (insertion.lateral === -1) {
// create a new level
insertAt(levelCoords, insertion.level, insertion.levelCoord);
insertAt(entriesByLevel, insertion.level, [entry]);
}
else {
// insert into existing level
insertAt(entriesByLevel[insertion.level], insertion.lateral, entry);
}
this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt;
}
/*
does not care about limits
*/
findInsertion(newEntry) {
let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this;
let levelCnt = levelCoords.length;
let candidateCoord = 0;
let touchingLevel = -1;
let touchingLateral = -1;
let touchingEntry = null;
let stackCnt = 0;
for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) {
const trackingCoord = levelCoords[trackingLevel];
// if the current level is past the placed entry, we have found a good empty space and can stop.
// if strictOrder, keep finding more lateral intersections.
if (!strictOrder && trackingCoord >= candidateCoord + this.getEntryThickness(newEntry)) {
break;
}
let trackingEntries = entriesByLevel[trackingLevel];
let trackingEntry;
let searchRes = internal_common_binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end
let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one
while ( // loop through entries that horizontally intersect
(trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list
trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry
) {
let trackingEntryBottom = trackingCoord + this.getEntryThickness(trackingEntry);
// intersects into the top of the candidate?
if (trackingEntryBottom > candidateCoord) {
candidateCoord = trackingEntryBottom;
touchingEntry = trackingEntry;
touchingLevel = trackingLevel;
touchingLateral = lateralIndex;
}
// butts up against top of candidate? (will happen if just intersected as well)
if (trackingEntryBottom === candidateCoord) {
// accumulate the highest possible stackCnt of the trackingEntries that butt up
stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1);
}
lateralIndex += 1;
}
}
// the destination level will be after touchingEntry's level. find it
let destLevel = 0;
if (touchingEntry) {
destLevel = touchingLevel + 1;
while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) {
destLevel += 1;
}
}
// if adding to an existing level, find where to insert
let destLateral = -1;
if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) {
destLateral = internal_common_binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0];
}
return {
touchingLevel,
touchingLateral,
touchingEntry,
stackCnt,
levelCoord: candidateCoord,
level: destLevel,
lateral: destLateral,
};
}
// sorted by levelCoord (lowest to highest)
toRects() {
let { entriesByLevel, levelCoords } = this;
let levelCnt = entriesByLevel.length;
let rects = [];
for (let level = 0; level < levelCnt; level += 1) {
let entries = entriesByLevel[level];
let levelCoord = levelCoords[level];
for (let entry of entries) {
rects.push(Object.assign(Object.assign({}, entry), { thickness: this.getEntryThickness(entry), levelCoord }));
}
}
return rects;
}
}
function getEntrySpanEnd(entry) {
return entry.span.end;
}
function buildEntryKey(entry) {
return entry.index + ':' + entry.span.start;
}
// returns groups with entries sorted by input order
function groupIntersectingEntries(entries) {
let merges = [];
for (let entry of entries) {
let filteredMerges = [];
let hungryMerge = {
span: entry.span,
entries: [entry],
};
for (let merge of merges) {
if (intersectSpans(merge.span, hungryMerge.span)) {
hungryMerge = {
entries: merge.entries.concat(hungryMerge.entries),
span: joinSpans(merge.span, hungryMerge.span),
};
}
else {
filteredMerges.push(merge);
}
}
filteredMerges.push(hungryMerge);
merges = filteredMerges;
}
return merges;
}
function joinSpans(span0, span1) {
return {
start: Math.min(span0.start, span1.start),
end: Math.max(span0.end, span1.end),
};
}
function intersectSpans(span0, span1) {
let start = Math.max(span0.start, span1.start);
let end = Math.min(span0.end, span1.end);
if (start < end) {
return { start, end };
}
return null;
}
// general util
// ---------------------------------------------------------------------------------------------------------------------
function insertAt(arr, index, item) {
arr.splice(index, 0, item);
}
function internal_common_binarySearch(a, searchVal, getItemVal) {
let startIndex = 0;
let endIndex = a.length; // exclusive
if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item
return [0, 0];
}
if (searchVal > getItemVal(a[endIndex - 1])) { // after last item
return [endIndex, 0];
}
while (startIndex < endIndex) {
let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2);
let middleVal = getItemVal(a[middleIndex]);
if (searchVal < middleVal) {
endIndex = middleIndex;
}
else if (searchVal > middleVal) {
startIndex = middleIndex + 1;
}
else { // equal!
return [middleIndex, 1];
}
}
return [startIndex, 0];
}
/*
An abstraction for a dragging interaction originating on an event.
Does higher-level things than PointerDragger, such as possibly:
- a "mirror" that moves with the pointer
- a minimum number of pixels or other criteria for a true drag to begin
subclasses must emit:
- pointerdown
- dragstart
- dragmove
- pointerup
- dragend
*/
class ElementDragging {
constructor(el, selector) {
this.emitter = new Emitter();
}
destroy() {
}
setMirrorIsVisible(bool) {
// optional if subclass doesn't want to support a mirror
}
setMirrorNeedsRevert(bool) {
// optional if subclass doesn't want to support a mirror
}
setAutoScrollEnabled(bool) {
// optional
}
}
// TODO: get rid of this in favor of options system,
// tho it's really easy to access this globally rather than pass thru options.
const config = {};
/*
Information about what will happen when an external element is dragged-and-dropped
onto a calendar. Contains information for creating an event.
*/
const DRAG_META_REFINERS = {
startTime: createDuration,
duration: createDuration,
create: Boolean,
sourceId: String,
};
function parseDragMeta(raw) {
let { refined, extra } = refineProps(raw, DRAG_META_REFINERS);
return {
startTime: refined.startTime || null,
duration: refined.duration || null,
create: refined.create != null ? refined.create : true,
sourceId: refined.sourceId,
leftoverProps: extra,
};
}
// Computes a default column header formatting string if `colFormat` is not explicitly defined
function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) {
// if more than one week row, or if there are a lot of columns with not much space,
// put just the day numbers will be in each cell
if (!datesRepDistinctDays || dayCnt > 10) {
return createFormatter({ weekday: 'short' }); // "Sat"
}
if (dayCnt > 1) {
return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12"
}
return createFormatter({ weekday: 'long' }); // "Saturday"
}
const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no
function renderInner$1(renderProps) {
return renderProps.text;
}
// BAD name for this class now. used in the Header
class TableDateCell extends BaseComponent {
render() {
let { dateEnv, options, theme, viewApi } = this.context;
let { props } = this;
let { date, dateProfile } = props;
let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile);
let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme));
let text = dateEnv.format(date, props.dayHeaderFormat);
// if colCnt is 1, we are already in a day-view and don't need a navlink
let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1)
? buildNavLinkAttrs(this.context, date)
: {};
let publicDate = dateEnv.toDate(date);
// workaround for Luxon (and maybe moment) returning prior-days when start-of-day
// in DST gap: https://github.com/fullcalendar/fullcalendar/issues/7633
if (dateEnv.namedTimeZoneImpl) {
publicDate = addMs(publicDate, 3600000); // add an hour
}
let renderProps = Object.assign(Object.assign(Object.assign({ date: publicDate, view: viewApi }, props.extraRenderProps), { text }), dayMeta);
return (y(ContentContainer, { elTag: "th", elClasses: classNames, elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan, 'data-date': !dayMeta.isDisabled ? formatDayString(date) : undefined }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContainer) => (y("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (y(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [
'fc-col-header-cell-cushion',
props.isSticky && 'fc-sticky',
] }))))));
}
}
const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' });
class TableDowCell extends BaseComponent {
render() {
let { props } = this;
let { dateEnv, theme, viewApi, options } = this.context;
let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT
let dateMeta = {
dow: props.dow,
isDisabled: false,
isFuture: false,
isPast: false,
isToday: false,
isOther: false,
};
let text = dateEnv.format(date, props.dayHeaderFormat);
let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public?
date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text });
return (y(ContentContainer, { elTag: "th", elClasses: [
CLASS_NAME,
...getDayClassNames(dateMeta, theme),
...(props.extraClassNames || []),
], elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => (y("div", { className: "fc-scrollgrid-sync-inner" },
y(InnerContent, { elTag: "a", elClasses: [
'fc-col-header-cell-cushion',
props.isSticky && 'fc-sticky',
], elAttrs: {
'aria-label': dateEnv.format(date, WEEKDAY_FORMAT),
} })))));
}
}
class DayHeader extends BaseComponent {
constructor() {
super(...arguments);
this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
}
render() {
let { context } = this;
let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props;
let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length);
return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y("tr", { role: "row" },
renderIntro && renderIntro('day'),
dates.map((date) => (datesRepDistinctDays ? (y(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (y(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))))))));
}
}
function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) {
return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt);
}
class DaySeriesModel {
constructor(range, dateProfileGenerator) {
let date = range.start;
let { end } = range;
let indices = [];
let dates = [];
let dayIndex = -1;
while (date < end) { // loop each day from start to end
if (dateProfileGenerator.isHiddenDay(date)) {
indices.push(dayIndex + 0.5); // mark that it's between indices
}
else {
dayIndex += 1;
indices.push(dayIndex);
dates.push(date);
}
date = addDays(date, 1);
}
this.dates = dates;
this.indices = indices;
this.cnt = dates.length;
}
sliceRange(range) {
let firstIndex = this.getDateDayIndex(range.start); // inclusive first index
let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index
let clippedFirstIndex = Math.max(0, firstIndex);
let clippedLastIndex = Math.min(this.cnt - 1, lastIndex);
// deal with in-between indices
clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell
clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell
if (clippedFirstIndex <= clippedLastIndex) {
return {
firstIndex: clippedFirstIndex,
lastIndex: clippedLastIndex,
isStart: firstIndex === clippedFirstIndex,
isEnd: lastIndex === clippedLastIndex,
};
}
return null;
}
// Given a date, returns its chronolocial cell-index from the first cell of the grid.
// If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
// If before the first offset, returns a negative number.
// If after the last offset, returns an offset past the last cell offset.
// Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
getDateDayIndex(date) {
let { indices } = this;
let dayOffset = Math.floor(diffDays(this.dates[0], date));
if (dayOffset < 0) {
return indices[0] - 1;
}
if (dayOffset >= indices.length) {
return indices[indices.length - 1] + 1;
}
return indices[dayOffset];
}
}
class DayTableModel {
constructor(daySeries, breakOnWeeks) {
let { dates } = daySeries;
let daysPerRow;
let firstDay;
let rowCnt;
if (breakOnWeeks) {
// count columns until the day-of-week repeats
firstDay = dates[0].getUTCDay();
for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) {
if (dates[daysPerRow].getUTCDay() === firstDay) {
break;
}
}
rowCnt = Math.ceil(dates.length / daysPerRow);
}
else {
rowCnt = 1;
daysPerRow = dates.length;
}
this.rowCnt = rowCnt;
this.colCnt = daysPerRow;
this.daySeries = daySeries;
this.cells = this.buildCells();
this.headerDates = this.buildHeaderDates();
}
buildCells() {
let rows = [];
for (let row = 0; row < this.rowCnt; row += 1) {
let cells = [];
for (let col = 0; col < this.colCnt; col += 1) {
cells.push(this.buildCell(row, col));
}
rows.push(cells);
}
return rows;
}
buildCell(row, col) {
let date = this.daySeries.dates[row * this.colCnt + col];
return {
key: date.toISOString(),
date,
};
}
buildHeaderDates() {
let dates = [];
for (let col = 0; col < this.colCnt; col += 1) {
dates.push(this.cells[0][col].date);
}
return dates;
}
sliceRange(range) {
let { colCnt } = this;
let seriesSeg = this.daySeries.sliceRange(range);
let segs = [];
if (seriesSeg) {
let { firstIndex, lastIndex } = seriesSeg;
let index = firstIndex;
while (index <= lastIndex) {
let row = Math.floor(index / colCnt);
let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1);
segs.push({
row,
firstCol: index % colCnt,
lastCol: (nextIndex - 1) % colCnt,
isStart: seriesSeg.isStart && index === firstIndex,
isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex,
});
index = nextIndex;
}
}
return segs;
}
}
class Slicer {
constructor() {
this.sliceBusinessHours = memoize(this._sliceBusinessHours);
this.sliceDateSelection = memoize(this._sliceDateSpan);
this.sliceEventStore = memoize(this._sliceEventStore);
this.sliceEventDrag = memoize(this._sliceInteraction);
this.sliceEventResize = memoize(this._sliceInteraction);
this.forceDayIfListItem = false; // hack
}
sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) {
let { eventUiBases } = props;
let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs);
return {
dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs),
businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs),
fgEventSegs: eventSegs.fg,
bgEventSegs: eventSegs.bg,
eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
eventSelection: props.eventSelection,
}; // TODO: give interactionSegs?
}
sliceNowDate(// does not memoize
date, dateProfile, nextDayThreshold, context, ...extraArgs) {
return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
dateProfile, nextDayThreshold, {}, context, ...extraArgs);
}
_sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) {
if (!businessHours) {
return [];
}
return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg;
}
_sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
if (eventStore) {
let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
return {
bg: this.sliceEventRanges(rangeRes.bg, extraArgs),
fg: this.sliceEventRanges(rangeRes.fg, extraArgs),
};
}
return { bg: [], fg: [] };
}
_sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
if (!interaction) {
return null;
}
let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
return {
segs: this.sliceEventRanges(rangeRes.fg, extraArgs),
affectedInstances: interaction.affectedEvents.instances,
isEvent: interaction.isEvent,
};
}
_sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) {
if (!dateSpan) {
return [];
}
let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold));
let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange);
if (activeDateSpanRange) {
dateSpan = Object.assign(Object.assign({}, dateSpan), { range: activeDateSpanRange });
let eventRange = fabricateEventRange(dateSpan, eventUiBases, context);
let segs = this.sliceRange(dateSpan.range, ...extraArgs);
for (let seg of segs) {
seg.eventRange = eventRange;
}
return segs;
}
return [];
}
/*
"complete" seg means it has component and eventRange
*/
sliceEventRanges(eventRanges, extraArgs) {
let segs = [];
for (let eventRange of eventRanges) {
segs.push(...this.sliceEventRange(eventRange, extraArgs));
}
return segs;
}
/*
"complete" seg means it has component and eventRange
*/
sliceEventRange(eventRange, extraArgs) {
let dateRange = eventRange.range;
// hack to make multi-day events that are being force-displayed as list-items to take up only one day
if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') {
dateRange = {
start: dateRange.start,
end: addDays(dateRange.start, 1),
};
}
let segs = this.sliceRange(dateRange, ...extraArgs);
for (let seg of segs) {
seg.eventRange = eventRange;
seg.isStart = eventRange.isStart && seg.isStart;
seg.isEnd = eventRange.isEnd && seg.isEnd;
}
return segs;
}
}
/*
for incorporating slotMinTime/slotMaxTime if appropriate
TODO: should be part of DateProfile!
TimelineDateProfile already does this btw
*/
function computeActiveRange(dateProfile, isComponentAllDay) {
let range = dateProfile.activeRange;
if (isComponentAllDay) {
return range;
}
return {
start: addMs(range.start, dateProfile.slotMinTime.milliseconds),
end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day
};
}
// high-level segmenting-aware tester functions
// ------------------------------------------------------------------------------------------------------------------------
function isInteractionValid(interaction, dateProfile, context) {
let { instances } = interaction.mutatedEvents;
for (let instanceId in instances) {
if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
return false;
}
}
return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions
}
function isDateSelectionValid(dateSelection, dateProfile, context) {
if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
return false;
}
return isNewPropsValid({ dateSelection }, context);
}
function isNewPropsValid(newProps, context) {
let calendarState = context.getCurrentData();
let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps);
return (context.pluginHooks.isPropsValid || isPropsValid)(props, context);
}
function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) {
if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
return false;
}
if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
return false;
}
return true;
}
// Moving Event Validation
// ------------------------------------------------------------------------------------------------------------------------
function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) {
let currentState = context.getCurrentData();
let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions
let subjectEventStore = interaction.mutatedEvents;
let subjectDefs = subjectEventStore.defs;
let subjectInstances = subjectEventStore.instances;
let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ?
state.eventUiBases :
{ '': currentState.selectionConfig });
if (filterConfig) {
subjectConfigs = mapHash(subjectConfigs, filterConfig);
}
// exclude the subject events. TODO: exclude defs too?
let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances);
let otherDefs = otherEventStore.defs;
let otherInstances = otherEventStore.instances;
let otherConfigs = compileEventUis(otherDefs, state.eventUiBases);
for (let subjectInstanceId in subjectInstances) {
let subjectInstance = subjectInstances[subjectInstanceId];
let subjectRange = subjectInstance.range;
let subjectConfig = subjectConfigs[subjectInstance.defId];
let subjectDef = subjectDefs[subjectInstance.defId];
// constraint
if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
return false;
}
// overlap
let { eventOverlap } = context.options;
let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null;
for (let otherInstanceId in otherInstances) {
let otherInstance = otherInstances[otherInstanceId];
// intersect! evaluate
if (rangesIntersect(subjectRange, otherInstance.range)) {
let otherOverlap = otherConfigs[otherInstance.defId].overlap;
// consider the other event's overlap. only do this if the subject event is a "real" event
if (otherOverlap === false && interaction.isEvent) {
return false;
}
if (subjectConfig.overlap === false) {
return false;
}
if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event
new EventImpl(context, subjectDef, subjectInstance))) {
return false;
}
}
}
// allow (a function)
let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state
for (let subjectAllow of subjectConfig.allows) {
let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay });
let origDef = calendarEventStore.defs[subjectDef.defId];
let origInstance = calendarEventStore.instances[subjectInstanceId];
let eventApi;
if (origDef) { // was previously in the calendar
eventApi = new EventImpl(context, origDef, origInstance);
}
else { // was an external event
eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates
}
if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) {
return false;
}
}
}
return true;
}
// Date Selection Validation
// ------------------------------------------------------------------------------------------------------------------------
function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) {
let relevantEventStore = state.eventStore;
let relevantDefs = relevantEventStore.defs;
let relevantInstances = relevantEventStore.instances;
let selection = state.dateSelection;
let selectionRange = selection.range;
let { selectionConfig } = context.getCurrentData();
if (filterConfig) {
selectionConfig = filterConfig(selectionConfig);
}
// constraint
if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
return false;
}
// overlap
let { selectOverlap } = context.options;
let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null;
for (let relevantInstanceId in relevantInstances) {
let relevantInstance = relevantInstances[relevantInstanceId];
// intersect! evaluate
if (rangesIntersect(selectionRange, relevantInstance.range)) {
if (selectionConfig.overlap === false) {
return false;
}
if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) {
return false;
}
}
}
// allow (a function)
for (let selectionAllow of selectionConfig.allows) {
let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection);
if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) {
return false;
}
}
return true;
}
// Constraint Utils
// ------------------------------------------------------------------------------------------------------------------------
function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) {
for (let constraint of constraints) {
if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) {
return false;
}
}
return true;
}
function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours
otherEventStore, // for if constraint is an even group ID
businessHoursUnexpanded, // for if constraint is 'businessHours'
context) {
if (constraint === 'businessHours') {
return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context));
}
if (typeof constraint === 'string') { // an group ID
return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint));
}
if (typeof constraint === 'object' && constraint) { // non-null object
return eventStoreToRanges(expandRecurring(constraint, subjectRange, context));
}
return []; // if it's false
}
// TODO: move to event-store file?
function eventStoreToRanges(eventStore) {
let { instances } = eventStore;
let ranges = [];
for (let instanceId in instances) {
ranges.push(instances[instanceId].range);
}
return ranges;
}
// TODO: move to geom file?
function anyRangesContainRange(outerRanges, innerRange) {
for (let outerRange of outerRanges) {
if (rangeContainsRange(outerRange, innerRange)) {
return true;
}
}
return false;
}
const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/;
class Scroller extends BaseComponent {
constructor() {
super(...arguments);
this.handleEl = (el) => {
this.el = el;
setRef(this.props.elRef, el);
};
}
render() {
let { props } = this;
let { liquid, liquidIsAbsolute } = props;
let isAbsolute = liquid && liquidIsAbsolute;
let className = ['fc-scroller'];
if (liquid) {
if (liquidIsAbsolute) {
className.push('fc-scroller-liquid-absolute');
}
else {
className.push('fc-scroller-liquid');
}
}
return (y("div", { ref: this.handleEl, className: className.join(' '), style: {
overflowX: props.overflowX,
overflowY: props.overflowY,
left: (isAbsolute && -(props.overcomeLeft || 0)) || '',
right: (isAbsolute && -(props.overcomeRight || 0)) || '',
bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '',
marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '',
marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '',
marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '',
maxHeight: props.maxHeight || '',
} }, props.children));
}
needsXScrolling() {
if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
return false;
}
// testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers.
// much more reliable to see if children are taller than the scroller, even tho doesn't account for
// inner-child margins and absolute positioning
let { el } = this;
let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth();
let { children } = el;
for (let i = 0; i < children.length; i += 1) {
let childEl = children[i];
if (childEl.getBoundingClientRect().width > realClientWidth) {
return true;
}
}
return false;
}
needsYScrolling() {
if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
return false;
}
// testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers.
// much more reliable to see if children are taller than the scroller, even tho doesn't account for
// inner-child margins and absolute positioning
let { el } = this;
let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth();
let { children } = el;
for (let i = 0; i < children.length; i += 1) {
let childEl = children[i];
if (childEl.getBoundingClientRect().height > realClientHeight) {
return true;
}
}
return false;
}
getXScrollbarWidth() {
if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
return 0;
}
return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important?
}
getYScrollbarWidth() {
if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
return 0;
}
return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important?
}
}
/*
TODO: somehow infer OtherArgs from masterCallback?
TODO: infer RefType from masterCallback if provided
*/
class RefMap {
constructor(masterCallback) {
this.masterCallback = masterCallback;
this.currentMap = {};
this.depths = {};
this.callbackMap = {};
this.handleValue = (val, key) => {
let { depths, currentMap } = this;
let removed = false;
let added = false;
if (val !== null) {
// for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore
removed = (key in currentMap);
currentMap[key] = val;
depths[key] = (depths[key] || 0) + 1;
added = true;
}
else {
depths[key] -= 1;
if (!depths[key]) {
delete currentMap[key];
delete this.callbackMap[key];
removed = true;
}
}
if (this.masterCallback) {
if (removed) {
this.masterCallback(null, String(key));
}
if (added) {
this.masterCallback(val, String(key));
}
}
};
}
createRef(key) {
let refCallback = this.callbackMap[key];
if (!refCallback) {
refCallback = this.callbackMap[key] = (val) => {
this.handleValue(val, String(key));
};
}
return refCallback;
}
// TODO: check callers that don't care about order. should use getAll instead
// NOTE: this method has become less valuable now that we are encouraged to map order by some other index
// TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect"
collect(startIndex, endIndex, step) {
return collectFromHash(this.currentMap, startIndex, endIndex, step);
}
getAll() {
return hashValuesToArray(this.currentMap);
}
}
function computeShrinkWidth(chunkEls) {
let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink');
let largestWidth = 0;
for (let shrinkCell of shrinkCells) {
largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell));
}
return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits
}
function getSectionHasLiquidHeight(props, sectionConfig) {
return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
}
function getAllowYScrolling(props, sectionConfig) {
return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
}
// TODO: ONLY use `arg`. force out internal function to use same API
function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) {
let { expandRows } = arg;
let content = typeof chunkConfig.content === 'function' ?
chunkConfig.content(arg) :
y('table', {
role: 'presentation',
className: [
chunkConfig.tableClassName,
sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
].join(' '),
style: {
minWidth: arg.tableMinWidth,
width: arg.clientWidth,
height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height
},
}, arg.tableColGroupNode, y(isHeader ? 'thead' : 'tbody', {
role: 'presentation',
}, typeof chunkConfig.rowContent === 'function'
? chunkConfig.rowContent(arg)
: chunkConfig.rowContent));
return content;
}
function isColPropsEqual(cols0, cols1) {
return isArraysEqual(cols0, cols1, isPropsEqual);
}
function renderMicroColGroup(cols, shrinkWidth) {
let colNodes = [];
/*
for ColProps with spans, it would have been great to make a single <col span="">
HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans.
SOLUTION: making individual <col> elements makes Chrome behave.
*/
for (let colProps of cols) {
let span = colProps.span || 1;
for (let i = 0; i < span; i += 1) {
colNodes.push(y("col", { style: {
width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
minWidth: colProps.minWidth || '',
} }));
}
}
return y('colgroup', {}, ...colNodes);
}
function sanitizeShrinkWidth(shrinkWidth) {
/* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
4 accounts for 2 2-pixel borders. TODO: better solution? */
return shrinkWidth == null ? 4 : shrinkWidth;
}
function hasShrinkWidth(cols) {
for (let col of cols) {
if (col.width === 'shrink') {
return true;
}
}
return false;
}
function getScrollGridClassNames(liquid, context) {
let classNames = [
'fc-scrollgrid',
context.theme.getClass('table'),
];
if (liquid) {
classNames.push('fc-scrollgrid-liquid');
}
return classNames;
}
function getSectionClassNames(sectionConfig, wholeTableVGrow) {
let classNames = [
'fc-scrollgrid-section',
`fc-scrollgrid-section-${sectionConfig.type}`,
sectionConfig.className, // used?
];
if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
classNames.push('fc-scrollgrid-section-liquid');
}
if (sectionConfig.isSticky) {
classNames.push('fc-scrollgrid-section-sticky');
}
return classNames;
}
function renderScrollShim(arg) {
return (y("div", { className: "fc-scrollgrid-sticky-shim", style: {
width: arg.clientWidth,
minWidth: arg.tableMinWidth,
} }));
}
function getStickyHeaderDates(options) {
let { stickyHeaderDates } = options;
if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
}
return stickyHeaderDates;
}
function getStickyFooterScrollbar(options) {
let { stickyFooterScrollbar } = options;
if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
}
return stickyFooterScrollbar;
}
class SimpleScrollGrid extends BaseComponent {
constructor() {
super(...arguments);
this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time
// yucky to memoize VNodes, but much more efficient for consumers
this.renderMicroColGroup = memoize(renderMicroColGroup);
this.scrollerRefs = new RefMap();
this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this));
this.state = {
shrinkWidth: null,
forceYScrollbars: false,
scrollerClientWidths: {},
scrollerClientHeights: {},
};
// TODO: can do a really simple print-view. dont need to join rows
this.handleSizing = () => {
this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims()));
};
}
render() {
let { props, state, context } = this;
let sectionConfigs = props.sections || [];
let cols = this.processCols(props.cols);
let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
let classNames = getScrollGridClassNames(props.liquid, context);
if (props.collapsibleWidth) {
classNames.push('fc-scrollgrid-collapsible');
}
// TODO: make DRY
let configCnt = sectionConfigs.length;
let configI = 0;
let currentConfig;
let headSectionNodes = [];
let bodySectionNodes = [];
let footSectionNodes = [];
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
configI += 1;
}
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false));
configI += 1;
}
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
configI += 1;
}
// firefox bug: when setting height on table and there is a thead or tfoot,
// the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
// use getCanVGrowWithinCell as a way to detect table-stupid firefox.
// if so, use a simpler dom structure, jam everything into a lone tbody.
let isBuggy = !getCanVGrowWithinCell();
const roleAttrs = { role: 'rowgroup' };
return y('table', {
role: 'grid',
className: classNames.join(' '),
style: { height: props.height },
}, Boolean(!isBuggy && headSectionNodes.length) && y('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && y('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && y('tfoot', roleAttrs, ...footSectionNodes), isBuggy && y('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes));
}
renderSection(sectionConfig, microColGroupNode, isHeader) {
if ('outerContent' in sectionConfig) {
return (y(_, { key: sectionConfig.key }, sectionConfig.outerContent));
}
return (y("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader)));
}
renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) {
if ('outerContent' in chunkConfig) {
return chunkConfig.outerContent;
}
let { props } = this;
let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state;
let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
let isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
// for `!props.liquid` - is WHOLE scrollgrid natural height?
// TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
let overflowY = !props.liquid ? 'visible' :
forceYScrollbars ? 'scroll' :
!needsYScrolling ? 'hidden' :
'auto';
let sectionKey = sectionConfig.key;
let content = renderChunkContent(sectionConfig, chunkConfig, {
tableColGroupNode: microColGroupNode,
tableMinWidth: '',
clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
expandRows: sectionConfig.expandRows,
syncRowHeights: false,
rowSyncHeights: [],
reportRowHeightChange: () => { },
}, isHeader);
return y(isHeader ? 'th' : 'td', {
ref: chunkConfig.elRef,
role: 'presentation',
}, y("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` },
y(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
: true }, content)));
}
_handleScrollerEl(scrollerEl, key) {
let section = getSectionByKey(this.props.sections, key);
if (section) {
setRef(section.chunk.scrollerElRef, scrollerEl);
}
}
componentDidMount() {
this.handleSizing();
this.context.addResizeHandler(this.handleSizing);
}
componentDidUpdate() {
// TODO: need better solution when state contains non-sizing things
this.handleSizing();
}
componentWillUnmount() {
this.context.removeResizeHandler(this.handleSizing);
}
computeShrinkWidth() {
return hasShrinkWidth(this.props.cols)
? computeShrinkWidth(this.scrollerElRefs.getAll())
: 0;
}
computeScrollerDims() {
let scrollbarWidth = getScrollbarWidths();
let { scrollerRefs, scrollerElRefs } = this;
let forceYScrollbars = false;
let scrollerClientWidths = {};
let scrollerClientHeights = {};
for (let sectionKey in scrollerRefs.currentMap) {
let scroller = scrollerRefs.currentMap[sectionKey];
if (scroller && scroller.needsYScrolling()) {
forceYScrollbars = true;
break;
}
}
for (let section of this.props.sections) {
let sectionKey = section.key;
let scrollerEl = scrollerElRefs.currentMap[sectionKey];
if (scrollerEl) {
let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
: 0));
scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
}
}
return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights };
}
}
SimpleScrollGrid.addStateEquality({
scrollerClientWidths: isPropsEqual,
scrollerClientHeights: isPropsEqual,
});
function getSectionByKey(sections, key) {
for (let section of sections) {
if (section.key === key) {
return section;
}
}
return null;
}
class EventContainer extends BaseComponent {
constructor() {
super(...arguments);
// memo
this.buildPublicEvent = memoize((context, eventDef, eventInstance) => new EventImpl(context, eventDef, eventInstance));
this.handleEl = (el) => {
this.el = el;
setRef(this.props.elRef, el);
if (el) {
setElSeg(el, this.props.seg);
}
};
}
render() {
const { props, context } = this;
const { options } = context;
const { seg } = props;
const { eventRange } = seg;
const { ui } = eventRange;
const renderProps = {
event: this.buildPublicEvent(context, eventRange.def, eventRange.instance),
view: context.viewApi,
timeText: props.timeText,
textColor: ui.textColor,
backgroundColor: ui.backgroundColor,
borderColor: ui.borderColor,
isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
isStart: Boolean(seg.isStart),
isEnd: Boolean(seg.isEnd),
isPast: Boolean(props.isPast),
isFuture: Boolean(props.isFuture),
isToday: Boolean(props.isToday),
isSelected: Boolean(props.isSelected),
isDragging: Boolean(props.isDragging),
isResizing: Boolean(props.isResizing),
};
return (y(ContentContainer, { elRef: this.handleEl, elTag: props.elTag, elAttrs: props.elAttrs, elClasses: [
...getEventClassNames(renderProps),
...seg.eventRange.ui.classNames,
...(props.elClasses || []),
], elStyle: props.elStyle, renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount }, props.children));
}
componentDidUpdate(prevProps) {
if (this.el && this.props.seg !== prevProps.seg) {
setElSeg(this.el, this.props.seg);
}
}
}
// should not be a purecomponent
class StandardEvent extends BaseComponent {
render() {
let { props, context } = this;
let { options } = context;
let { seg } = props;
let { ui } = seg.eventRange;
let timeFormat = options.eventTimeFormat || props.defaultTimeFormat;
let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
return (y(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: {
borderColor: ui.borderColor,
backgroundColor: ui.backgroundColor,
}, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1, timeText: timeText }), (InnerContent, eventContentArg) => (y(_, null,
y(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }),
Boolean(eventContentArg.isStartResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-start" })),
Boolean(eventContentArg.isEndResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-end" }))))));
}
}
StandardEvent.addPropsEquality({
seg: isPropsEqual,
});
function renderInnerContent$1(innerProps) {
return (y("div", { className: "fc-event-main-frame" },
innerProps.timeText && (y("div", { className: "fc-event-time" }, innerProps.timeText)),
y("div", { className: "fc-event-title-container" },
y("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || y(_, null, "\u00A0")))));
}
const NowIndicatorContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
let { options } = context;
let renderProps = {
isAxis: props.isAxis,
date: context.dateEnv.toDate(props.date),
view: context.viewApi,
};
return (y(ContentContainer, { elRef: props.elRef, elTag: props.elTag || 'div', elAttrs: props.elAttrs, elClasses: props.elClasses, elStyle: props.elStyle, renderProps: renderProps, generatorName: "nowIndicatorContent", customGenerator: options.nowIndicatorContent, classNameGenerator: options.nowIndicatorClassNames, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children));
}));
const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
class DayCellContainer extends BaseComponent {
constructor() {
super(...arguments);
this.refineRenderProps = memoizeObjArg(refineRenderProps);
}
render() {
let { props, context } = this;
let { options } = context;
let renderProps = this.refineRenderProps({
date: props.date,
dateProfile: props.dateProfile,
todayRange: props.todayRange,
isMonthStart: props.isMonthStart || false,
showDayNumber: props.showDayNumber,
extraRenderProps: props.extraRenderProps,
viewApi: context.viewApi,
dateEnv: context.dateEnv,
monthStartFormat: options.monthStartFormat,
});
return (y(ContentContainer, { elRef: props.elRef, elTag: props.elTag, elAttrs: Object.assign(Object.assign({}, props.elAttrs), (renderProps.isDisabled ? {} : { 'data-date': formatDayString(props.date) })), elClasses: [
...getDayClassNames(renderProps, context.theme),
...(props.elClasses || []),
], elStyle: props.elStyle, renderProps: renderProps, generatorName: "dayCellContent", customGenerator: options.dayCellContent, defaultGenerator: props.defaultGenerator, classNameGenerator:
// don't use custom classNames if disabled
renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount }, props.children));
}
}
function hasCustomDayCellContent(options) {
return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options));
}
function refineRenderProps(raw) {
let { date, dateEnv, dateProfile, isMonthStart } = raw;
let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile);
let dayNumberText = raw.showDayNumber ? (dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)) : '';
return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { isMonthStart,
dayNumberText }), raw.extraRenderProps);
}
class BgEvent extends BaseComponent {
render() {
let { props } = this;
let { seg } = props;
return (y(EventContainer, { elTag: "div", elClasses: ['fc-bg-event'], elStyle: { backgroundColor: seg.eventRange.ui.backgroundColor }, defaultGenerator: renderInnerContent, seg: seg, timeText: "", isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, disableDragging: true, disableResizing: true }));
}
}
function renderInnerContent(props) {
let { title } = props.event;
return title && (y("div", { className: "fc-event-title" }, props.event.title));
}
function renderFill(fillType) {
return (y("div", { className: `fc-${fillType}` }));
}
const WeekNumberContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
let { dateEnv, options } = context;
let { date } = props;
let format = options.weekNumberFormat || props.defaultFormat;
let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
let text = dateEnv.format(date, format);
let renderProps = { num, text, date };
return (y(ContentContainer // why isn't WeekNumberContentArg being auto-detected?
, { elRef: props.elRef, elTag: props.elTag, elAttrs: props.elAttrs, elClasses: props.elClasses, elStyle: props.elStyle, renderProps: renderProps, generatorName: "weekNumberContent", customGenerator: options.weekNumberContent, defaultGenerator: renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children));
}));
function renderInner(innerProps) {
return innerProps.text;
}
const PADDING_FROM_VIEWPORT = 10;
class Popover extends BaseComponent {
constructor() {
super(...arguments);
this.state = {
titleId: getUniqueDomId(),
};
this.handleRootEl = (el) => {
this.rootEl = el;
if (this.props.elRef) {
setRef(this.props.elRef, el);
}
};
// Triggered when the user clicks *anywhere* in the document, for the autoHide feature
this.handleDocumentMouseDown = (ev) => {
// only hide the popover if the click happened outside the popover
const target = getEventTargetViaRoot(ev);
if (!this.rootEl.contains(target)) {
this.handleCloseClick();
}
};
this.handleDocumentKeyDown = (ev) => {
if (ev.key === 'Escape') {
this.handleCloseClick();
}
};
this.handleCloseClick = () => {
let { onClose } = this.props;
if (onClose) {
onClose();
}
};
}
render() {
let { theme, options } = this.context;
let { props, state } = this;
let classNames = [
'fc-popover',
theme.getClass('popover'),
].concat(props.extraClassNames || []);
return compat_module_j(y("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }),
y("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
y("span", { className: "fc-popover-title", id: state.titleId }, props.title),
y("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })),
y("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
}
componentDidMount() {
document.addEventListener('mousedown', this.handleDocumentMouseDown);
document.addEventListener('keydown', this.handleDocumentKeyDown);
this.updateSize();
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
document.removeEventListener('keydown', this.handleDocumentKeyDown);
}
updateSize() {
let { isRtl } = this.context;
let { alignmentEl, alignGridTop } = this.props;
let { rootEl } = this;
let alignmentRect = computeClippedClientRect(alignmentEl);
if (alignmentRect) {
let popoverDims = rootEl.getBoundingClientRect();
// position relative to viewport
let popoverTop = alignGridTop
? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
: alignmentRect.top;
let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
// constrain
popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
let origin = rootEl.offsetParent.getBoundingClientRect();
applyStyle(rootEl, {
top: popoverTop - origin.top,
left: popoverLeft - origin.left,
});
}
}
}
class MorePopover extends DateComponent {
constructor() {
super(...arguments);
this.handleRootEl = (rootEl) => {
this.rootEl = rootEl;
if (rootEl) {
this.context.registerInteractiveComponent(this, {
el: rootEl,
useEventCenter: false,
});
}
else {
this.context.unregisterInteractiveComponent(this);
}
};
}
render() {
let { options, dateEnv } = this.context;
let { props } = this;
let { startDate, todayRange, dateProfile } = props;
let title = dateEnv.format(startDate, options.dayPopoverFormat);
return (y(DayCellContainer, { elRef: this.handleRootEl, date: startDate, dateProfile: dateProfile, todayRange: todayRange }, (InnerContent, renderProps, elAttrs) => (y(Popover, { elRef: elAttrs.ref, id: props.id, title: title, extraClassNames: ['fc-more-popover'].concat(elAttrs.className || []), extraAttrs: elAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })),
props.children))));
}
queryHit(positionLeft, positionTop, elWidth, elHeight) {
let { rootEl, props } = this;
if (positionLeft >= 0 && positionLeft < elWidth &&
positionTop >= 0 && positionTop < elHeight) {
return {
dateProfile: props.dateProfile,
dateSpan: Object.assign({ allDay: !props.forceTimed, range: {
start: props.startDate,
end: props.endDate,
} }, props.extraDateSpan),
dayEl: rootEl,
rect: {
left: 0,
top: 0,
right: elWidth,
bottom: elHeight,
},
layer: 1, // important when comparing with hits from other components
};
}
return null;
}
}
class MoreLinkContainer extends BaseComponent {
constructor() {
super(...arguments);
this.state = {
isPopoverOpen: false,
popoverId: getUniqueDomId(),
};
this.handleLinkEl = (linkEl) => {
this.linkEl = linkEl;
if (this.props.elRef) {
setRef(this.props.elRef, linkEl);
}
};
this.handleClick = (ev) => {
let { props, context } = this;
let { moreLinkClick } = context.options;
let date = computeRange(props).start;
function buildPublicSeg(seg) {
let { def, instance, range } = seg.eventRange;
return {
event: new EventImpl(context, def, instance),
start: context.dateEnv.toDate(range.start),
end: context.dateEnv.toDate(range.end),
isStart: seg.isStart,
isEnd: seg.isEnd,
};
}
if (typeof moreLinkClick === 'function') {
moreLinkClick = moreLinkClick({
date,
allDay: Boolean(props.allDayDate),
allSegs: props.allSegs.map(buildPublicSeg),
hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
jsEvent: ev,
view: context.viewApi,
});
}
if (!moreLinkClick || moreLinkClick === 'popover') {
this.setState({ isPopoverOpen: true });
}
else if (typeof moreLinkClick === 'string') { // a view name
context.calendarApi.zoomTo(date, moreLinkClick);
}
};
this.handlePopoverClose = () => {
this.setState({ isPopoverOpen: false });
};
}
render() {
let { props, state } = this;
return (y(ViewContextType.Consumer, null, (context) => {
let { viewApi, options, calendarApi } = context;
let { moreLinkText } = options;
let { moreCnt } = props;
let range = computeRange(props);
let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
? moreLinkText.call(calendarApi, moreCnt)
: `+${moreCnt} ${moreLinkText}`;
let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text);
let renderProps = {
num: moreCnt,
shortText: `+${moreCnt}`,
text,
view: viewApi,
};
return (y(_, null,
Boolean(props.moreCnt) && (y(ContentContainer, { elTag: props.elTag || 'a', elRef: this.handleLinkEl, elClasses: [
...(props.elClasses || []),
'fc-more-link',
], elStyle: props.elStyle, elAttrs: Object.assign(Object.assign(Object.assign({}, props.elAttrs), createAriaClickAttrs(this.handleClick)), { title: hint, 'aria-expanded': state.isPopoverOpen, 'aria-controls': state.isPopoverOpen ? state.popoverId : '' }), renderProps: renderProps, generatorName: "moreLinkContent", customGenerator: options.moreLinkContent, defaultGenerator: props.defaultGenerator || renderMoreLinkInner, classNameGenerator: options.moreLinkClassNames, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, props.children)),
state.isPopoverOpen && (y(MorePopover, { id: state.popoverId, startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: this.parentEl, alignmentEl: props.alignmentElRef ?
props.alignmentElRef.current :
this.linkEl, alignGridTop: props.alignGridTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose }, props.popoverContent()))));
}));
}
componentDidMount() {
this.updateParentEl();
}
componentDidUpdate() {
this.updateParentEl();
}
updateParentEl() {
if (this.linkEl) {
this.parentEl = elementClosest(this.linkEl, '.fc-view-harness');
}
}
}
function renderMoreLinkInner(props) {
return props.text;
}
function computeRange(props) {
if (props.allDayDate) {
return {
start: props.allDayDate,
end: addDays(props.allDayDate, 1),
};
}
let { hiddenSegs } = props;
return {
start: computeEarliestSegStart(hiddenSegs),
end: computeLatestSegEnd(hiddenSegs),
};
}
function computeEarliestSegStart(segs) {
return segs.reduce(pickEarliestStart).eventRange.range.start;
}
function pickEarliestStart(seg0, seg1) {
return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
}
function computeLatestSegEnd(segs) {
return segs.reduce(pickLatestEnd).eventRange.range.end;
}
function pickLatestEnd(seg0, seg1) {
return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
}
class Store {
constructor() {
this.handlers = [];
}
set(value) {
this.currentValue = value;
for (let handler of this.handlers) {
handler(value);
}
}
subscribe(handler) {
this.handlers.push(handler);
if (this.currentValue !== undefined) {
handler(this.currentValue);
}
}
}
/*
Subscribers will get a LIST of CustomRenderings
*/
class CustomRenderingStore extends Store {
constructor() {
super(...arguments);
this.map = new Map();
}
// for consistent order
handle(customRendering) {
const { map } = this;
let updated = false;
if (customRendering.isActive) {
map.set(customRendering.id, customRendering);
updated = true;
}
else if (map.has(customRendering.id)) {
map.delete(customRendering.id);
updated = true;
}
if (updated) {
this.set(map);
}
}
}
;// ../../node_modules/@fullcalendar/core/index.js
/* unused harmony import specifier */ var core_createFormatter;
/* unused harmony import specifier */ var core_BASE_OPTION_DEFAULTS;
/* unused harmony import specifier */ var core_DateEnv;
/* unused harmony import specifier */ var core_sliceEventStore;
const globalLocales = [];
const MINIMAL_RAW_EN_LOCALE = {
code: 'en',
week: {
dow: 0,
doy: 4, // 4 days need to be within the year to be considered the first week
},
direction: 'ltr',
buttonText: {
prev: 'prev',
next: 'next',
prevYear: 'prev year',
nextYear: 'next year',
year: 'year',
today: 'today',
month: 'month',
week: 'week',
day: 'day',
list: 'list',
},
weekText: 'W',
weekTextLong: 'Week',
closeHint: 'Close',
timeHint: 'Time',
eventHint: 'Event',
allDayText: 'all-day',
moreLinkText: 'more',
noEventsText: 'No events to display',
};
const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), {
// Includes things we don't want other locales to inherit,
// things that derive from other translatable strings.
buttonHints: {
prev: 'Previous $0',
next: 'Next $0',
today(buttonText, unit) {
return (unit === 'day')
? 'Today'
: `This ${buttonText}`;
},
}, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) {
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`;
} });
function organizeRawLocales(explicitRawLocales) {
let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en';
let allRawLocales = globalLocales.concat(explicitRawLocales);
let rawLocaleMap = {
en: RAW_EN_LOCALE,
};
for (let rawLocale of allRawLocales) {
rawLocaleMap[rawLocale.code] = rawLocale;
}
return {
map: rawLocaleMap,
defaultCode,
};
}
function buildLocale(inputSingular, available) {
if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) {
return parseLocale(inputSingular.code, [inputSingular.code], inputSingular);
}
return queryLocale(inputSingular, available);
}
function queryLocale(codeArg, available) {
let codes = [].concat(codeArg || []); // will convert to array
let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE;
return parseLocale(codeArg, codes, raw);
}
function queryRawLocale(codes, available) {
for (let i = 0; i < codes.length; i += 1) {
let parts = codes[i].toLocaleLowerCase().split('-');
for (let j = parts.length; j > 0; j -= 1) {
let simpleId = parts.slice(0, j).join('-');
if (available[simpleId]) {
return available[simpleId];
}
}
}
return null;
}
function parseLocale(codeArg, codes, raw) {
let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']);
delete merged.code; // don't want this part of the options
let { week } = merged;
delete merged.week;
return {
codeArg,
codes,
week,
simpleNumberFormat: new Intl.NumberFormat(codeArg),
options: merged,
};
}
// TODO: easier way to add new hooks? need to update a million things
function createPlugin(input) {
return {
id: guid(),
name: input.name,
premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
deps: input.deps || [],
reducers: input.reducers || [],
isLoadingFuncs: input.isLoadingFuncs || [],
contextInit: [].concat(input.contextInit || []),
eventRefiners: input.eventRefiners || {},
eventDefMemberAdders: input.eventDefMemberAdders || [],
eventSourceRefiners: input.eventSourceRefiners || {},
isDraggableTransformers: input.isDraggableTransformers || [],
eventDragMutationMassagers: input.eventDragMutationMassagers || [],
eventDefMutationAppliers: input.eventDefMutationAppliers || [],
dateSelectionTransformers: input.dateSelectionTransformers || [],
datePointTransforms: input.datePointTransforms || [],
dateSpanTransforms: input.dateSpanTransforms || [],
views: input.views || {},
viewPropsTransformers: input.viewPropsTransformers || [],
isPropsValid: input.isPropsValid || null,
externalDefTransforms: input.externalDefTransforms || [],
viewContainerAppends: input.viewContainerAppends || [],
eventDropTransformers: input.eventDropTransformers || [],
componentInteractions: input.componentInteractions || [],
calendarInteractions: input.calendarInteractions || [],
themeClasses: input.themeClasses || {},
eventSourceDefs: input.eventSourceDefs || [],
cmdFormatter: input.cmdFormatter,
recurringTypes: input.recurringTypes || [],
namedTimeZonedImpl: input.namedTimeZonedImpl,
initialView: input.initialView || '',
elementDraggingImpl: input.elementDraggingImpl,
optionChangeHandlers: input.optionChangeHandlers || {},
scrollGridImpl: input.scrollGridImpl || null,
listenerRefiners: input.listenerRefiners || {},
optionRefiners: input.optionRefiners || {},
propSetHandlers: input.propSetHandlers || {},
};
}
function buildPluginHooks(pluginDefs, globalDefs) {
let currentPluginIds = {};
let hooks = {
premiumReleaseDate: undefined,
reducers: [],
isLoadingFuncs: [],
contextInit: [],
eventRefiners: {},
eventDefMemberAdders: [],
eventSourceRefiners: {},
isDraggableTransformers: [],
eventDragMutationMassagers: [],
eventDefMutationAppliers: [],
dateSelectionTransformers: [],
datePointTransforms: [],
dateSpanTransforms: [],
views: {},
viewPropsTransformers: [],
isPropsValid: null,
externalDefTransforms: [],
viewContainerAppends: [],
eventDropTransformers: [],
componentInteractions: [],
calendarInteractions: [],
themeClasses: {},
eventSourceDefs: [],
cmdFormatter: null,
recurringTypes: [],
namedTimeZonedImpl: null,
initialView: '',
elementDraggingImpl: null,
optionChangeHandlers: {},
scrollGridImpl: null,
listenerRefiners: {},
optionRefiners: {},
propSetHandlers: {},
};
function addDefs(defs) {
for (let def of defs) {
const pluginName = def.name;
const currentId = currentPluginIds[pluginName];
if (currentId === undefined) {
currentPluginIds[pluginName] = def.id;
addDefs(def.deps);
hooks = combineHooks(hooks, def);
}
else if (currentId !== def.id) {
// different ID than the one already added
console.warn(`Duplicate plugin '${pluginName}'`);
}
}
}
if (pluginDefs) {
addDefs(pluginDefs);
}
addDefs(globalDefs);
return hooks;
}
function buildBuildPluginHooks() {
let currentOverrideDefs = [];
let currentGlobalDefs = [];
let currentHooks;
return (overrideDefs, globalDefs) => {
if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
currentHooks = buildPluginHooks(overrideDefs, globalDefs);
}
currentOverrideDefs = overrideDefs;
currentGlobalDefs = globalDefs;
return currentHooks;
};
}
function combineHooks(hooks0, hooks1) {
return {
premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
reducers: hooks0.reducers.concat(hooks1.reducers),
isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
contextInit: hooks0.contextInit.concat(hooks1.contextInit),
eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners),
eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners),
isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
views: Object.assign(Object.assign({}, hooks0.views), hooks1.views),
viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses),
eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl,
initialView: hooks0.initialView || hooks1.initialView,
elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl,
optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers),
scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl,
listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners),
optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners),
propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers),
};
}
function compareOptionalDates(date0, date1) {
if (date0 === undefined) {
return date1;
}
if (date1 === undefined) {
return date0;
}
return new Date(Math.max(date0.valueOf(), date1.valueOf()));
}
class StandardTheme extends internal_common_Theme {
}
StandardTheme.prototype.classes = {
root: 'fc-theme-standard',
tableCellShaded: 'fc-cell-shaded',
buttonGroup: 'fc-button-group',
button: 'fc-button fc-button-primary',
buttonActive: 'fc-button-active',
};
StandardTheme.prototype.baseIconClass = 'fc-icon';
StandardTheme.prototype.iconClasses = {
close: 'fc-icon-x',
prev: 'fc-icon-chevron-left',
next: 'fc-icon-chevron-right',
prevYear: 'fc-icon-chevrons-left',
nextYear: 'fc-icon-chevrons-right',
};
StandardTheme.prototype.rtlIconClasses = {
prev: 'fc-icon-chevron-right',
next: 'fc-icon-chevron-left',
prevYear: 'fc-icon-chevrons-right',
nextYear: 'fc-icon-chevrons-left',
};
StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly
StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon';
StandardTheme.prototype.iconOverridePrefix = 'fc-icon-';
function compileViewDefs(defaultConfigs, overrideConfigs) {
let hash = {};
let viewType;
for (viewType in defaultConfigs) {
ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
}
for (viewType in overrideConfigs) {
ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
}
return hash;
}
function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
if (hash[viewType]) {
return hash[viewType];
}
let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
if (viewDef) {
hash[viewType] = viewDef;
}
return viewDef;
}
function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
let defaultConfig = defaultConfigs[viewType];
let overrideConfig = overrideConfigs[viewType];
let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
let theComponent = queryProp('component');
let superType = queryProp('superType');
let superDef = null;
if (superType) {
if (superType === viewType) {
throw new Error('Can\'t have a custom view type that references itself');
}
superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
}
if (!theComponent && superDef) {
theComponent = superDef.component;
}
if (!theComponent) {
return null; // don't throw a warning, might be settings for a single-unit view
}
return {
type: viewType,
component: theComponent,
defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})),
overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})),
};
}
function parseViewConfigs(inputs) {
return mapHash(inputs, parseViewConfig);
}
function parseViewConfig(input) {
let rawOptions = typeof input === 'function' ?
{ component: input } :
input;
let { component } = rawOptions;
if (rawOptions.content) {
// TODO: remove content/classNames/didMount/etc from options?
component = createViewHookComponent(rawOptions);
}
else if (component && !(component.prototype instanceof BaseComponent)) {
// WHY?: people were using `component` property for `content`
// TODO: converge on one setting name
component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component }));
}
return {
superType: rawOptions.type,
component: component,
rawOptions, // includes type and component too :(
};
}
function createViewHookComponent(options) {
return (viewProps) => (y(ViewContextType.Consumer, null, (context) => (y(ContentContainer, { elTag: "div", elClasses: buildViewClassNames(context.viewSpec), renderProps: Object.assign(Object.assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }), generatorName: undefined, customGenerator: options.content, classNameGenerator: options.classNames, didMount: options.didMount, willUnmount: options.willUnmount }))));
}
function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
let defaultConfigs = parseViewConfigs(defaultInputs);
let overrideConfigs = parseViewConfigs(optionOverrides.views);
let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults));
}
function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
let durationInput = viewDef.overrides.duration ||
viewDef.defaults.duration ||
dynamicOptionOverrides.duration ||
optionOverrides.duration;
let duration = null;
let durationUnit = '';
let singleUnit = '';
let singleUnitOverrides = {};
if (durationInput) {
duration = createDurationCached(durationInput);
if (duration) { // valid?
let denom = greatestDurationDenominator(duration);
durationUnit = denom.unit;
if (denom.value === 1) {
singleUnit = durationUnit;
singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
}
}
}
let queryButtonText = (optionsSubset) => {
let buttonTextMap = optionsSubset.buttonText || {};
let buttonTextKey = viewDef.defaults.buttonTextKey;
if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) {
return buttonTextMap[buttonTextKey];
}
if (buttonTextMap[viewDef.type] != null) {
return buttonTextMap[viewDef.type];
}
if (buttonTextMap[singleUnit] != null) {
return buttonTextMap[singleUnit];
}
return null;
};
let queryButtonTitle = (optionsSubset) => {
let buttonHints = optionsSubset.buttonHints || {};
let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text
if (buttonKey != null && buttonHints[buttonKey] != null) {
return buttonHints[buttonKey];
}
if (buttonHints[viewDef.type] != null) {
return buttonHints[viewDef.type];
}
if (buttonHints[singleUnit] != null) {
return buttonHints[singleUnit];
}
return null;
};
return {
type: viewDef.type,
component: viewDef.component,
duration,
durationUnit,
singleUnit,
optionDefaults: viewDef.defaults,
optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides),
buttonTextOverride: queryButtonText(dynamicOptionOverrides) ||
queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence
viewDef.overrides.buttonText,
buttonTextDefault: queryButtonText(localeDefaults) ||
viewDef.defaults.buttonText ||
queryButtonText(BASE_OPTION_DEFAULTS) ||
viewDef.type,
// not DRY
buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) ||
queryButtonTitle(optionOverrides) ||
viewDef.overrides.buttonHint,
buttonTitleDefault: queryButtonTitle(localeDefaults) ||
viewDef.defaults.buttonHint ||
queryButtonTitle(BASE_OPTION_DEFAULTS),
// will eventually fall back to buttonText
};
}
// hack to get memoization working
let durationInputMap = {};
function createDurationCached(durationInput) {
let json = JSON.stringify(durationInput);
let res = durationInputMap[json];
if (res === undefined) {
res = createDuration(durationInput);
durationInputMap[json] = res;
}
return res;
}
function reduceViewType(viewType, action) {
switch (action.type) {
case 'CHANGE_VIEW_TYPE':
viewType = action.viewType;
}
return viewType;
}
function reduceCurrentDate(currentDate, action) {
switch (action.type) {
case 'CHANGE_DATE':
return action.dateMarker;
default:
return currentDate;
}
}
// should be initialized once and stay constant
// this will change too
function getInitialDate(options, dateEnv, nowManager) {
let initialDateInput = options.initialDate;
// compute the initial ambig-timezone date
if (initialDateInput != null) {
return dateEnv.createMarker(initialDateInput);
}
return nowManager.getDateMarker();
}
function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
switch (action.type) {
case 'SET_OPTION':
return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue });
default:
return dynamicOptionOverrides;
}
}
function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) {
let dp;
switch (action.type) {
case 'CHANGE_VIEW_TYPE':
return dateProfileGenerator.build(action.dateMarker || currentDate);
case 'CHANGE_DATE':
return dateProfileGenerator.build(action.dateMarker);
case 'PREV':
dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate);
if (dp.isValid) {
return dp;
}
break;
case 'NEXT':
dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate);
if (dp.isValid) {
return dp;
}
break;
}
return currentDateProfile;
}
function initEventSources(calendarOptions, dateProfile, context) {
let activeRange = dateProfile ? dateProfile.activeRange : null;
return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context);
}
function reduceEventSources(eventSources, action, dateProfile, context) {
let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
switch (action.type) {
case 'ADD_EVENT_SOURCES': // already parsed
return addSources(eventSources, action.sources, activeRange, context);
case 'REMOVE_EVENT_SOURCE':
return removeSource(eventSources, action.sourceId);
case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
case 'NEXT':
case 'CHANGE_DATE':
case 'CHANGE_VIEW_TYPE':
if (dateProfile) {
return fetchDirtySources(eventSources, activeRange, context);
}
return eventSources;
case 'FETCH_EVENT_SOURCES':
return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type?
arrayToHash(action.sourceIds) :
excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context);
case 'RECEIVE_EVENTS':
case 'RECEIVE_EVENT_ERROR':
return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange);
case 'REMOVE_ALL_EVENT_SOURCES':
return {};
default:
return eventSources;
}
}
function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) {
let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context);
}
function computeEventSourcesLoading(eventSources) {
for (let sourceId in eventSources) {
if (eventSources[sourceId].isFetching) {
return true;
}
}
return false;
}
function addSources(eventSourceHash, sources, fetchRange, context) {
let hash = {};
for (let source of sources) {
hash[source.sourceId] = source;
}
if (fetchRange) {
hash = fetchDirtySources(hash, fetchRange, context);
}
return Object.assign(Object.assign({}, eventSourceHash), hash);
}
function removeSource(eventSourceHash, sourceId) {
return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId);
}
function fetchDirtySources(sourceHash, fetchRange, context) {
return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context);
}
function isSourceDirty(eventSource, fetchRange, context) {
if (!doesSourceNeedRange(eventSource, context)) {
return !eventSource.latestFetchId;
}
return !context.options.lazyFetching ||
!eventSource.fetchRange ||
eventSource.isFetching || // always cancel outdated in-progress fetches
fetchRange.start < eventSource.fetchRange.start ||
fetchRange.end > eventSource.fetchRange.end;
}
function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) {
let nextSources = {};
for (let sourceId in prevSources) {
let source = prevSources[sourceId];
if (sourceIdHash[sourceId]) {
nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context);
}
else {
nextSources[sourceId] = source;
}
}
return nextSources;
}
function fetchSource(eventSource, fetchRange, isRefetch, context) {
let { options, calendarApi } = context;
let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId];
let fetchId = guid();
sourceDef.fetch({
eventSource,
range: fetchRange,
isRefetch,
context,
}, (res) => {
let { rawEvents } = res;
if (options.eventSourceSuccess) {
rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents;
}
if (eventSource.success) {
rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents;
}
context.dispatch({
type: 'RECEIVE_EVENTS',
sourceId: eventSource.sourceId,
fetchId,
fetchRange,
rawEvents,
});
}, (error) => {
let errorHandled = false;
if (options.eventSourceFailure) {
options.eventSourceFailure.call(calendarApi, error);
errorHandled = true;
}
if (eventSource.failure) {
eventSource.failure(error);
errorHandled = true;
}
if (!errorHandled) {
console.warn(error.message, error);
}
context.dispatch({
type: 'RECEIVE_EVENT_ERROR',
sourceId: eventSource.sourceId,
fetchId,
fetchRange,
error,
});
});
return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId });
}
function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) {
let eventSource = sourceHash[sourceId];
if (eventSource && // not already removed
fetchId === eventSource.latestFetchId) {
return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) });
}
return sourceHash;
}
function excludeStaticSources(eventSources, context) {
return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context));
}
function parseInitialSources(rawOptions, context) {
let refiners = buildEventSourceRefiners(context);
let rawSources = [].concat(rawOptions.eventSources || []);
let sources = []; // parsed
if (rawOptions.initialEvents) {
rawSources.unshift(rawOptions.initialEvents);
}
if (rawOptions.events) {
rawSources.unshift(rawOptions.events);
}
for (let rawSource of rawSources) {
let source = parseEventSource(rawSource, context, refiners);
if (source) {
sources.push(source);
}
}
return sources;
}
function doesSourceNeedRange(eventSource, context) {
let defs = context.pluginHooks.eventSourceDefs;
return !defs[eventSource.sourceDefId].ignoreRange;
}
function reduceDateSelection(currentSelection, action) {
switch (action.type) {
case 'UNSELECT_DATES':
return null;
case 'SELECT_DATES':
return action.selection;
default:
return currentSelection;
}
}
function reduceSelectedEvent(currentInstanceId, action) {
switch (action.type) {
case 'UNSELECT_EVENT':
return '';
case 'SELECT_EVENT':
return action.eventInstanceId;
default:
return currentInstanceId;
}
}
function reduceEventDrag(currentDrag, action) {
let newDrag;
switch (action.type) {
case 'UNSET_EVENT_DRAG':
return null;
case 'SET_EVENT_DRAG':
newDrag = action.state;
return {
affectedEvents: newDrag.affectedEvents,
mutatedEvents: newDrag.mutatedEvents,
isEvent: newDrag.isEvent,
};
default:
return currentDrag;
}
}
function reduceEventResize(currentResize, action) {
let newResize;
switch (action.type) {
case 'UNSET_EVENT_RESIZE':
return null;
case 'SET_EVENT_RESIZE':
newResize = action.state;
return {
affectedEvents: newResize.affectedEvents,
mutatedEvents: newResize.mutatedEvents,
isEvent: newResize.isEvent,
};
default:
return currentResize;
}
}
function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
return { header, footer };
}
function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
let sectionWidgets = {};
let viewsWithButtons = [];
let hasTitle = false;
for (let sectionName in sectionStrHash) {
let sectionStr = sectionStrHash[sectionName];
let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi);
sectionWidgets[sectionName] = sectionRes.widgets;
viewsWithButtons.push(...sectionRes.viewsWithButtons);
hasTitle = hasTitle || sectionRes.hasTitle;
}
return { sectionWidgets, viewsWithButtons, hasTitle };
}
/*
BAD: querying icons and text here. should be done at render time
*/
function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined
calendarOptionOverrides, // overrides only!, unrefined :(
theme, viewSpecs, calendarApi) {
let isRtl = calendarOptions.direction === 'rtl';
let calendarCustomButtons = calendarOptions.customButtons || {};
let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {};
let calendarButtonText = calendarOptions.buttonText || {};
let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {};
let calendarButtonHints = calendarOptions.buttonHints || {};
let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
let viewsWithButtons = [];
let hasTitle = false;
let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => {
if (buttonName === 'title') {
hasTitle = true;
return { buttonName };
}
let customButtonProps;
let viewSpec;
let buttonClick;
let buttonIcon; // only one of these will be set
let buttonText; // "
let buttonHint;
// ^ for the title="" attribute, for accessibility
if ((customButtonProps = calendarCustomButtons[buttonName])) {
buttonClick = (ev) => {
if (customButtonProps.click) {
customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context?
}
};
(buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) ||
(buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
(buttonText = customButtonProps.text);
buttonHint = customButtonProps.hint || customButtonProps.text;
}
else if ((viewSpec = viewSpecs[buttonName])) {
viewsWithButtons.push(buttonName);
buttonClick = () => {
calendarApi.changeView(buttonName);
};
(buttonText = viewSpec.buttonTextOverride) ||
(buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
(buttonText = viewSpec.buttonTextDefault);
let textFallback = viewSpec.buttonTextOverride ||
viewSpec.buttonTextDefault;
buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride ||
viewSpec.buttonTitleDefault ||
calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName
textFallback);
}
else if (calendarApi[buttonName]) { // a calendarApi method
buttonClick = () => {
calendarApi[buttonName]();
};
(buttonText = calendarButtonTextOverrides[buttonName]) ||
(buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
(buttonText = calendarButtonText[buttonName]); // everything else is considered default
if (buttonName === 'prevYear' || buttonName === 'nextYear') {
let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next';
buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] ||
calendarButtonHints[prevOrNext], [
calendarButtonText.year || 'year',
'year',
], calendarButtonText[buttonName]);
}
else {
buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] ||
calendarButtonHints[buttonName], [
calendarButtonText[navUnit] || navUnit,
navUnit,
], calendarButtonText[buttonName]);
}
}
return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint };
})));
return { widgets, viewsWithButtons, hasTitle };
}
// always represents the current view. otherwise, it'd need to change value every time date changes
class ViewImpl {
constructor(type, getCurrentData, dateEnv) {
this.type = type;
this.getCurrentData = getCurrentData;
this.dateEnv = dateEnv;
}
get calendar() {
return this.getCurrentData().calendarApi;
}
get title() {
return this.getCurrentData().viewTitle;
}
get activeStart() {
return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
}
get activeEnd() {
return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
}
get currentStart() {
return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
}
get currentEnd() {
return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
}
getOption(name) {
return this.getCurrentData().options[name]; // are the view-specific options
}
}
let eventSourceDef$2 = {
ignoreRange: true,
parseMeta(refined) {
if (Array.isArray(refined.events)) {
return refined.events;
}
return null;
},
fetch(arg, successCallback) {
successCallback({
rawEvents: arg.eventSource.meta,
});
},
};
const arrayEventSourcePlugin = createPlugin({
name: 'array-event-source',
eventSourceDefs: [eventSourceDef$2],
});
let eventSourceDef$1 = {
parseMeta(refined) {
if (typeof refined.events === 'function') {
return refined.events;
}
return null;
},
fetch(arg, successCallback, errorCallback) {
const { dateEnv } = arg.context;
const func = arg.eventSource.meta;
unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback);
},
};
const funcEventSourcePlugin = createPlugin({
name: 'func-event-source',
eventSourceDefs: [eventSourceDef$1],
});
const JSON_FEED_EVENT_SOURCE_REFINERS = {
method: String,
extraParams: identity,
startParam: String,
endParam: String,
timeZoneParam: String,
};
let eventSourceDef = {
parseMeta(refined) {
if (refined.url && (refined.format === 'json' || !refined.format)) {
return {
url: refined.url,
format: 'json',
method: (refined.method || 'GET').toUpperCase(),
extraParams: refined.extraParams,
startParam: refined.startParam,
endParam: refined.endParam,
timeZoneParam: refined.timeZoneParam,
};
}
return null;
},
fetch(arg, successCallback, errorCallback) {
const { meta } = arg.eventSource;
const requestParams = buildRequestParams(meta, arg.range, arg.context);
requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => {
successCallback({ rawEvents, response });
}, errorCallback);
},
};
const jsonFeedEventSourcePlugin = createPlugin({
name: 'json-event-source',
eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS,
eventSourceDefs: [eventSourceDef],
});
function buildRequestParams(meta, range, context) {
let { dateEnv, options } = context;
let startParam;
let endParam;
let timeZoneParam;
let customRequestParams;
let params = {};
startParam = meta.startParam;
if (startParam == null) {
startParam = options.startParam;
}
endParam = meta.endParam;
if (endParam == null) {
endParam = options.endParam;
}
timeZoneParam = meta.timeZoneParam;
if (timeZoneParam == null) {
timeZoneParam = options.timeZoneParam;
}
// retrieve any outbound GET/POST data from the options
if (typeof meta.extraParams === 'function') {
// supplied as a function that returns a key/value object
customRequestParams = meta.extraParams();
}
else {
// probably supplied as a straight key/value object
customRequestParams = meta.extraParams || {};
}
Object.assign(params, customRequestParams);
params[startParam] = dateEnv.formatIso(range.start);
params[endParam] = dateEnv.formatIso(range.end);
if (dateEnv.timeZone !== 'local') {
params[timeZoneParam] = dateEnv.timeZone;
}
return params;
}
const SIMPLE_RECURRING_REFINERS = {
daysOfWeek: identity,
startTime: createDuration,
endTime: createDuration,
duration: createDuration,
startRecur: identity,
endRecur: identity,
};
let recurring = {
parse(refined, dateEnv) {
if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) {
let recurringData = {
daysOfWeek: refined.daysOfWeek || null,
startTime: refined.startTime || null,
endTime: refined.endTime || null,
startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null,
endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null,
dateEnv,
};
let duration;
if (refined.duration) {
duration = refined.duration;
}
if (!duration && refined.startTime && refined.endTime) {
duration = subtractDurations(refined.endTime, refined.startTime);
}
return {
allDayGuess: Boolean(!refined.startTime && !refined.endTime),
duration,
typeData: recurringData, // doesn't need endTime anymore but oh well
};
}
return null;
},
expand(typeData, framingRange, dateEnv) {
let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur });
if (clippedFramingRange) {
return expandRanges(typeData.daysOfWeek, typeData.startTime, typeData.dateEnv, dateEnv, clippedFramingRange);
}
return [];
},
};
const simpleRecurringEventsPlugin = createPlugin({
name: 'simple-recurring-event',
recurringTypes: [recurring],
eventRefiners: SIMPLE_RECURRING_REFINERS,
});
function expandRanges(daysOfWeek, startTime, eventDateEnv, calendarDateEnv, framingRange) {
let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null;
let dayMarker = startOfDay(framingRange.start);
let endMarker = framingRange.end;
let instanceStarts = [];
// https://github.com/fullcalendar/fullcalendar/issues/7934
if (startTime) {
if (startTime.milliseconds < 0) {
// possible for next-day to have negative business hours that go into current day
endMarker = addDays(endMarker, 1);
}
else if (startTime.milliseconds >= 1000 * 60 * 60 * 24) {
// possible for prev-day to have >24hr business hours that go into current day
dayMarker = addDays(dayMarker, -1);
}
}
while (dayMarker < endMarker) {
let instanceStart;
// if everyday, or this particular day-of-week
if (!dowHash || dowHash[dayMarker.getUTCDay()]) {
if (startTime) {
instanceStart = calendarDateEnv.add(dayMarker, startTime);
}
else {
instanceStart = dayMarker;
}
instanceStarts.push(calendarDateEnv.createMarker(eventDateEnv.toDate(instanceStart)));
}
dayMarker = addDays(dayMarker, 1);
}
return instanceStarts;
}
const changeHandlerPlugin = createPlugin({
name: 'change-handler',
optionChangeHandlers: {
events(events, context) {
handleEventSources([events], context);
},
eventSources: handleEventSources,
},
});
/*
BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out
*/
function handleEventSources(inputs, context) {
let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources);
if (unfoundSources.length === 1 &&
inputs.length === 1 &&
Array.isArray(unfoundSources[0]._raw) &&
Array.isArray(inputs[0])) {
context.dispatch({
type: 'RESET_RAW_EVENTS',
sourceId: unfoundSources[0].sourceId,
rawEvents: inputs[0],
});
return;
}
let newInputs = [];
for (let input of inputs) {
let inputFound = false;
for (let i = 0; i < unfoundSources.length; i += 1) {
if (unfoundSources[i]._raw === input) {
unfoundSources.splice(i, 1); // delete
inputFound = true;
break;
}
}
if (!inputFound) {
newInputs.push(input);
}
}
for (let unfoundSource of unfoundSources) {
context.dispatch({
type: 'REMOVE_EVENT_SOURCE',
sourceId: unfoundSource.sourceId,
});
}
for (let newInput of newInputs) {
context.calendarApi.addEventSource(newInput);
}
}
function handleDateProfile(dateProfile, context) {
context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi }));
}
function handleEventStore(eventStore, context) {
let { emitter } = context;
if (emitter.hasHandlers('eventsSet')) {
emitter.trigger('eventsSet', buildEventApis(eventStore, context));
}
}
/*
this array is exposed on the root namespace so that UMD plugins can add to it.
see the rollup-bundles script.
*/
const globalPlugins = [
arrayEventSourcePlugin,
funcEventSourcePlugin,
jsonFeedEventSourcePlugin,
simpleRecurringEventsPlugin,
changeHandlerPlugin,
createPlugin({
name: 'misc',
isLoadingFuncs: [
(state) => computeEventSourcesLoading(state.eventSources),
],
propSetHandlers: {
dateProfile: handleDateProfile,
eventStore: handleEventStore,
},
}),
];
class TaskRunner {
constructor(runTaskOption, drainedOption) {
this.runTaskOption = runTaskOption;
this.drainedOption = drainedOption;
this.queue = [];
this.delayedRunner = new DelayedRunner(this.drain.bind(this));
}
request(task, delay) {
this.queue.push(task);
this.delayedRunner.request(delay);
}
pause(scope) {
this.delayedRunner.pause(scope);
}
resume(scope, force) {
this.delayedRunner.resume(scope, force);
}
drain() {
let { queue } = this;
while (queue.length) {
let completedTasks = [];
let task;
while ((task = queue.shift())) {
this.runTask(task);
completedTasks.push(task);
}
this.drained(completedTasks);
} // keep going, in case new tasks were added in the drained handler
}
runTask(task) {
if (this.runTaskOption) {
this.runTaskOption(task);
}
}
drained(completedTasks) {
if (this.drainedOption) {
this.drainedOption(completedTasks);
}
}
}
// Computes what the title at the top of the calendarApi should be for this view
function buildTitle(dateProfile, viewOptions, dateEnv) {
let range;
// for views that span a large unit of time, show the proper interval, ignoring stray days before and after
if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
range = dateProfile.currentRange;
}
else { // for day units or smaller, use the actual day range
range = dateProfile.activeRange;
}
return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), {
isEndExclusive: dateProfile.isRangeAllDay,
defaultSeparator: viewOptions.titleRangeSeparator,
});
}
// Generates the format string that should be used to generate the title for the current date range.
// Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
function buildTitleFormat(dateProfile) {
let { currentRangeUnit } = dateProfile;
if (currentRangeUnit === 'year') {
return { year: 'numeric' };
}
if (currentRangeUnit === 'month') {
return { year: 'numeric', month: 'long' }; // like "September 2014"
}
let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
if (days !== null && days > 1) {
// multi-day range. shorter, like "Sep 9 - 10 2014"
return { year: 'numeric', month: 'short', day: 'numeric' };
}
// one day. longer, like "September 9 2014"
return { year: 'numeric', month: 'long', day: 'numeric' };
}
/*
TODO: test switching timezones when NO timezone plugin
*/
class CalendarNowManager {
constructor() {
this.resetListeners = new Set();
}
handleInput(dateEnv, // will change if timezone setup changed
nowInput) {
const oldDateEnv = this.dateEnv;
if (dateEnv !== oldDateEnv) {
if (typeof nowInput === 'function') {
this.nowFn = nowInput;
}
else if (!oldDateEnv) { // first time?
this.nowAnchorDate = dateEnv.toDate(nowInput
? dateEnv.createMarker(nowInput)
: dateEnv.createNowMarker());
this.nowAnchorQueried = Date.now();
}
this.dateEnv = dateEnv;
// not first time? fire reset handlers
if (oldDateEnv) {
for (const resetListener of this.resetListeners.values()) {
resetListener();
}
}
}
}
getDateMarker() {
return this.nowAnchorDate
? this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf() +
(Date.now() - this.nowAnchorQueried))
: this.dateEnv.createMarker(this.nowFn());
}
addResetListener(handler) {
this.resetListeners.add(handler);
}
removeResetListener(handler) {
this.resetListeners.delete(handler);
}
}
// in future refactor, do the redux-style function(state=initial) for initial-state
// also, whatever is happening in constructor, have it happen in action queue too
class CalendarDataManager {
constructor(props) {
this.computeCurrentViewData = memoize(this._computeCurrentViewData);
this.organizeRawLocales = memoize(organizeRawLocales);
this.buildLocale = memoize(buildLocale);
this.buildPluginHooks = buildBuildPluginHooks();
this.buildDateEnv = memoize(buildDateEnv$1);
this.buildTheme = memoize(buildTheme);
this.parseToolbars = memoize(parseToolbars);
this.buildViewSpecs = memoize(buildViewSpecs);
this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
this.buildViewApi = memoize(buildViewApi);
this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual);
this.buildEventUiBases = memoize(buildEventUiBases);
this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
this.buildTitle = memoize(buildTitle);
this.nowManager = new CalendarNowManager();
this.emitter = new Emitter();
this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this));
this.currentCalendarOptionsInput = {};
this.currentCalendarOptionsRefined = {};
this.currentViewOptionsInput = {};
this.currentViewOptionsRefined = {};
this.currentCalendarOptionsRefiners = {};
this.optionsForRefining = [];
this.optionsForHandling = [];
this.getCurrentData = () => this.data;
this.dispatch = (action) => {
this.actionRunner.request(action); // protects against recursive calls to _handleAction
};
this.props = props;
this.actionRunner.pause();
this.nowManager = new CalendarNowManager();
let dynamicOptionOverrides = {};
let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView;
let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
// wire things up
// TODO: not DRY
props.calendarApi.currentDataManager = this;
this.emitter.setThisContext(props.calendarApi);
this.emitter.setOptions(currentViewData.options);
let calendarContext = {
nowManager: this.nowManager,
dateEnv: optionsData.dateEnv,
options: optionsData.calendarOptions,
pluginHooks: optionsData.pluginHooks,
calendarApi: props.calendarApi,
dispatch: this.dispatch,
emitter: this.emitter,
getCurrentData: this.getCurrentData,
};
let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager);
let dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) {
currentDate = dateProfile.currentRange.start;
}
// needs to be after setThisContext
for (let callback of optionsData.pluginHooks.contextInit) {
callback(calendarContext);
}
// NOT DRY
let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext);
let initialState = {
dynamicOptionOverrides,
currentViewType,
currentDate,
dateProfile,
businessHours: this.parseContextBusinessHours(calendarContext),
eventSources,
eventUiBases: {},
eventStore: createEmptyEventStore(),
renderableEventStore: createEmptyEventStore(),
dateSelection: null,
eventSelection: '',
eventDrag: null,
eventResize: null,
selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig,
};
let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState);
for (let reducer of optionsData.pluginHooks.reducers) {
Object.assign(initialState, reducer(null, null, contextAndState));
}
if (computeIsLoading(initialState, calendarContext)) {
this.emitter.trigger('loading', true); // NOT DRY
}
this.state = initialState;
this.updateData();
this.actionRunner.resume();
}
resetOptions(optionOverrides, changedOptionNames) {
let { props } = this;
if (changedOptionNames === undefined) {
props.optionOverrides = optionOverrides;
}
else {
props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides);
this.optionsForRefining.push(...changedOptionNames);
}
if (changedOptionNames === undefined || changedOptionNames.length) {
this.actionRunner.request({
type: 'NOTHING',
});
}
}
_handleAction(action) {
let { props, state, emitter } = this;
let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action);
let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
let currentViewType = reduceViewType(state.currentViewType, action);
let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
// wire things up
// TODO: not DRY
props.calendarApi.currentDataManager = this;
emitter.setThisContext(props.calendarApi);
emitter.setOptions(currentViewData.options);
let calendarContext = {
nowManager: this.nowManager,
dateEnv: optionsData.dateEnv,
options: optionsData.calendarOptions,
pluginHooks: optionsData.pluginHooks,
calendarApi: props.calendarApi,
dispatch: this.dispatch,
emitter,
getCurrentData: this.getCurrentData,
};
let { currentDate, dateProfile } = state;
if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack
dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
}
currentDate = reduceCurrentDate(currentDate, action);
dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator);
if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator
action.type === 'NEXT' || // "
!rangeContainsMarker(dateProfile.currentRange, currentDate)) {
currentDate = dateProfile.currentRange.start;
}
let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext);
let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext);
let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading
let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ?
(state.renderableEventStore || eventStore) : // try from previous state
eventStore;
let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj
let eventUiBySource = this.buildEventUiBySource(eventSources);
let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
let newState = {
dynamicOptionOverrides,
currentViewType,
currentDate,
dateProfile,
eventSources,
eventStore,
renderableEventStore,
selectionConfig,
eventUiBases,
businessHours: this.parseContextBusinessHours(calendarContext),
dateSelection: reduceDateSelection(state.dateSelection, action),
eventSelection: reduceSelectedEvent(state.eventSelection, action),
eventDrag: reduceEventDrag(state.eventDrag, action),
eventResize: reduceEventResize(state.eventResize, action),
};
let contextAndState = Object.assign(Object.assign({}, calendarContext), newState);
for (let reducer of optionsData.pluginHooks.reducers) {
Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value
}
let wasLoading = computeIsLoading(state, calendarContext);
let isLoading = computeIsLoading(newState, calendarContext);
// TODO: use propSetHandlers in plugin system
if (!wasLoading && isLoading) {
emitter.trigger('loading', true);
}
else if (wasLoading && !isLoading) {
emitter.trigger('loading', false);
}
this.state = newState;
if (props.onAction) {
props.onAction(action);
}
}
updateData() {
let { props, state } = this;
let oldData = this.data;
let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi);
let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides);
let data = this.data = Object.assign(Object.assign(Object.assign({ nowManager: this.nowManager, viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state);
let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
let oldCalendarOptions = oldData && oldData.calendarOptions;
let newCalendarOptions = optionsData.calendarOptions;
if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) {
if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
// hack
state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data);
state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv);
state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv);
}
for (let optionName in changeHandlers) {
if (this.optionsForHandling.indexOf(optionName) !== -1 ||
oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
changeHandlers[optionName](newCalendarOptions[optionName], data);
}
}
}
this.optionsForHandling = [];
if (props.onData) {
props.onData(data);
}
}
computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
// TODO: blacklist options that are handled by optionChangeHandlers
if (!this.optionsForRefining.length &&
optionOverrides === this.stableOptionOverrides &&
dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
return this.stableCalendarOptionsData;
}
let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
warnUnknownOptions(extra);
let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator);
let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults);
let theme = this.buildTheme(refinedOptions, pluginHooks);
let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi);
return this.stableCalendarOptionsData = {
calendarOptions: refinedOptions,
pluginHooks,
dateEnv,
viewSpecs,
theme,
toolbarConfig,
localeDefaults,
availableRawLocales: availableLocaleData.map,
};
}
// always called from behind a memoizer
processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
let { locales, locale } = mergeRawOptions([
BASE_OPTION_DEFAULTS,
optionOverrides,
dynamicOptionOverrides,
]);
let availableLocaleData = this.organizeRawLocales(locales);
let availableRawLocales = availableLocaleData.map;
let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
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);
let extra = {};
let raw = mergeRawOptions([
BASE_OPTION_DEFAULTS,
localeDefaults,
optionOverrides,
dynamicOptionOverrides,
]);
let refined = {};
let currentRaw = this.currentCalendarOptionsInput;
let currentRefined = this.currentCalendarOptionsRefined;
let anyChanges = false;
for (let optionName in raw) {
if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
(optionName in currentRaw) &&
COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) {
refined[optionName] = currentRefined[optionName];
}
else if (refiners[optionName]) {
refined[optionName] = refiners[optionName](raw[optionName]);
anyChanges = true;
}
else {
extra[optionName] = currentRaw[optionName];
}
}
if (anyChanges) {
this.currentCalendarOptionsInput = raw;
this.currentCalendarOptionsRefined = refined;
this.stableOptionOverrides = optionOverrides;
this.stableDynamicOptionOverrides = dynamicOptionOverrides;
}
this.optionsForHandling.push(...this.optionsForRefining);
this.optionsForRefining = [];
return {
rawOptions: this.currentCalendarOptionsInput,
refinedOptions: this.currentCalendarOptionsRefined,
pluginHooks,
availableLocaleData,
localeDefaults,
extra,
};
}
_computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
let viewSpec = optionsData.viewSpecs[viewType];
if (!viewSpec) {
throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
}
let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
warnUnknownOptions(extra);
this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now);
let dateProfileGenerator = this.buildDateProfileGenerator({
dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
nowManager: this.nowManager,
duration: viewSpec.duration,
durationUnit: viewSpec.durationUnit,
usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
dateEnv: optionsData.dateEnv,
calendarApi: this.props.calendarApi,
slotMinTime: refinedOptions.slotMinTime,
slotMaxTime: refinedOptions.slotMaxTime,
showNonCurrentDates: refinedOptions.showNonCurrentDates,
dayCount: refinedOptions.dayCount,
dateAlignment: refinedOptions.dateAlignment,
dateIncrement: refinedOptions.dateIncrement,
hiddenDays: refinedOptions.hiddenDays,
weekends: refinedOptions.weekends,
validRangeInput: refinedOptions.validRange,
visibleRangeInput: refinedOptions.visibleRange,
fixedWeekCount: refinedOptions.fixedWeekCount,
});
let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
}
processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
let raw = mergeRawOptions([
BASE_OPTION_DEFAULTS,
viewSpec.optionDefaults,
localeDefaults,
optionOverrides,
viewSpec.optionOverrides,
dynamicOptionOverrides,
]);
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);
let refined = {};
let currentRaw = this.currentViewOptionsInput;
let currentRefined = this.currentViewOptionsRefined;
let anyChanges = false;
let extra = {};
for (let optionName in raw) {
if (raw[optionName] === currentRaw[optionName] ||
(COMPLEX_OPTION_COMPARATORS[optionName] &&
COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) {
refined[optionName] = currentRefined[optionName];
}
else {
if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
(COMPLEX_OPTION_COMPARATORS[optionName] &&
COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
refined[optionName] = this.currentCalendarOptionsRefined[optionName];
}
}
else if (refiners[optionName]) {
refined[optionName] = refiners[optionName](raw[optionName]);
}
else {
extra[optionName] = raw[optionName];
}
anyChanges = true;
}
}
if (anyChanges) {
this.currentViewOptionsInput = raw;
this.currentViewOptionsRefined = refined;
}
return {
rawOptions: this.currentViewOptionsInput,
refinedOptions: this.currentViewOptionsRefined,
extra,
};
}
}
function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) {
let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
return new DateEnv({
calendarSystem: 'gregory',
timeZone,
namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl,
locale,
weekNumberCalculation,
firstDay,
weekText,
cmdFormatter: pluginHooks.cmdFormatter,
defaultSeparator,
});
}
function buildTheme(options, pluginHooks) {
let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme;
return new ThemeClass(options);
}
function buildDateProfileGenerator(props) {
let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
return new DateProfileGeneratorClass(props);
}
function buildViewApi(type, getCurrentData, dateEnv) {
return new ViewImpl(type, getCurrentData, dateEnv);
}
function buildEventUiBySource(eventSources) {
return mapHash(eventSources, (eventSource) => eventSource.ui);
}
function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
let eventUiBases = { '': eventUiSingleBase };
for (let defId in eventDefs) {
let def = eventDefs[defId];
if (def.sourceId && eventUiBySource[def.sourceId]) {
eventUiBases[defId] = eventUiBySource[def.sourceId];
}
}
return eventUiBases;
}
function buildViewUiProps(calendarContext) {
let { options } = calendarContext;
return {
eventUiSingleBase: createEventUi({
display: options.eventDisplay,
editable: options.editable,
startEditable: options.eventStartEditable,
durationEditable: options.eventDurationEditable,
constraint: options.eventConstraint,
overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
allow: options.eventAllow,
backgroundColor: options.eventBackgroundColor,
borderColor: options.eventBorderColor,
textColor: options.eventTextColor,
color: options.eventColor,
// classNames: options.eventClassNames // render hook will handle this
}, calendarContext),
selectionConfig: createEventUi({
constraint: options.selectConstraint,
overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
allow: options.selectAllow,
}, calendarContext),
};
}
function computeIsLoading(state, context) {
for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
if (isLoadingFunc(state)) {
return true;
}
}
return false;
}
function parseContextBusinessHours(calendarContext) {
return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
}
function warnUnknownOptions(options, viewName) {
for (let optionName in options) {
console.warn(`Unknown option '${optionName}'` +
(viewName ? ` for view '${viewName}'` : ''));
}
}
class ToolbarSection extends BaseComponent {
render() {
let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
return y('div', { className: 'fc-toolbar-chunk' }, ...children);
}
renderWidgetGroup(widgetGroup) {
let { props } = this;
let { theme } = this.context;
let children = [];
let isOnlyButtons = true;
for (let widget of widgetGroup) {
let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget;
if (buttonName === 'title') {
isOnlyButtons = false;
children.push(y("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title));
}
else {
let isPressed = buttonName === props.activeButton;
let isDisabled = (!props.isTodayEnabled && buttonName === 'today') ||
(!props.isPrevEnabled && buttonName === 'prev') ||
(!props.isNextEnabled && buttonName === 'next');
let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')];
if (isPressed) {
buttonClasses.push(theme.getClass('buttonActive'));
}
children.push(y("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? y("span", { className: buttonIcon, role: "img" }) : '')));
}
}
if (children.length > 1) {
let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || '';
return y('div', { className: groupClassName }, ...children);
}
return children[0];
}
}
class Toolbar extends BaseComponent {
render() {
let { model, extraClassName } = this.props;
let forceLtr = false;
let startContent;
let endContent;
let sectionWidgets = model.sectionWidgets;
let centerContent = sectionWidgets.center;
if (sectionWidgets.left) {
forceLtr = true;
startContent = sectionWidgets.left;
}
else {
startContent = sectionWidgets.start;
}
if (sectionWidgets.right) {
forceLtr = true;
endContent = sectionWidgets.right;
}
else {
endContent = sectionWidgets.end;
}
let classNames = [
extraClassName || '',
'fc-toolbar',
forceLtr ? 'fc-toolbar-ltr' : '',
];
return (y("div", { className: classNames.join(' ') },
this.renderSection('start', startContent || []),
this.renderSection('center', centerContent || []),
this.renderSection('end', endContent || [])));
}
renderSection(key, widgetGroups) {
let { props } = this;
return (y(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, navUnit: props.navUnit, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled, titleId: props.titleId }));
}
}
class ViewHarness extends BaseComponent {
constructor() {
super(...arguments);
this.state = {
availableWidth: null,
};
this.handleEl = (el) => {
this.el = el;
setRef(this.props.elRef, el);
this.updateAvailableWidth();
};
this.handleResize = () => {
this.updateAvailableWidth();
};
}
render() {
let { props, state } = this;
let { aspectRatio } = props;
let classNames = [
'fc-view-harness',
(aspectRatio || props.liquid || props.height)
? 'fc-view-harness-active' // harness controls the height
: 'fc-view-harness-passive', // let the view do the height
];
let height = '';
let paddingBottom = '';
if (aspectRatio) {
if (state.availableWidth !== null) {
height = state.availableWidth / aspectRatio;
}
else {
// while waiting to know availableWidth, we can't set height to *zero*
// because will cause lots of unnecessary scrollbars within scrollgrid.
// BETTER: don't start rendering ANYTHING yet until we know container width
// NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
paddingBottom = `${(1 / aspectRatio) * 100}%`;
}
}
else {
height = props.height || '';
}
return (y("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children));
}
componentDidMount() {
this.context.addResizeHandler(this.handleResize);
}
componentWillUnmount() {
this.context.removeResizeHandler(this.handleResize);
}
updateAvailableWidth() {
if (this.el && // needed. but why?
this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
) {
this.setState({ availableWidth: this.el.offsetWidth });
}
}
}
/*
Detects when the user clicks on an event within a DateComponent
*/
class EventClicking extends internal_common_Interaction {
constructor(settings) {
super(settings);
this.handleSegClick = (ev, segEl) => {
let { component } = this;
let { context } = component;
let seg = getElSeg(segEl);
if (seg && // might be the <div> surrounding the more link
component.isValidSegDownEl(ev.target)) {
// our way to simulate a link click for elements that can't be <a> tags
// grab before trigger fired in case trigger trashes DOM thru rerendering
let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
context.emitter.trigger('eventClick', {
el: segEl,
event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance),
jsEvent: ev,
view: context.viewApi,
});
if (url && !ev.defaultPrevented) {
window.location.href = url;
}
}
};
this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events
this.handleSegClick);
}
}
/*
Triggers events and adds/removes core classNames when the user's pointer
enters/leaves event-elements of a component.
*/
class EventHovering extends internal_common_Interaction {
constructor(settings) {
super(settings);
// for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
this.handleEventElRemove = (el) => {
if (el === this.currentSegEl) {
this.handleSegLeave(null, this.currentSegEl);
}
};
this.handleSegEnter = (ev, segEl) => {
if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
this.currentSegEl = segEl;
this.triggerEvent('eventMouseEnter', ev, segEl);
}
};
this.handleSegLeave = (ev, segEl) => {
if (this.currentSegEl) {
this.currentSegEl = null;
this.triggerEvent('eventMouseLeave', ev, segEl);
}
};
this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events
this.handleSegEnter, this.handleSegLeave);
}
destroy() {
this.removeHoverListeners();
}
triggerEvent(publicEvName, ev, segEl) {
let { component } = this;
let { context } = component;
let seg = getElSeg(segEl);
if (!ev || component.isValidSegDownEl(ev.target)) {
context.emitter.trigger(publicEvName, {
el: segEl,
event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance),
jsEvent: ev,
view: context.viewApi,
});
}
}
}
class CalendarContent extends PureComponent {
constructor() {
super(...arguments);
this.buildViewContext = memoize(buildViewContext);
this.buildViewPropTransformers = memoize(buildViewPropTransformers);
this.buildToolbarProps = memoize(buildToolbarProps);
this.headerRef = d();
this.footerRef = d();
this.interactionsStore = {};
// eslint-disable-next-line
this.state = {
viewLabelId: getUniqueDomId(),
};
// Component Registration
// -----------------------------------------------------------------------------------------------------------------
this.registerInteractiveComponent = (component, settingsInput) => {
let settings = parseInteractionSettings(component, settingsInput);
let DEFAULT_INTERACTIONS = [
EventClicking,
EventHovering,
];
let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions);
let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
this.interactionsStore[component.uid] = interactions;
interactionSettingsStore[component.uid] = settings;
};
this.unregisterInteractiveComponent = (component) => {
let listeners = this.interactionsStore[component.uid];
if (listeners) {
for (let listener of listeners) {
listener.destroy();
}
delete this.interactionsStore[component.uid];
}
delete interactionSettingsStore[component.uid];
};
// Resizing
// -----------------------------------------------------------------------------------------------------------------
this.resizeRunner = new DelayedRunner(() => {
this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ?
this.props.emitter.trigger('windowResize', { view: this.props.viewApi });
});
this.handleWindowResize = (ev) => {
let { options } = this.props;
if (options.handleWindowResize &&
ev.target === window // avoid jqui events
) {
this.resizeRunner.request(options.windowResizeDelay);
}
};
}
/*
renders INSIDE of an outer div
*/
render() {
let { props } = this;
let { toolbarConfig, options } = props;
let viewVGrow = false;
let viewHeight = '';
let viewAspectRatio;
if (props.isHeightAuto || props.forPrint) {
viewHeight = '';
}
else if (options.height != null) {
viewVGrow = true;
}
else if (options.contentHeight != null) {
viewHeight = options.contentHeight;
}
else {
viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
}
let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
? this.state.viewLabelId
: undefined;
return (y(ViewContextType.Provider, { value: viewContext },
y(NowTimer, { unit: "day" }, (nowDate) => {
let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, nowDate, props.viewTitle);
return (y(_, null,
toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))),
y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId },
this.renderView(props),
this.buildAppendContent()),
toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps)))));
})));
}
componentDidMount() {
let { props } = this;
this.calendarInteractions = props.pluginHooks.calendarInteractions
.map((CalendarInteractionClass) => new CalendarInteractionClass(props));
window.addEventListener('resize', this.handleWindowResize);
let { propSetHandlers } = props.pluginHooks;
for (let propName in propSetHandlers) {
propSetHandlers[propName](props[propName], props);
}
}
componentDidUpdate(prevProps) {
let { props } = this;
let { propSetHandlers } = props.pluginHooks;
for (let propName in propSetHandlers) {
if (props[propName] !== prevProps[propName]) {
propSetHandlers[propName](props[propName], props);
}
}
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleWindowResize);
this.resizeRunner.clear();
for (let interaction of this.calendarInteractions) {
interaction.destroy();
}
this.props.emitter.trigger('_unmount');
}
buildAppendContent() {
let { props } = this;
let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props));
return y(_, {}, ...children);
}
renderView(props) {
let { pluginHooks } = props;
let { viewSpec } = props;
let viewProps = {
dateProfile: props.dateProfile,
businessHours: props.businessHours,
eventStore: props.renderableEventStore,
eventUiBases: props.eventUiBases,
dateSelection: props.dateSelection,
eventSelection: props.eventSelection,
eventDrag: props.eventDrag,
eventResize: props.eventResize,
isHeightAuto: props.isHeightAuto,
forPrint: props.forPrint,
};
let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
for (let transformer of transformers) {
Object.assign(viewProps, transformer.transform(viewProps, props));
}
let ViewComponent = viewSpec.component;
return (y(ViewComponent, Object.assign({}, viewProps)));
}
}
function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) {
// don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason
let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false);
let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false);
return {
title,
activeButton: viewSpec.type,
navUnit: viewSpec.singleUnit,
isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
isPrevEnabled: prevInfo.isValid,
isNextEnabled: nextInfo.isValid,
};
}
// Plugin
// -----------------------------------------------------------------------------------------------------------------
function buildViewPropTransformers(theClasses) {
return theClasses.map((TheClass) => new TheClass());
}
class Calendar extends CalendarImpl {
constructor(el, optionOverrides = {}) {
super();
this.isRendering = false;
this.isRendered = false;
this.currentClassNames = [];
this.customContentRenderId = 0;
this.handleAction = (action) => {
// actions we know we want to render immediately
switch (action.type) {
case 'SET_EVENT_DRAG':
case 'SET_EVENT_RESIZE':
this.renderRunner.tryDrain();
}
};
this.handleData = (data) => {
this.currentData = data;
this.renderRunner.request(data.calendarOptions.rerenderDelay);
};
this.handleRenderRequest = () => {
if (this.isRendering) {
this.isRendered = true;
let { currentData } = this;
flushSync(() => {
D(y(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => {
this.setClassNames(classNames);
this.setHeight(height);
return (y(RenderId.Provider, { value: this.customContentRenderId },
y(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData))));
}), this.el);
});
}
else if (this.isRendered) {
this.isRendered = false;
D(null, this.el);
this.setClassNames([]);
this.setHeight('');
}
};
ensureElHasStyles(el);
this.el = el;
this.renderRunner = new DelayedRunner(this.handleRenderRequest);
new CalendarDataManager({
optionOverrides,
calendarApi: this,
onAction: this.handleAction,
onData: this.handleData,
});
}
render() {
let wasRendering = this.isRendering;
if (!wasRendering) {
this.isRendering = true;
}
else {
this.customContentRenderId += 1;
}
this.renderRunner.request();
if (wasRendering) {
this.updateSize();
}
}
destroy() {
if (this.isRendering) {
this.isRendering = false;
this.renderRunner.request();
}
}
updateSize() {
flushSync(() => {
super.updateSize();
});
}
batchRendering(func) {
this.renderRunner.pause('batchRendering');
func();
this.renderRunner.resume('batchRendering');
}
pauseRendering() {
this.renderRunner.pause('pauseRendering');
}
resumeRendering() {
this.renderRunner.resume('pauseRendering', true);
}
resetOptions(optionOverrides, changedOptionNames) {
this.currentDataManager.resetOptions(optionOverrides, changedOptionNames);
}
setClassNames(classNames) {
if (!isArraysEqual(classNames, this.currentClassNames)) {
let { classList } = this.el;
for (let className of this.currentClassNames) {
classList.remove(className);
}
for (let className of classNames) {
classList.add(className);
}
this.currentClassNames = classNames;
}
}
setHeight(height) {
applyStyleProp(this.el, 'height', height);
}
}
function formatDate(dateInput, options = {}) {
let dateEnv = buildDateEnv(options);
let formatter = core_createFormatter(options);
let dateMeta = dateEnv.createMarkerMeta(dateInput);
if (!dateMeta) { // TODO: warning?
return '';
}
return dateEnv.format(dateMeta.marker, formatter, {
forcedTzo: dateMeta.forcedTzo,
});
}
function formatRange(startInput, endInput, options) {
let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object
let formatter = core_createFormatter(options);
let startMeta = dateEnv.createMarkerMeta(startInput);
let endMeta = dateEnv.createMarkerMeta(endInput);
if (!startMeta || !endMeta) { // TODO: warning?
return '';
}
return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, {
forcedStartTzo: startMeta.forcedTzo,
forcedEndTzo: endMeta.forcedTzo,
isEndExclusive: options.isEndExclusive,
defaultSeparator: core_BASE_OPTION_DEFAULTS.defaultRangeSeparator,
});
}
// TODO: more DRY and optimized
function buildDateEnv(settings) {
let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere
return new core_DateEnv(Object.assign(Object.assign({ timeZone: core_BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale }));
}
// HELPERS
/*
if nextDayThreshold is specified, slicing is done in an all-day fashion.
you can get nextDayThreshold from context.nextDayThreshold
*/
function sliceEvents(props, allDay) {
return core_sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg;
}
const core_version = '6.1.20';
;// ../../node_modules/@fullcalendar/interaction/index.js
/* unused harmony import specifier */ var interaction_createEmptyEventStore;
/* unused harmony import specifier */ var interaction_eventTupleToStore;
/* unused harmony import specifier */ var interaction_isInteractionValid;
/* unused harmony import specifier */ var interaction_enableCursor;
/* unused harmony import specifier */ var interaction_disableCursor;
/* unused harmony import specifier */ var interaction_EventImpl;
/* unused harmony import specifier */ var interaction_interactionSettingsStore;
/* unused harmony import specifier */ var interaction_parseDragMeta;
/* unused harmony import specifier */ var interaction_elementMatches;
/* unused harmony import specifier */ var interaction_refineEventDef;
/* unused harmony import specifier */ var interaction_parseEventDef;
/* unused harmony import specifier */ var interaction_getDefaultEventEnd;
/* unused harmony import specifier */ var interaction_createEventInstance;
/* unused harmony import specifier */ var interaction_config;
/* unused harmony import specifier */ var interaction_BASE_OPTION_DEFAULTS;
config.touchMouseIgnoreWait = 500;
let ignoreMouseDepth = 0;
let listenerCnt = 0;
let isWindowTouchMoveCancelled = false;
/*
Uses a "pointer" abstraction, which monitors UI events for both mouse and touch.
Tracks when the pointer "drags" on a certain element, meaning down+move+up.
Also, tracks if there was touch-scrolling.
Also, can prevent touch-scrolling from happening.
Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement.
emits:
- pointerdown
- pointermove
- pointerup
*/
class PointerDragging {
constructor(containerEl) {
this.subjectEl = null;
// options that can be directly assigned by caller
this.selector = ''; // will cause subjectEl in all emitted events to be this element
this.handleSelector = '';
this.shouldIgnoreMove = false;
this.shouldWatchScroll = true; // for simulating pointermove on scroll
// internal states
this.isDragging = false;
this.isTouchDragging = false;
this.wasTouchScroll = false;
// Mouse
// ----------------------------------------------------------------------------------------------------
this.handleMouseDown = (ev) => {
if (!this.shouldIgnoreMouse() &&
isPrimaryMouseButton(ev) &&
this.tryStart(ev)) {
let pev = this.createEventFromMouse(ev, true);
this.emitter.trigger('pointerdown', pev);
this.initScrollWatch(pev);
if (!this.shouldIgnoreMove) {
document.addEventListener('mousemove', this.handleMouseMove);
}
document.addEventListener('mouseup', this.handleMouseUp);
}
};
this.handleMouseMove = (ev) => {
let pev = this.createEventFromMouse(ev);
this.recordCoords(pev);
this.emitter.trigger('pointermove', pev);
};
this.handleMouseUp = (ev) => {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
this.emitter.trigger('pointerup', this.createEventFromMouse(ev));
this.cleanup(); // call last so that pointerup has access to props
};
// Touch
// ----------------------------------------------------------------------------------------------------
this.handleTouchStart = (ev) => {
if (this.tryStart(ev)) {
this.isTouchDragging = true;
let pev = this.createEventFromTouch(ev, true);
this.emitter.trigger('pointerdown', pev);
this.initScrollWatch(pev);
// unlike mouse, need to attach to target, not document
// https://stackoverflow.com/a/45760014
let targetEl = ev.target;
if (!this.shouldIgnoreMove) {
targetEl.addEventListener('touchmove', this.handleTouchMove);
}
targetEl.addEventListener('touchend', this.handleTouchEnd);
targetEl.addEventListener('touchcancel', this.handleTouchEnd); // treat it as a touch end
// attach a handler to get called when ANY scroll action happens on the page.
// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
// http://stackoverflow.com/a/32954565/96342
window.addEventListener('scroll', this.handleTouchScroll, true);
}
};
this.handleTouchMove = (ev) => {
let pev = this.createEventFromTouch(ev);
this.recordCoords(pev);
this.emitter.trigger('pointermove', pev);
};
this.handleTouchEnd = (ev) => {
if (this.isDragging) { // done to guard against touchend followed by touchcancel
let targetEl = ev.target;
targetEl.removeEventListener('touchmove', this.handleTouchMove);
targetEl.removeEventListener('touchend', this.handleTouchEnd);
targetEl.removeEventListener('touchcancel', this.handleTouchEnd);
window.removeEventListener('scroll', this.handleTouchScroll, true); // useCaptured=true
this.emitter.trigger('pointerup', this.createEventFromTouch(ev));
this.cleanup(); // call last so that pointerup has access to props
this.isTouchDragging = false;
startIgnoringMouse();
}
};
this.handleTouchScroll = () => {
this.wasTouchScroll = true;
};
this.handleScroll = (ev) => {
if (!this.shouldIgnoreMove) {
let pageX = (window.scrollX - this.prevScrollX) + this.prevPageX;
let pageY = (window.scrollY - this.prevScrollY) + this.prevPageY;
this.emitter.trigger('pointermove', {
origEvent: ev,
isTouch: this.isTouchDragging,
subjectEl: this.subjectEl,
pageX,
pageY,
deltaX: pageX - this.origPageX,
deltaY: pageY - this.origPageY,
});
}
};
this.containerEl = containerEl;
this.emitter = new Emitter();
containerEl.addEventListener('mousedown', this.handleMouseDown);
containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true });
listenerCreated();
}
destroy() {
this.containerEl.removeEventListener('mousedown', this.handleMouseDown);
this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
listenerDestroyed();
}
tryStart(ev) {
let subjectEl = this.querySubjectEl(ev);
let downEl = ev.target;
if (subjectEl &&
(!this.handleSelector || elementClosest(downEl, this.handleSelector))) {
this.subjectEl = subjectEl;
this.isDragging = true; // do this first so cancelTouchScroll will work
this.wasTouchScroll = false;
return true;
}
return false;
}
cleanup() {
isWindowTouchMoveCancelled = false;
this.isDragging = false;
this.subjectEl = null;
// keep wasTouchScroll around for later access
this.destroyScrollWatch();
}
querySubjectEl(ev) {
if (this.selector) {
return elementClosest(ev.target, this.selector);
}
return this.containerEl;
}
shouldIgnoreMouse() {
return ignoreMouseDepth || this.isTouchDragging;
}
// can be called by user of this class, to cancel touch-based scrolling for the current drag
cancelTouchScroll() {
if (this.isDragging) {
isWindowTouchMoveCancelled = true;
}
}
// Scrolling that simulates pointermoves
// ----------------------------------------------------------------------------------------------------
initScrollWatch(ev) {
if (this.shouldWatchScroll) {
this.recordCoords(ev);
window.addEventListener('scroll', this.handleScroll, true); // useCapture=true
}
}
recordCoords(ev) {
if (this.shouldWatchScroll) {
this.prevPageX = ev.pageX;
this.prevPageY = ev.pageY;
this.prevScrollX = window.scrollX;
this.prevScrollY = window.scrollY;
}
}
destroyScrollWatch() {
if (this.shouldWatchScroll) {
window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true
}
}
// Event Normalization
// ----------------------------------------------------------------------------------------------------
createEventFromMouse(ev, isFirst) {
let deltaX = 0;
let deltaY = 0;
// TODO: repeat code
if (isFirst) {
this.origPageX = ev.pageX;
this.origPageY = ev.pageY;
}
else {
deltaX = ev.pageX - this.origPageX;
deltaY = ev.pageY - this.origPageY;
}
return {
origEvent: ev,
isTouch: false,
subjectEl: this.subjectEl,
pageX: ev.pageX,
pageY: ev.pageY,
deltaX,
deltaY,
};
}
createEventFromTouch(ev, isFirst) {
let touches = ev.touches;
let pageX;
let pageY;
let deltaX = 0;
let deltaY = 0;
// if touch coords available, prefer,
// because FF would give bad ev.pageX ev.pageY
if (touches && touches.length) {
pageX = touches[0].pageX;
pageY = touches[0].pageY;
}
else {
pageX = ev.pageX;
pageY = ev.pageY;
}
// TODO: repeat code
if (isFirst) {
this.origPageX = pageX;
this.origPageY = pageY;
}
else {
deltaX = pageX - this.origPageX;
deltaY = pageY - this.origPageY;
}
return {
origEvent: ev,
isTouch: true,
subjectEl: this.subjectEl,
pageX,
pageY,
deltaX,
deltaY,
};
}
}
// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
function isPrimaryMouseButton(ev) {
return ev.button === 0 && !ev.ctrlKey;
}
// Ignoring fake mouse events generated by touch
// ----------------------------------------------------------------------------------------------------
function startIgnoringMouse() {
ignoreMouseDepth += 1;
setTimeout(() => {
ignoreMouseDepth -= 1;
}, config.touchMouseIgnoreWait);
}
// We want to attach touchmove as early as possible for Safari
// ----------------------------------------------------------------------------------------------------
function listenerCreated() {
listenerCnt += 1;
if (listenerCnt === 1) {
window.addEventListener('touchmove', onWindowTouchMove, { passive: false });
}
}
function listenerDestroyed() {
listenerCnt -= 1;
if (!listenerCnt) {
window.removeEventListener('touchmove', onWindowTouchMove, { passive: false });
}
}
function onWindowTouchMove(ev) {
if (isWindowTouchMoveCancelled) {
ev.preventDefault();
}
}
/*
An effect in which an element follows the movement of a pointer across the screen.
The moving element is a clone of some other element.
Must call start + handleMove + stop.
*/
class ElementMirror {
constructor() {
this.isVisible = false; // must be explicitly enabled
this.sourceEl = null;
this.mirrorEl = null;
this.sourceElRect = null; // screen coords relative to viewport
// options that can be set directly by caller
this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues
this.zIndex = 9999;
this.revertDuration = 0;
}
start(sourceEl, pageX, pageY) {
this.sourceEl = sourceEl;
this.sourceElRect = this.sourceEl.getBoundingClientRect();
this.origScreenX = pageX - window.scrollX;
this.origScreenY = pageY - window.scrollY;
this.deltaX = 0;
this.deltaY = 0;
this.updateElPosition();
}
handleMove(pageX, pageY) {
this.deltaX = (pageX - window.scrollX) - this.origScreenX;
this.deltaY = (pageY - window.scrollY) - this.origScreenY;
this.updateElPosition();
}
// can be called before start
setIsVisible(bool) {
if (bool) {
if (!this.isVisible) {
if (this.mirrorEl) {
this.mirrorEl.style.display = '';
}
this.isVisible = bool; // needs to happen before updateElPosition
this.updateElPosition(); // because was not updating the position while invisible
}
}
else if (this.isVisible) {
if (this.mirrorEl) {
this.mirrorEl.style.display = 'none';
}
this.isVisible = bool;
}
}
// always async
stop(needsRevertAnimation, callback) {
let done = () => {
this.cleanup();
callback();
};
if (needsRevertAnimation &&
this.mirrorEl &&
this.isVisible &&
this.revertDuration && // if 0, transition won't work
(this.deltaX || this.deltaY) // if same coords, transition won't work
) {
this.doRevertAnimation(done, this.revertDuration);
}
else {
setTimeout(done, 0);
}
}
doRevertAnimation(callback, revertDuration) {
let mirrorEl = this.mirrorEl;
let finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened
mirrorEl.style.transition =
'top ' + revertDuration + 'ms,' +
'left ' + revertDuration + 'ms';
applyStyle(mirrorEl, {
left: finalSourceElRect.left,
top: finalSourceElRect.top,
});
whenTransitionDone(mirrorEl, () => {
mirrorEl.style.transition = '';
callback();
});
}
cleanup() {
if (this.mirrorEl) {
removeElement(this.mirrorEl);
this.mirrorEl = null;
}
this.sourceEl = null;
}
updateElPosition() {
if (this.sourceEl && this.isVisible) {
applyStyle(this.getMirrorEl(), {
left: this.sourceElRect.left + this.deltaX,
top: this.sourceElRect.top + this.deltaY,
});
}
}
getMirrorEl() {
let sourceElRect = this.sourceElRect;
let mirrorEl = this.mirrorEl;
if (!mirrorEl) {
mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true
// we don't want long taps or any mouse interaction causing selection/menus.
// would use preventSelection(), but that prevents selectstart, causing problems.
mirrorEl.style.userSelect = 'none';
mirrorEl.style.webkitUserSelect = 'none';
mirrorEl.style.pointerEvents = 'none';
mirrorEl.classList.add('fc-event-dragging');
applyStyle(mirrorEl, {
position: 'fixed',
zIndex: this.zIndex,
visibility: '',
boxSizing: 'border-box',
width: sourceElRect.right - sourceElRect.left,
height: sourceElRect.bottom - sourceElRect.top,
right: 'auto',
bottom: 'auto',
margin: 0,
});
this.parentNode.appendChild(mirrorEl);
}
return mirrorEl;
}
}
/*
Is a cache for a given element's scroll information (all the info that ScrollController stores)
in addition the "client rectangle" of the element.. the area within the scrollbars.
The cache can be in one of two modes:
- doesListening:false - ignores when the container is scrolled by someone else
- doesListening:true - watch for scrolling and update the cache
*/
class ScrollGeomCache extends ScrollController {
constructor(scrollController, doesListening) {
super();
this.handleScroll = () => {
this.scrollTop = this.scrollController.getScrollTop();
this.scrollLeft = this.scrollController.getScrollLeft();
this.handleScrollChange();
};
this.scrollController = scrollController;
this.doesListening = doesListening;
this.scrollTop = this.origScrollTop = scrollController.getScrollTop();
this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft();
this.scrollWidth = scrollController.getScrollWidth();
this.scrollHeight = scrollController.getScrollHeight();
this.clientWidth = scrollController.getClientWidth();
this.clientHeight = scrollController.getClientHeight();
this.clientRect = this.computeClientRect(); // do last in case it needs cached values
if (this.doesListening) {
this.getEventTarget().addEventListener('scroll', this.handleScroll);
}
}
destroy() {
if (this.doesListening) {
this.getEventTarget().removeEventListener('scroll', this.handleScroll);
}
}
getScrollTop() {
return this.scrollTop;
}
getScrollLeft() {
return this.scrollLeft;
}
setScrollTop(top) {
this.scrollController.setScrollTop(top);
if (!this.doesListening) {
// we are not relying on the element to normalize out-of-bounds scroll values
// so we need to sanitize ourselves
this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0);
this.handleScrollChange();
}
}
setScrollLeft(top) {
this.scrollController.setScrollLeft(top);
if (!this.doesListening) {
// we are not relying on the element to normalize out-of-bounds scroll values
// so we need to sanitize ourselves
this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0);
this.handleScrollChange();
}
}
getClientWidth() {
return this.clientWidth;
}
getClientHeight() {
return this.clientHeight;
}
getScrollWidth() {
return this.scrollWidth;
}
getScrollHeight() {
return this.scrollHeight;
}
handleScrollChange() {
}
}
class ElementScrollGeomCache extends ScrollGeomCache {
constructor(el, doesListening) {
super(new ElementScrollController(el), doesListening);
}
getEventTarget() {
return this.scrollController.el;
}
computeClientRect() {
return computeInnerRect(this.scrollController.el);
}
}
class WindowScrollGeomCache extends ScrollGeomCache {
constructor(doesListening) {
super(new WindowScrollController(), doesListening);
}
getEventTarget() {
return window;
}
computeClientRect() {
return {
left: this.scrollLeft,
right: this.scrollLeft + this.clientWidth,
top: this.scrollTop,
bottom: this.scrollTop + this.clientHeight,
};
}
// the window is the only scroll object that changes it's rectangle relative
// to the document's topleft as it scrolls
handleScrollChange() {
this.clientRect = this.computeClientRect();
}
}
// If available we are using native "performance" API instead of "Date"
// Read more about it on MDN:
// https://developer.mozilla.org/en-US/docs/Web/API/Performance
const getTime = typeof performance === 'function' ? performance.now : Date.now;
/*
For a pointer interaction, automatically scrolls certain scroll containers when the pointer
approaches the edge.
The caller must call start + handleMove + stop.
*/
class AutoScroller {
constructor() {
// options that can be set by caller
this.isEnabled = true;
this.scrollQuery = [window, '.fc-scroller'];
this.edgeThreshold = 50; // pixels
this.maxVelocity = 300; // pixels per second
// internal state
this.pointerScreenX = null;
this.pointerScreenY = null;
this.isAnimating = false;
this.scrollCaches = null;
// protect against the initial pointerdown being too close to an edge and starting the scroll
this.everMovedUp = false;
this.everMovedDown = false;
this.everMovedLeft = false;
this.everMovedRight = false;
this.animate = () => {
if (this.isAnimating) { // wasn't cancelled between animation calls
let edge = this.computeBestEdge(this.pointerScreenX + window.scrollX, this.pointerScreenY + window.scrollY);
if (edge) {
let now = getTime();
this.handleSide(edge, (now - this.msSinceRequest) / 1000);
this.requestAnimation(now);
}
else {
this.isAnimating = false; // will stop animation
}
}
};
}
start(pageX, pageY, scrollStartEl) {
if (this.isEnabled) {
this.scrollCaches = this.buildCaches(scrollStartEl);
this.pointerScreenX = null;
this.pointerScreenY = null;
this.everMovedUp = false;
this.everMovedDown = false;
this.everMovedLeft = false;
this.everMovedRight = false;
this.handleMove(pageX, pageY);
}
}
handleMove(pageX, pageY) {
if (this.isEnabled) {
let pointerScreenX = pageX - window.scrollX;
let pointerScreenY = pageY - window.scrollY;
let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY;
let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX;
if (yDelta < 0) {
this.everMovedUp = true;
}
else if (yDelta > 0) {
this.everMovedDown = true;
}
if (xDelta < 0) {
this.everMovedLeft = true;
}
else if (xDelta > 0) {
this.everMovedRight = true;
}
this.pointerScreenX = pointerScreenX;
this.pointerScreenY = pointerScreenY;
if (!this.isAnimating) {
this.isAnimating = true;
this.requestAnimation(getTime());
}
}
}
stop() {
if (this.isEnabled) {
this.isAnimating = false; // will stop animation
for (let scrollCache of this.scrollCaches) {
scrollCache.destroy();
}
this.scrollCaches = null;
}
}
requestAnimation(now) {
this.msSinceRequest = now;
requestAnimationFrame(this.animate);
}
handleSide(edge, seconds) {
let { scrollCache } = edge;
let { edgeThreshold } = this;
let invDistance = edgeThreshold - edge.distance;
let velocity = // the closer to the edge, the faster we scroll
((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic
this.maxVelocity * seconds;
let sign = 1;
switch (edge.name) {
case 'left':
sign = -1;
// falls through
case 'right':
scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign);
break;
case 'top':
sign = -1;
// falls through
case 'bottom':
scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign);
break;
}
}
// left/top are relative to document topleft
computeBestEdge(left, top) {
let { edgeThreshold } = this;
let bestSide = null;
let scrollCaches = this.scrollCaches || [];
for (let scrollCache of scrollCaches) {
let rect = scrollCache.clientRect;
let leftDist = left - rect.left;
let rightDist = rect.right - left;
let topDist = top - rect.top;
let bottomDist = rect.bottom - top;
// completely within the rect?
if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() &&
(!bestSide || bestSide.distance > topDist)) {
bestSide = { scrollCache, name: 'top', distance: topDist };
}
if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() &&
(!bestSide || bestSide.distance > bottomDist)) {
bestSide = { scrollCache, name: 'bottom', distance: bottomDist };
}
/*
TODO: fix broken RTL scrolling. canScrollLeft always returning false
https://github.com/fullcalendar/fullcalendar/issues/4837
*/
if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() &&
(!bestSide || bestSide.distance > leftDist)) {
bestSide = { scrollCache, name: 'left', distance: leftDist };
}
if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() &&
(!bestSide || bestSide.distance > rightDist)) {
bestSide = { scrollCache, name: 'right', distance: rightDist };
}
}
}
return bestSide;
}
buildCaches(scrollStartEl) {
return this.queryScrollEls(scrollStartEl).map((el) => {
if (el === window) {
return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls
}
return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls
});
}
queryScrollEls(scrollStartEl) {
let els = [];
for (let query of this.scrollQuery) {
if (typeof query === 'object') {
els.push(query);
}
else {
/*
TODO: in the future, always have auto-scroll happen on element where current Hit came from
Ticket: https://github.com/fullcalendar/fullcalendar/issues/4593
*/
els.push(...Array.prototype.slice.call(scrollStartEl.getRootNode().querySelectorAll(query)));
}
}
return els;
}
}
/*
Monitors dragging on an element. Has a number of high-level features:
- minimum distance required before dragging
- minimum wait time ("delay") before dragging
- a mirror element that follows the pointer
*/
class FeaturefulElementDragging extends ElementDragging {
constructor(containerEl, selector) {
super(containerEl);
this.containerEl = containerEl;
// options that can be directly set by caller
// the caller can also set the PointerDragging's options as well
this.delay = null;
this.minDistance = 0;
this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag
this.mirrorNeedsRevert = false;
this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup
this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation
this.isDelayEnded = false;
this.isDistanceSurpassed = false;
this.delayTimeoutId = null;
this.onPointerDown = (ev) => {
if (!this.isDragging) { // so new drag doesn't happen while revert animation is going
this.isInteracting = true;
this.isDelayEnded = false;
this.isDistanceSurpassed = false;
preventSelection(document.body);
preventContextMenu(document.body);
// prevent links from being visited if there's an eventual drag.
// also prevents selection in older browsers (maybe?).
// not necessary for touch, besides, browser would complain about passiveness.
if (!ev.isTouch) {
ev.origEvent.preventDefault();
}
this.emitter.trigger('pointerdown', ev);
if (this.isInteracting && // not destroyed via pointerdown handler
!this.pointer.shouldIgnoreMove) {
// actions related to initiating dragstart+dragmove+dragend...
this.mirror.setIsVisible(false); // reset. caller must set-visible
this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down
this.startDelay(ev);
if (!this.minDistance) {
this.handleDistanceSurpassed(ev);
}
}
}
};
this.onPointerMove = (ev) => {
if (this.isInteracting) {
this.emitter.trigger('pointermove', ev);
if (!this.isDistanceSurpassed) {
let minDistance = this.minDistance;
let distanceSq; // current distance from the origin, squared
let { deltaX, deltaY } = ev;
distanceSq = deltaX * deltaX + deltaY * deltaY;
if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
this.handleDistanceSurpassed(ev);
}
}
if (this.isDragging) {
// a real pointer move? (not one simulated by scrolling)
if (ev.origEvent.type !== 'scroll') {
this.mirror.handleMove(ev.pageX, ev.pageY);
this.autoScroller.handleMove(ev.pageX, ev.pageY);
}
this.emitter.trigger('dragmove', ev);
}
}
};
this.onPointerUp = (ev) => {
if (this.isInteracting) {
this.isInteracting = false;
allowSelection(document.body);
allowContextMenu(document.body);
this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert
if (this.isDragging) {
this.autoScroller.stop();
this.tryStopDrag(ev); // which will stop the mirror
}
if (this.delayTimeoutId) {
clearTimeout(this.delayTimeoutId);
this.delayTimeoutId = null;
}
}
};
let pointer = this.pointer = new PointerDragging(containerEl);
pointer.emitter.on('pointerdown', this.onPointerDown);
pointer.emitter.on('pointermove', this.onPointerMove);
pointer.emitter.on('pointerup', this.onPointerUp);
if (selector) {
pointer.selector = selector;
}
this.mirror = new ElementMirror();
this.autoScroller = new AutoScroller();
}
destroy() {
this.pointer.destroy();
// HACK: simulate a pointer-up to end the current drag
// TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire)
this.onPointerUp({});
}
startDelay(ev) {
if (typeof this.delay === 'number') {
this.delayTimeoutId = setTimeout(() => {
this.delayTimeoutId = null;
this.handleDelayEnd(ev);
}, this.delay); // not assignable to number!
}
else {
this.handleDelayEnd(ev);
}
}
handleDelayEnd(ev) {
this.isDelayEnded = true;
this.tryStartDrag(ev);
}
handleDistanceSurpassed(ev) {
this.isDistanceSurpassed = true;
this.tryStartDrag(ev);
}
tryStartDrag(ev) {
if (this.isDelayEnded && this.isDistanceSurpassed) {
if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) {
this.isDragging = true;
this.mirrorNeedsRevert = false;
this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl);
this.emitter.trigger('dragstart', ev);
if (this.touchScrollAllowed === false) {
this.pointer.cancelTouchScroll();
}
}
}
}
tryStopDrag(ev) {
// .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events
// that come from the document to fire beforehand. much more convenient this way.
this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev));
}
stopDrag(ev) {
this.isDragging = false;
this.emitter.trigger('dragend', ev);
}
// fill in the implementations...
setIgnoreMove(bool) {
this.pointer.shouldIgnoreMove = bool;
}
setMirrorIsVisible(bool) {
this.mirror.setIsVisible(bool);
}
setMirrorNeedsRevert(bool) {
this.mirrorNeedsRevert = bool;
}
setAutoScrollEnabled(bool) {
this.autoScroller.isEnabled = bool;
}
}
/*
When this class is instantiated, it records the offset of an element (relative to the document topleft),
and continues to monitor scrolling, updating the cached coordinates if it needs to.
Does not access the DOM after instantiation, so highly performant.
Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element
and an determine if a given point is inside the combined clipping rectangle.
*/
class OffsetTracker {
constructor(el) {
this.el = el;
this.origRect = computeRect(el);
// will work fine for divs that have overflow:hidden
this.scrollCaches = getClippingParents(el).map((scrollEl) => new ElementScrollGeomCache(scrollEl, true));
}
destroy() {
for (let scrollCache of this.scrollCaches) {
scrollCache.destroy();
}
}
computeLeft() {
let left = this.origRect.left;
for (let scrollCache of this.scrollCaches) {
left += scrollCache.origScrollLeft - scrollCache.getScrollLeft();
}
return left;
}
computeTop() {
let top = this.origRect.top;
for (let scrollCache of this.scrollCaches) {
top += scrollCache.origScrollTop - scrollCache.getScrollTop();
}
return top;
}
isWithinClipping(pageX, pageY) {
let point = { left: pageX, top: pageY };
for (let scrollCache of this.scrollCaches) {
if (!isIgnoredClipping(scrollCache.getEventTarget()) &&
!pointInsideRect(point, scrollCache.clientRect)) {
return false;
}
}
return true;
}
}
// certain clipping containers should never constrain interactions, like <html> and <body>
// https://github.com/fullcalendar/fullcalendar/issues/3615
function isIgnoredClipping(node) {
let tagName = node.tagName;
return tagName === 'HTML' || tagName === 'BODY';
}
/*
Tracks movement over multiple droppable areas (aka "hits")
that exist in one or more DateComponents.
Relies on an existing draggable.
emits:
- pointerdown
- dragstart
- hitchange - fires initially, even if not over a hit
- pointerup
- (hitchange - again, to null, if ended over a hit)
- dragend
*/
class HitDragging {
constructor(dragging, droppableStore) {
// options that can be set by caller
this.useSubjectCenter = false;
this.requireInitial = true; // if doesn't start out on a hit, won't emit any events
this.disablePointCheck = false;
this.initialHit = null;
this.movingHit = null;
this.finalHit = null; // won't ever be populated if shouldIgnoreMove
this.handlePointerDown = (ev) => {
let { dragging } = this;
this.initialHit = null;
this.movingHit = null;
this.finalHit = null;
this.prepareHits();
this.processFirstCoord(ev);
if (this.initialHit || !this.requireInitial) {
dragging.setIgnoreMove(false);
// TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :(
this.emitter.trigger('pointerdown', ev);
}
else {
dragging.setIgnoreMove(true);
}
};
this.handleDragStart = (ev) => {
this.emitter.trigger('dragstart', ev);
this.handleMove(ev, true); // force = fire even if initially null
};
this.handleDragMove = (ev) => {
this.emitter.trigger('dragmove', ev);
this.handleMove(ev);
};
this.handlePointerUp = (ev) => {
this.releaseHits();
this.emitter.trigger('pointerup', ev);
};
this.handleDragEnd = (ev) => {
if (this.movingHit) {
this.emitter.trigger('hitupdate', null, true, ev);
}
this.finalHit = this.movingHit;
this.movingHit = null;
this.emitter.trigger('dragend', ev);
};
this.droppableStore = droppableStore;
dragging.emitter.on('pointerdown', this.handlePointerDown);
dragging.emitter.on('dragstart', this.handleDragStart);
dragging.emitter.on('dragmove', this.handleDragMove);
dragging.emitter.on('pointerup', this.handlePointerUp);
dragging.emitter.on('dragend', this.handleDragEnd);
this.dragging = dragging;
this.emitter = new Emitter();
}
// sets initialHit
// sets coordAdjust
processFirstCoord(ev) {
let origPoint = { left: ev.pageX, top: ev.pageY };
let adjustedPoint = origPoint;
let subjectEl = ev.subjectEl;
let subjectRect;
if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot
subjectRect = computeRect(subjectEl);
adjustedPoint = constrainPoint(adjustedPoint, subjectRect);
}
let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top);
if (initialHit) {
if (this.useSubjectCenter && subjectRect) {
let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect);
if (slicedSubjectRect) {
adjustedPoint = getRectCenter(slicedSubjectRect);
}
}
this.coordAdjust = diffPoints(adjustedPoint, origPoint);
}
else {
this.coordAdjust = { left: 0, top: 0 };
}
}
handleMove(ev, forceHandle) {
let hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top);
if (forceHandle || !isHitsEqual(this.movingHit, hit)) {
this.movingHit = hit;
this.emitter.trigger('hitupdate', hit, false, ev);
}
}
prepareHits() {
this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => {
interactionSettings.component.prepareHits();
return new OffsetTracker(interactionSettings.el);
});
}
releaseHits() {
let { offsetTrackers } = this;
for (let id in offsetTrackers) {
offsetTrackers[id].destroy();
}
this.offsetTrackers = {};
}
queryHitForOffset(offsetLeft, offsetTop) {
let { droppableStore, offsetTrackers } = this;
let bestHit = null;
for (let id in droppableStore) {
let component = droppableStore[id].component;
let offsetTracker = offsetTrackers[id];
if (offsetTracker && // wasn't destroyed mid-drag
offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
let originLeft = offsetTracker.computeLeft();
let originTop = offsetTracker.computeTop();
let positionLeft = offsetLeft - originLeft;
let positionTop = offsetTop - originTop;
let { origRect } = offsetTracker;
let width = origRect.right - origRect.left;
let height = origRect.bottom - origRect.top;
if (
// must be within the element's bounds
positionLeft >= 0 && positionLeft < width &&
positionTop >= 0 && positionTop < height) {
let hit = component.queryHit(positionLeft, positionTop, width, height);
if (hit && (
// make sure the hit is within activeRange, meaning it's not a dead cell
rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) &&
// Ensure the component we are querying for the hit is accessibly my the pointer
// Prevents obscured calendars (ex: under a modal dialog) from accepting hit
// https://github.com/fullcalendar/fullcalendar/issues/5026
(this.disablePointCheck ||
offsetTracker.el.contains(offsetTracker.el.getRootNode().elementFromPoint(
// add-back origins to get coordinate relative to top-left of window viewport
positionLeft + originLeft - window.scrollX, positionTop + originTop - window.scrollY))) &&
(!bestHit || hit.layer > bestHit.layer)) {
hit.componentId = id;
hit.context = component.context;
// TODO: better way to re-orient rectangle
hit.rect.left += originLeft;
hit.rect.right += originLeft;
hit.rect.top += originTop;
hit.rect.bottom += originTop;
bestHit = hit;
}
}
}
}
return bestHit;
}
}
function isHitsEqual(hit0, hit1) {
if (!hit0 && !hit1) {
return true;
}
if (Boolean(hit0) !== Boolean(hit1)) {
return false;
}
return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan);
}
function buildDatePointApiWithContext(dateSpan, context) {
let props = {};
for (let transform of context.pluginHooks.datePointTransforms) {
Object.assign(props, transform(dateSpan, context));
}
Object.assign(props, buildDatePointApi(dateSpan, context.dateEnv));
return props;
}
function buildDatePointApi(span, dateEnv) {
return {
date: dateEnv.toDate(span.range.start),
dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }),
allDay: span.allDay,
};
}
/*
Monitors when the user clicks on a specific date/time of a component.
A pointerdown+pointerup on the same "hit" constitutes a click.
*/
class DateClicking extends internal_common_Interaction {
constructor(settings) {
super(settings);
this.handlePointerDown = (pev) => {
let { dragging } = this;
let downEl = pev.origEvent.target;
// do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
dragging.setIgnoreMove(!this.component.isValidDateDownEl(downEl));
};
// won't even fire if moving was ignored
this.handleDragEnd = (ev) => {
let { component } = this;
let { pointer } = this.dragging;
if (!pointer.wasTouchScroll) {
let { initialHit, finalHit } = this.hitDragging;
if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) {
let { context } = component;
let arg = Object.assign(Object.assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view });
context.emitter.trigger('dateClick', arg);
}
}
};
// we DO want to watch pointer moves because otherwise finalHit won't get populated
this.dragging = new FeaturefulElementDragging(settings.el);
this.dragging.autoScroller.isEnabled = false;
let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
hitDragging.emitter.on('pointerdown', this.handlePointerDown);
hitDragging.emitter.on('dragend', this.handleDragEnd);
}
destroy() {
this.dragging.destroy();
}
}
/*
Tracks when the user selects a portion of time of a component,
constituted by a drag over date cells, with a possible delay at the beginning of the drag.
*/
class DateSelecting extends internal_common_Interaction {
constructor(settings) {
super(settings);
this.dragSelection = null;
this.handlePointerDown = (ev) => {
let { component, dragging } = this;
let { options } = component.context;
let canSelect = options.selectable &&
component.isValidDateDownEl(ev.origEvent.target);
// don't bother to watch expensive moves if component won't do selection
dragging.setIgnoreMove(!canSelect);
// if touch, require user to hold down
dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null;
};
this.handleDragStart = (ev) => {
this.component.context.calendarApi.unselect(ev); // unselect previous selections
};
this.handleHitUpdate = (hit, isFinal) => {
let { context } = this.component;
let dragSelection = null;
let isInvalid = false;
if (hit) {
let initialHit = this.hitDragging.initialHit;
let disallowed = hit.componentId === initialHit.componentId
&& this.isHitComboAllowed
&& !this.isHitComboAllowed(initialHit, hit);
if (!disallowed) {
dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers);
}
if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
isInvalid = true;
dragSelection = null;
}
}
if (dragSelection) {
context.dispatch({ type: 'SELECT_DATES', selection: dragSelection });
}
else if (!isFinal) { // only unselect if moved away while dragging
context.dispatch({ type: 'UNSELECT_DATES' });
}
if (!isInvalid) {
enableCursor();
}
else {
disableCursor();
}
if (!isFinal) {
this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging
}
};
this.handlePointerUp = (pev) => {
if (this.dragSelection) {
// selection is already rendered, so just need to report selection
triggerDateSelect(this.dragSelection, pev, this.component.context);
this.dragSelection = null;
}
};
let { component } = settings;
let { options } = component.context;
let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
dragging.touchScrollAllowed = false;
dragging.minDistance = options.selectMinDistance || 0;
dragging.autoScroller.isEnabled = options.dragScroll;
let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
hitDragging.emitter.on('pointerdown', this.handlePointerDown);
hitDragging.emitter.on('dragstart', this.handleDragStart);
hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
hitDragging.emitter.on('pointerup', this.handlePointerUp);
}
destroy() {
this.dragging.destroy();
}
}
function getComponentTouchDelay$1(component) {
let { options } = component.context;
let delay = options.selectLongPressDelay;
if (delay == null) {
delay = options.longPressDelay;
}
return delay;
}
function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) {
let dateSpan0 = hit0.dateSpan;
let dateSpan1 = hit1.dateSpan;
let ms = [
dateSpan0.range.start,
dateSpan0.range.end,
dateSpan1.range.start,
dateSpan1.range.end,
];
ms.sort(compareNumbers);
let props = {};
for (let transformer of dateSelectionTransformers) {
let res = transformer(hit0, hit1);
if (res === false) {
return null;
}
if (res) {
Object.assign(props, res);
}
}
props.range = { start: ms[0], end: ms[3] };
props.allDay = dateSpan0.allDay;
return props;
}
class EventDragging extends internal_common_Interaction {
constructor(settings) {
super(settings);
// internal state
this.subjectEl = null;
this.subjectSeg = null; // the seg being selected/dragged
this.isDragging = false;
this.eventRange = null;
this.relevantEvents = null; // the events being dragged
this.receivingContext = null;
this.validMutation = null;
this.mutatedRelevantEvents = null;
this.handlePointerDown = (ev) => {
let origTarget = ev.origEvent.target;
let { component, dragging } = this;
let { mirror } = dragging;
let { options } = component.context;
let initialContext = component.context;
this.subjectEl = ev.subjectEl;
let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl);
let eventRange = this.eventRange = subjectSeg.eventRange;
let eventInstanceId = eventRange.instance.instanceId;
this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId);
dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance;
dragging.delay =
// only do a touch delay if touch and this event hasn't been selected yet
(ev.isTouch && eventInstanceId !== component.props.eventSelection) ?
getComponentTouchDelay(component) :
null;
if (options.fixedMirrorParent) {
mirror.parentNode = options.fixedMirrorParent;
}
else {
mirror.parentNode = elementClosest(origTarget, '.fc');
}
mirror.revertDuration = options.dragRevertDuration;
let isValid = component.isValidSegDownEl(origTarget) &&
!elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer
dragging.setIgnoreMove(!isValid);
// disable dragging for elements that are resizable (ie, selectable)
// but are not draggable
this.isDragging = isValid &&
ev.subjectEl.classList.contains('fc-event-draggable');
};
this.handleDragStart = (ev) => {
let initialContext = this.component.context;
let eventRange = this.eventRange;
let eventInstanceId = eventRange.instance.instanceId;
if (ev.isTouch) {
// need to select a different event?
if (eventInstanceId !== this.component.props.eventSelection) {
initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId });
}
}
else {
// if now using mouse, but was previous touch interaction, clear selected event
initialContext.dispatch({ type: 'UNSELECT_EVENT' });
}
if (this.isDragging) {
initialContext.calendarApi.unselect(ev); // unselect *date* selection
initialContext.emitter.trigger('eventDragStart', {
el: this.subjectEl,
event: new EventImpl(initialContext, eventRange.def, eventRange.instance),
jsEvent: ev.origEvent,
view: initialContext.viewApi,
});
}
};
this.handleHitUpdate = (hit, isFinal) => {
if (!this.isDragging) {
return;
}
let relevantEvents = this.relevantEvents;
let initialHit = this.hitDragging.initialHit;
let initialContext = this.component.context;
// states based on new hit
let receivingContext = null;
let mutation = null;
let mutatedRelevantEvents = null;
let isInvalid = false;
let interaction = {
affectedEvents: relevantEvents,
mutatedEvents: createEmptyEventStore(),
isEvent: true,
};
if (hit) {
receivingContext = hit.context;
let receivingOptions = receivingContext.options;
if (initialContext === receivingContext ||
(receivingOptions.editable && receivingOptions.droppable)) {
mutation = computeEventMutation(initialHit, hit, this.eventRange.instance.range.start, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers);
if (mutation) {
mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext);
interaction.mutatedEvents = mutatedRelevantEvents;
if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
isInvalid = true;
mutation = null;
mutatedRelevantEvents = null;
interaction.mutatedEvents = createEmptyEventStore();
}
}
}
else {
receivingContext = null;
}
}
this.displayDrag(receivingContext, interaction);
if (!isInvalid) {
enableCursor();
}
else {
disableCursor();
}
if (!isFinal) {
if (initialContext === receivingContext && // TODO: write test for this
isHitsEqual(initialHit, hit)) {
mutation = null;
}
this.dragging.setMirrorNeedsRevert(!mutation);
// render the mirror if no already-rendered mirror
// TODO: wish we could somehow wait for dispatch to guarantee render
this.dragging.setMirrorIsVisible(!hit || !this.subjectEl.getRootNode().querySelector('.fc-event-mirror'));
// assign states based on new hit
this.receivingContext = receivingContext;
this.validMutation = mutation;
this.mutatedRelevantEvents = mutatedRelevantEvents;
}
};
this.handlePointerUp = () => {
if (!this.isDragging) {
this.cleanup(); // because handleDragEnd won't fire
}
};
this.handleDragEnd = (ev) => {
if (this.isDragging) {
let initialContext = this.component.context;
let initialView = initialContext.viewApi;
let { receivingContext, validMutation } = this;
let eventDef = this.eventRange.def;
let eventInstance = this.eventRange.instance;
let eventApi = new EventImpl(initialContext, eventDef, eventInstance);
let relevantEvents = this.relevantEvents;
let mutatedRelevantEvents = this.mutatedRelevantEvents;
let { finalHit } = this.hitDragging;
this.clearDrag(); // must happen after revert animation
initialContext.emitter.trigger('eventDragStop', {
el: this.subjectEl,
event: eventApi,
jsEvent: ev.origEvent,
view: initialView,
});
if (validMutation) {
// dropped within same calendar
if (receivingContext === initialContext) {
let updatedEventApi = new EventImpl(initialContext, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
initialContext.dispatch({
type: 'MERGE_EVENTS',
eventStore: mutatedRelevantEvents,
});
let eventChangeArg = {
oldEvent: eventApi,
event: updatedEventApi,
relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance),
revert() {
initialContext.dispatch({
type: 'MERGE_EVENTS',
eventStore: relevantEvents, // the pre-change data
});
},
};
let transformed = {};
for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) {
Object.assign(transformed, transformer(validMutation, initialContext));
}
initialContext.emitter.trigger('eventDrop', Object.assign(Object.assign(Object.assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView }));
initialContext.emitter.trigger('eventChange', eventChangeArg);
// dropped in different calendar
}
else if (receivingContext) {
let eventRemoveArg = {
event: eventApi,
relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance),
revert() {
initialContext.dispatch({
type: 'MERGE_EVENTS',
eventStore: relevantEvents,
});
},
};
initialContext.emitter.trigger('eventLeave', Object.assign(Object.assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView }));
initialContext.dispatch({
type: 'REMOVE_EVENTS',
eventStore: relevantEvents,
});
initialContext.emitter.trigger('eventRemove', eventRemoveArg);
let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId];
let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId];
let addedEventApi = new EventImpl(receivingContext, addedEventDef, addedEventInstance);
receivingContext.dispatch({
type: 'MERGE_EVENTS',
eventStore: mutatedRelevantEvents,
});
let eventAddArg = {
event: addedEventApi,
relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance),
revert() {
receivingContext.dispatch({
type: 'REMOVE_EVENTS',
eventStore: mutatedRelevantEvents,
});
},
};
receivingContext.emitter.trigger('eventAdd', eventAddArg);
if (ev.isTouch) {
receivingContext.dispatch({
type: 'SELECT_EVENT',
eventInstanceId: eventInstance.instanceId,
});
}
receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi }));
receivingContext.emitter.trigger('eventReceive', Object.assign(Object.assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi }));
}
}
else {
initialContext.emitter.trigger('_noEventDrop');
}
}
this.cleanup();
};
let { component } = this;
let { options } = component.context;
let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
dragging.pointer.selector = EventDragging.SELECTOR;
dragging.touchScrollAllowed = false;
dragging.autoScroller.isEnabled = options.dragScroll;
let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore);
hitDragging.useSubjectCenter = settings.useEventCenter;
hitDragging.emitter.on('pointerdown', this.handlePointerDown);
hitDragging.emitter.on('dragstart', this.handleDragStart);
hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
hitDragging.emitter.on('pointerup', this.handlePointerUp);
hitDragging.emitter.on('dragend', this.handleDragEnd);
}
destroy() {
this.dragging.destroy();
}
// render a drag state on the next receivingCalendar
displayDrag(nextContext, state) {
let initialContext = this.component.context;
let prevContext = this.receivingContext;
// does the previous calendar need to be cleared?
if (prevContext && prevContext !== nextContext) {
// does the initial calendar need to be cleared?
// if so, don't clear all the way. we still need to to hide the affectedEvents
if (prevContext === initialContext) {
prevContext.dispatch({
type: 'SET_EVENT_DRAG',
state: {
affectedEvents: state.affectedEvents,
mutatedEvents: createEmptyEventStore(),
isEvent: true,
},
});
// completely clear the old calendar if it wasn't the initial
}
else {
prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
}
}
if (nextContext) {
nextContext.dispatch({ type: 'SET_EVENT_DRAG', state });
}
}
clearDrag() {
let initialCalendar = this.component.context;
let { receivingContext } = this;
if (receivingContext) {
receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
}
// the initial calendar might have an dummy drag state from displayDrag
if (initialCalendar !== receivingContext) {
initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' });
}
}
cleanup() {
this.subjectSeg = null;
this.isDragging = false;
this.eventRange = null;
this.relevantEvents = null;
this.receivingContext = null;
this.validMutation = null;
this.mutatedRelevantEvents = null;
}
}
// TODO: test this in IE11
// QUESTION: why do we need it on the resizable???
EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable';
function computeEventMutation(hit0, hit1, eventInstanceStart, massagers) {
let dateSpan0 = hit0.dateSpan;
let dateSpan1 = hit1.dateSpan;
let date0 = dateSpan0.range.start;
let date1 = dateSpan1.range.start;
let standardProps = {};
if (dateSpan0.allDay !== dateSpan1.allDay) {
standardProps.allDay = dateSpan1.allDay;
standardProps.hasEnd = hit1.context.options.allDayMaintainDuration;
if (dateSpan1.allDay) {
// means date1 is already start-of-day,
// but date0 needs to be converted
date0 = startOfDay(eventInstanceStart);
}
else {
// Moving from allDate->timed
// Doesn't matter where on the event the drag began, mutate the event's start-date to date1
date0 = eventInstanceStart;
}
}
let delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ?
hit0.largeUnit :
null);
if (delta.milliseconds) { // has hours/minutes/seconds
standardProps.allDay = false;
}
let mutation = {
datesDelta: delta,
standardProps,
};
for (let massager of massagers) {
massager(mutation, hit0, hit1);
}
return mutation;
}
function getComponentTouchDelay(component) {
let { options } = component.context;
let delay = options.eventLongPressDelay;
if (delay == null) {
delay = options.longPressDelay;
}
return delay;
}
class EventResizing extends internal_common_Interaction {
constructor(settings) {
super(settings);
// internal state
this.draggingSegEl = null;
this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg?
this.eventRange = null;
this.relevantEvents = null;
this.validMutation = null;
this.mutatedRelevantEvents = null;
this.handlePointerDown = (ev) => {
let { component } = this;
let segEl = this.querySegEl(ev);
let seg = getElSeg(segEl);
let eventRange = this.eventRange = seg.eventRange;
this.dragging.minDistance = component.context.options.eventDragMinDistance;
// if touch, need to be working with a selected event
this.dragging.setIgnoreMove(!this.component.isValidSegDownEl(ev.origEvent.target) ||
(ev.isTouch && this.component.props.eventSelection !== eventRange.instance.instanceId));
};
this.handleDragStart = (ev) => {
let { context } = this.component;
let eventRange = this.eventRange;
this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, this.eventRange.instance.instanceId);
let segEl = this.querySegEl(ev);
this.draggingSegEl = segEl;
this.draggingSeg = getElSeg(segEl);
context.calendarApi.unselect();
context.emitter.trigger('eventResizeStart', {
el: segEl,
event: new EventImpl(context, eventRange.def, eventRange.instance),
jsEvent: ev.origEvent,
view: context.viewApi,
});
};
this.handleHitUpdate = (hit, isFinal, ev) => {
let { context } = this.component;
let relevantEvents = this.relevantEvents;
let initialHit = this.hitDragging.initialHit;
let eventInstance = this.eventRange.instance;
let mutation = null;
let mutatedRelevantEvents = null;
let isInvalid = false;
let interaction = {
affectedEvents: relevantEvents,
mutatedEvents: createEmptyEventStore(),
isEvent: true,
};
if (hit) {
let disallowed = hit.componentId === initialHit.componentId
&& this.isHitComboAllowed
&& !this.isHitComboAllowed(initialHit, hit);
if (!disallowed) {
mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range);
}
}
if (mutation) {
mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context);
interaction.mutatedEvents = mutatedRelevantEvents;
if (!isInteractionValid(interaction, hit.dateProfile, context)) {
isInvalid = true;
mutation = null;
mutatedRelevantEvents = null;
interaction.mutatedEvents = null;
}
}
if (mutatedRelevantEvents) {
context.dispatch({
type: 'SET_EVENT_RESIZE',
state: interaction,
});
}
else {
context.dispatch({ type: 'UNSET_EVENT_RESIZE' });
}
if (!isInvalid) {
enableCursor();
}
else {
disableCursor();
}
if (!isFinal) {
if (mutation && isHitsEqual(initialHit, hit)) {
mutation = null;
}
this.validMutation = mutation;
this.mutatedRelevantEvents = mutatedRelevantEvents;
}
};
this.handleDragEnd = (ev) => {
let { context } = this.component;
let eventDef = this.eventRange.def;
let eventInstance = this.eventRange.instance;
let eventApi = new EventImpl(context, eventDef, eventInstance);
let relevantEvents = this.relevantEvents;
let mutatedRelevantEvents = this.mutatedRelevantEvents;
context.emitter.trigger('eventResizeStop', {
el: this.draggingSegEl,
event: eventApi,
jsEvent: ev.origEvent,
view: context.viewApi,
});
if (this.validMutation) {
let updatedEventApi = new EventImpl(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
context.dispatch({
type: 'MERGE_EVENTS',
eventStore: mutatedRelevantEvents,
});
let eventChangeArg = {
oldEvent: eventApi,
event: updatedEventApi,
relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance),
revert() {
context.dispatch({
type: 'MERGE_EVENTS',
eventStore: relevantEvents, // the pre-change events
});
},
};
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 }));
context.emitter.trigger('eventChange', eventChangeArg);
}
else {
context.emitter.trigger('_noEventResize');
}
// reset all internal state
this.draggingSeg = null;
this.relevantEvents = null;
this.validMutation = null;
// okay to keep eventInstance around. useful to set it in handlePointerDown
};
let { component } = settings;
let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
dragging.pointer.selector = '.fc-event-resizer';
dragging.touchScrollAllowed = false;
dragging.autoScroller.isEnabled = component.context.options.dragScroll;
let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
hitDragging.emitter.on('pointerdown', this.handlePointerDown);
hitDragging.emitter.on('dragstart', this.handleDragStart);
hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
hitDragging.emitter.on('dragend', this.handleDragEnd);
}
destroy() {
this.dragging.destroy();
}
querySegEl(ev) {
return elementClosest(ev.subjectEl, '.fc-event');
}
}
function computeMutation(hit0, hit1, isFromStart, instanceRange) {
let dateEnv = hit0.context.dateEnv;
let date0 = hit0.dateSpan.range.start;
let date1 = hit1.dateSpan.range.start;
let delta = diffDates(date0, date1, dateEnv, hit0.largeUnit);
if (isFromStart) {
if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
return { startDelta: delta };
}
}
else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
return { endDelta: delta };
}
return null;
}
class UnselectAuto {
constructor(context) {
this.context = context;
this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system
this.matchesCancel = false;
this.matchesEvent = false;
this.onSelect = (selectInfo) => {
if (selectInfo.jsEvent) {
this.isRecentPointerDateSelect = true;
}
};
this.onDocumentPointerDown = (pev) => {
let unselectCancel = this.context.options.unselectCancel;
let downEl = getEventTargetViaRoot(pev.origEvent);
this.matchesCancel = !!elementClosest(downEl, unselectCancel);
this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event?
};
this.onDocumentPointerUp = (pev) => {
let { context } = this;
let { documentPointer } = this;
let calendarState = context.getCurrentData();
// touch-scrolling should never unfocus any type of selection
if (!documentPointer.wasTouchScroll) {
if (calendarState.dateSelection && // an existing date selection?
!this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
) {
let unselectAuto = context.options.unselectAuto;
if (unselectAuto && (!unselectAuto || !this.matchesCancel)) {
context.calendarApi.unselect(pev);
}
}
if (calendarState.eventSelection && // an existing event selected?
!this.matchesEvent // interaction DIDN'T start on an event
) {
context.dispatch({ type: 'UNSELECT_EVENT' });
}
}
this.isRecentPointerDateSelect = false;
};
let documentPointer = this.documentPointer = new PointerDragging(document);
documentPointer.shouldIgnoreMove = true;
documentPointer.shouldWatchScroll = false;
documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown);
documentPointer.emitter.on('pointerup', this.onDocumentPointerUp);
/*
TODO: better way to know about whether there was a selection with the pointer
*/
context.emitter.on('select', this.onSelect);
}
destroy() {
this.context.emitter.off('select', this.onSelect);
this.documentPointer.destroy();
}
}
const OPTION_REFINERS = {
fixedMirrorParent: identity,
};
const LISTENER_REFINERS = {
dateClick: identity,
eventDragStart: identity,
eventDragStop: identity,
eventDrop: identity,
eventResizeStart: identity,
eventResizeStop: identity,
eventResize: identity,
drop: identity,
eventReceive: identity,
eventLeave: identity,
};
/*
Given an already instantiated draggable object for one-or-more elements,
Interprets any dragging as an attempt to drag an events that lives outside
of a calendar onto a calendar.
*/
class ExternalElementDragging {
constructor(dragging, suppliedDragMeta) {
this.receivingContext = null;
this.droppableEvent = null; // will exist for all drags, even if create:false
this.suppliedDragMeta = null;
this.dragMeta = null;
this.handleDragStart = (ev) => {
this.dragMeta = this.buildDragMeta(ev.subjectEl);
};
this.handleHitUpdate = (hit, isFinal, ev) => {
let { dragging } = this.hitDragging;
let receivingContext = null;
let droppableEvent = null;
let isInvalid = false;
let interaction = {
affectedEvents: interaction_createEmptyEventStore(),
mutatedEvents: interaction_createEmptyEventStore(),
isEvent: this.dragMeta.create,
};
if (hit) {
receivingContext = hit.context;
if (this.canDropElOnCalendar(ev.subjectEl, receivingContext)) {
droppableEvent = computeEventForDateSpan(hit.dateSpan, this.dragMeta, receivingContext);
interaction.mutatedEvents = interaction_eventTupleToStore(droppableEvent);
isInvalid = !interaction_isInteractionValid(interaction, hit.dateProfile, receivingContext);
if (isInvalid) {
interaction.mutatedEvents = interaction_createEmptyEventStore();
droppableEvent = null;
}
}
}
this.displayDrag(receivingContext, interaction);
// show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
// TODO: wish we could somehow wait for dispatch to guarantee render
dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'));
if (!isInvalid) {
interaction_enableCursor();
}
else {
interaction_disableCursor();
}
if (!isFinal) {
dragging.setMirrorNeedsRevert(!droppableEvent);
this.receivingContext = receivingContext;
this.droppableEvent = droppableEvent;
}
};
this.handleDragEnd = (pev) => {
let { receivingContext, droppableEvent } = this;
this.clearDrag();
if (receivingContext && droppableEvent) {
let finalHit = this.hitDragging.finalHit;
let finalView = finalHit.context.viewApi;
let dragMeta = this.dragMeta;
receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView }));
if (dragMeta.create) {
let addingEvents = interaction_eventTupleToStore(droppableEvent);
receivingContext.dispatch({
type: 'MERGE_EVENTS',
eventStore: addingEvents,
});
if (pev.isTouch) {
receivingContext.dispatch({
type: 'SELECT_EVENT',
eventInstanceId: droppableEvent.instance.instanceId,
});
}
// signal that an external event landed
receivingContext.emitter.trigger('eventReceive', {
event: new interaction_EventImpl(receivingContext, droppableEvent.def, droppableEvent.instance),
relatedEvents: [],
revert() {
receivingContext.dispatch({
type: 'REMOVE_EVENTS',
eventStore: addingEvents,
});
},
draggedEl: pev.subjectEl,
view: finalView,
});
}
}
this.receivingContext = null;
this.droppableEvent = null;
};
let hitDragging = this.hitDragging = new HitDragging(dragging, interaction_interactionSettingsStore);
hitDragging.requireInitial = false; // will start outside of a component
hitDragging.emitter.on('dragstart', this.handleDragStart);
hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
hitDragging.emitter.on('dragend', this.handleDragEnd);
this.suppliedDragMeta = suppliedDragMeta;
}
buildDragMeta(subjectEl) {
if (typeof this.suppliedDragMeta === 'object') {
return interaction_parseDragMeta(this.suppliedDragMeta);
}
if (typeof this.suppliedDragMeta === 'function') {
return interaction_parseDragMeta(this.suppliedDragMeta(subjectEl));
}
return getDragMetaFromEl(subjectEl);
}
displayDrag(nextContext, state) {
let prevContext = this.receivingContext;
if (prevContext && prevContext !== nextContext) {
prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
}
if (nextContext) {
nextContext.dispatch({ type: 'SET_EVENT_DRAG', state });
}
}
clearDrag() {
if (this.receivingContext) {
this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
}
}
canDropElOnCalendar(el, receivingContext) {
let dropAccept = receivingContext.options.dropAccept;
if (typeof dropAccept === 'function') {
return dropAccept.call(receivingContext.calendarApi, el);
}
if (typeof dropAccept === 'string' && dropAccept) {
return Boolean(interaction_elementMatches(el, dropAccept));
}
return true;
}
}
// Utils for computing event store from the DragMeta
// ----------------------------------------------------------------------------------------------------
function computeEventForDateSpan(dateSpan, dragMeta, context) {
let defProps = Object.assign({}, dragMeta.leftoverProps);
for (let transform of context.pluginHooks.externalDefTransforms) {
Object.assign(defProps, transform(dateSpan, dragMeta));
}
let { refined, extra } = interaction_refineEventDef(defProps, context);
let def = interaction_parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
context);
let start = dateSpan.range.start;
// only rely on time info if drop zone is all-day,
// otherwise, we already know the time
if (dateSpan.allDay && dragMeta.startTime) {
start = context.dateEnv.add(start, dragMeta.startTime);
}
let end = dragMeta.duration ?
context.dateEnv.add(start, dragMeta.duration) :
interaction_getDefaultEventEnd(dateSpan.allDay, start, context);
let instance = interaction_createEventInstance(def.defId, { start, end });
return { def, instance };
}
// Utils for extracting data from element
// ----------------------------------------------------------------------------------------------------
function getDragMetaFromEl(el) {
let str = getEmbeddedElData(el, 'event');
let obj = str ?
JSON.parse(str) :
{ create: false }; // if no embedded data, assume no event creation
return interaction_parseDragMeta(obj);
}
config.dataAttrPrefix = '';
function getEmbeddedElData(el, name) {
let prefix = interaction_config.dataAttrPrefix;
let prefixedName = (prefix ? prefix + '-' : '') + name;
return el.getAttribute('data-' + prefixedName) || '';
}
/*
Makes an element (that is *external* to any calendar) draggable.
Can pass in data that determines how an event will be created when dropped onto a calendar.
Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system.
*/
class ExternalDraggable {
constructor(el, settings = {}) {
this.handlePointerDown = (ev) => {
let { dragging } = this;
let { minDistance, longPressDelay } = this.settings;
dragging.minDistance =
minDistance != null ?
minDistance :
(ev.isTouch ? 0 : interaction_BASE_OPTION_DEFAULTS.eventDragMinDistance);
dragging.delay =
ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv
(longPressDelay != null ? longPressDelay : interaction_BASE_OPTION_DEFAULTS.longPressDelay) :
0;
};
this.handleDragStart = (ev) => {
if (ev.isTouch &&
this.dragging.delay &&
ev.subjectEl.classList.contains('fc-event')) {
this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected');
}
};
this.settings = settings;
let dragging = this.dragging = new FeaturefulElementDragging(el);
dragging.touchScrollAllowed = false;
if (settings.itemSelector != null) {
dragging.pointer.selector = settings.itemSelector;
}
if (settings.appendTo != null) {
dragging.mirror.parentNode = settings.appendTo; // TODO: write tests
}
dragging.emitter.on('pointerdown', this.handlePointerDown);
dragging.emitter.on('dragstart', this.handleDragStart);
new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
}
destroy() {
this.dragging.destroy();
}
}
/*
Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements.
The third-party system is responsible for drawing the visuals effects of the drag.
This class simply monitors for pointer movements and fires events.
It also has the ability to hide the moving element (the "mirror") during the drag.
*/
class InferredElementDragging extends ElementDragging {
constructor(containerEl) {
super(containerEl);
this.shouldIgnoreMove = false;
this.mirrorSelector = '';
this.currentMirrorEl = null;
this.handlePointerDown = (ev) => {
this.emitter.trigger('pointerdown', ev);
if (!this.shouldIgnoreMove) {
// fire dragstart right away. does not support delay or min-distance
this.emitter.trigger('dragstart', ev);
}
};
this.handlePointerMove = (ev) => {
if (!this.shouldIgnoreMove) {
this.emitter.trigger('dragmove', ev);
}
};
this.handlePointerUp = (ev) => {
this.emitter.trigger('pointerup', ev);
if (!this.shouldIgnoreMove) {
// fire dragend right away. does not support a revert animation
this.emitter.trigger('dragend', ev);
}
};
let pointer = this.pointer = new PointerDragging(containerEl);
pointer.emitter.on('pointerdown', this.handlePointerDown);
pointer.emitter.on('pointermove', this.handlePointerMove);
pointer.emitter.on('pointerup', this.handlePointerUp);
}
destroy() {
this.pointer.destroy();
}
setIgnoreMove(bool) {
this.shouldIgnoreMove = bool;
}
setMirrorIsVisible(bool) {
if (bool) {
// restore a previously hidden element.
// use the reference in case the selector class has already been removed.
if (this.currentMirrorEl) {
this.currentMirrorEl.style.visibility = '';
this.currentMirrorEl = null;
}
}
else {
let mirrorEl = this.mirrorSelector
// TODO: somehow query FullCalendars WITHIN shadow-roots
? document.querySelector(this.mirrorSelector)
: null;
if (mirrorEl) {
this.currentMirrorEl = mirrorEl;
mirrorEl.style.visibility = 'hidden';
}
}
}
}
/*
Bridges third-party drag-n-drop systems with FullCalendar.
Must be instantiated and destroyed by caller.
*/
class ThirdPartyDraggable {
constructor(containerOrSettings, settings) {
let containerEl = document;
if (
// wish we could just test instanceof EventTarget, but doesn't work in IE11
containerOrSettings === document ||
containerOrSettings instanceof Element) {
containerEl = containerOrSettings;
settings = settings || {};
}
else {
settings = (containerOrSettings || {});
}
let dragging = this.dragging = new InferredElementDragging(containerEl);
if (typeof settings.itemSelector === 'string') {
dragging.pointer.selector = settings.itemSelector;
}
else if (containerEl === document) {
dragging.pointer.selector = '[data-event]';
}
if (typeof settings.mirrorSelector === 'string') {
dragging.mirrorSelector = settings.mirrorSelector;
}
let externalDragging = new ExternalElementDragging(dragging, settings.eventData);
// The hit-detection system requires that the dnd-mirror-element be pointer-events:none,
// but this can't be guaranteed for third-party draggables, so disable
externalDragging.hitDragging.disablePointCheck = true;
}
destroy() {
this.dragging.destroy();
}
}
var interaction_index = createPlugin({
name: '@fullcalendar/interaction',
componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing],
calendarInteractions: [UnselectAuto],
elementDraggingImpl: FeaturefulElementDragging,
optionRefiners: OPTION_REFINERS,
listenerRefiners: LISTENER_REFINERS,
});
;// ../../node_modules/@fullcalendar/daygrid/internal.js
/* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells.
----------------------------------------------------------------------------------------------------------------------*/
// It is a manager for a Table subcomponent, which does most of the heavy lifting.
// It is responsible for managing width/height.
class TableView extends DateComponent {
constructor() {
super(...arguments);
this.headerElRef = d();
}
renderSimpleLayout(headerRowContent, bodyContent) {
let { props, context } = this;
let sections = [];
let stickyHeaderDates = getStickyHeaderDates(context.options);
if (headerRowContent) {
sections.push({
type: 'header',
key: 'header',
isSticky: stickyHeaderDates,
chunk: {
elRef: this.headerElRef,
tableClassName: 'fc-col-header',
rowContent: headerRowContent,
},
});
}
sections.push({
type: 'body',
key: 'body',
liquid: true,
chunk: { content: bodyContent },
});
return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec },
y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections })));
}
renderHScrollLayout(headerRowContent, bodyContent, colCnt, dayMinWidth) {
let ScrollGrid = this.context.pluginHooks.scrollGridImpl;
if (!ScrollGrid) {
throw new Error('No ScrollGrid implementation');
}
let { props, context } = this;
let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
let sections = [];
if (headerRowContent) {
sections.push({
type: 'header',
key: 'header',
isSticky: stickyHeaderDates,
chunks: [{
key: 'main',
elRef: this.headerElRef,
tableClassName: 'fc-col-header',
rowContent: headerRowContent,
}],
});
}
sections.push({
type: 'body',
key: 'body',
liquid: true,
chunks: [{
key: 'main',
content: bodyContent,
}],
});
if (stickyFooterScrollbar) {
sections.push({
type: 'footer',
key: 'footer',
isSticky: true,
chunks: [{
key: 'main',
content: renderScrollShim,
}],
});
}
return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec },
y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections })));
}
}
function splitSegsByRow(segs, rowCnt) {
let byRow = [];
for (let i = 0; i < rowCnt; i += 1) {
byRow[i] = [];
}
for (let seg of segs) {
byRow[seg.row].push(seg);
}
return byRow;
}
function splitSegsByFirstCol(segs, colCnt) {
let byCol = [];
for (let i = 0; i < colCnt; i += 1) {
byCol[i] = [];
}
for (let seg of segs) {
byCol[seg.firstCol].push(seg);
}
return byCol;
}
function splitInteractionByRow(ui, rowCnt) {
let byRow = [];
if (!ui) {
for (let i = 0; i < rowCnt; i += 1) {
byRow[i] = null;
}
}
else {
for (let i = 0; i < rowCnt; i += 1) {
byRow[i] = {
affectedInstances: ui.affectedInstances,
isEvent: ui.isEvent,
segs: [],
};
}
for (let seg of ui.segs) {
byRow[seg.row].segs.push(seg);
}
}
return byRow;
}
const DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: true,
meridiem: 'narrow',
});
function hasListItemDisplay(seg) {
let { display } = seg.eventRange.ui;
return display === 'list-item' || (display === 'auto' &&
!seg.eventRange.def.allDay &&
seg.firstCol === seg.lastCol && // can't be multi-day
seg.isStart && // "
seg.isEnd // "
);
}
class TableBlockEvent extends BaseComponent {
render() {
let { props } = this;
return (y(StandardEvent, Object.assign({}, props, { elClasses: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay })));
}
}
class TableListItemEvent extends BaseComponent {
render() {
let { props, context } = this;
let { options } = context;
let { seg } = props;
let timeFormat = options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT;
let timeText = buildSegTimeText(seg, timeFormat, context, true, props.defaultDisplayEventEnd);
return (y(EventContainer, Object.assign({}, props, { elTag: "a", elClasses: ['fc-daygrid-event', 'fc-daygrid-dot-event'], elAttrs: getSegAnchorAttrs(props.seg, context), defaultGenerator: internal_renderInnerContent, timeText: timeText, isResizing: false, isDateSelecting: false })));
}
}
function internal_renderInnerContent(renderProps) {
return (y(_, null,
y("div", { className: "fc-daygrid-event-dot", style: { borderColor: renderProps.borderColor || renderProps.backgroundColor } }),
renderProps.timeText && (y("div", { className: "fc-event-time" }, renderProps.timeText)),
y("div", { className: "fc-event-title" }, renderProps.event.title || y(_, null, "\u00A0"))));
}
class TableCellMoreLink extends BaseComponent {
constructor() {
super(...arguments);
this.compileSegs = memoize(compileSegs);
}
render() {
let { props } = this;
let { allSegs, invisibleSegs } = this.compileSegs(props.singlePlacements);
return (y(MoreLinkContainer, { elClasses: ['fc-daygrid-more-link'], dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: () => {
let isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
(props.eventResize ? props.eventResize.affectedInstances : null) ||
{};
return (y(_, null, allSegs.map((seg) => {
let instanceId = seg.eventRange.instance.instanceId;
return (y("div", { className: "fc-daygrid-event-harness", key: instanceId, style: {
visibility: isForcedInvisible[instanceId] ? 'hidden' : '',
} }, hasListItemDisplay(seg) ? (y(TableListItemEvent, Object.assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (y(TableBlockEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange))))));
})));
} }));
}
}
function compileSegs(singlePlacements) {
let allSegs = [];
let invisibleSegs = [];
for (let placement of singlePlacements) {
allSegs.push(placement.seg);
if (!placement.isVisible) {
invisibleSegs.push(placement.seg);
}
}
return { allSegs, invisibleSegs };
}
const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'narrow' });
class TableCell extends DateComponent {
constructor() {
super(...arguments);
this.rootElRef = d();
this.state = {
dayNumberId: getUniqueDomId(),
};
this.handleRootEl = (el) => {
setRef(this.rootElRef, el);
setRef(this.props.elRef, el);
};
}
render() {
let { context, props, state, rootElRef } = this;
let { options, dateEnv } = context;
let { date, dateProfile } = props;
// TODO: memoize this?
const isMonthStart = props.showDayNumber &&
shouldDisplayMonthStart(date, dateProfile.currentRange, dateEnv);
return (y(DayCellContainer, { elTag: "td", elRef: this.handleRootEl, elClasses: [
'fc-daygrid-day',
...(props.extraClassNames || []),
], elAttrs: Object.assign(Object.assign(Object.assign({}, props.extraDataAttrs), (props.showDayNumber ? { 'aria-labelledby': state.dayNumberId } : {})), { role: 'gridcell' }), defaultGenerator: renderTopInner, date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, isMonthStart: isMonthStart, extraRenderProps: props.extraRenderProps }, (InnerContent, renderProps) => (y("div", { ref: props.innerElRef, className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", style: { minHeight: props.minHeight } },
props.showWeekNumber && (y(WeekNumberContainer, { elTag: "a", elClasses: ['fc-daygrid-week-number'], elAttrs: buildNavLinkAttrs(context, date, 'week'), date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT })),
!renderProps.isDisabled &&
(props.showDayNumber || hasCustomDayCellContent(options) || props.forceDayTop) ? (y("div", { className: "fc-daygrid-day-top" },
y(InnerContent, { elTag: "a", elClasses: [
'fc-daygrid-day-number',
isMonthStart && 'fc-daygrid-month-start',
], elAttrs: Object.assign(Object.assign({}, buildNavLinkAttrs(context, date)), { id: state.dayNumberId }) }))) : props.showDayNumber ? (
// for creating correct amount of space (see issue #7162)
y("div", { className: "fc-daygrid-day-top", style: { visibility: 'hidden' } },
y("a", { className: "fc-daygrid-day-number" }, "\u00A0"))) : undefined,
y("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef },
props.fgContent,
y("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } },
y(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))),
y("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))));
}
}
function renderTopInner(props) {
return props.dayNumberText || y(_, null, "\u00A0");
}
function shouldDisplayMonthStart(date, currentRange, dateEnv) {
const { start: currentStart, end: currentEnd } = currentRange;
const currentEndIncl = addMs(currentEnd, -1);
const currentFirstYear = dateEnv.getYear(currentStart);
const currentFirstMonth = dateEnv.getMonth(currentStart);
const currentLastYear = dateEnv.getYear(currentEndIncl);
const currentLastMonth = dateEnv.getMonth(currentEndIncl);
// spans more than one month?
return !(currentFirstYear === currentLastYear && currentFirstMonth === currentLastMonth) &&
Boolean(
// first date in current view?
date.valueOf() === currentStart.valueOf() ||
// a month-start that's within the current range?
(dateEnv.getDay(date) === 1 && date.valueOf() < currentEnd.valueOf()));
}
function generateSegKey(seg) {
return seg.eventRange.instance.instanceId + ':' + seg.firstCol;
}
function generateSegUid(seg) {
return generateSegKey(seg) + ':' + seg.lastCol;
}
function computeFgSegPlacement(segs, // assumed already sorted
dayMaxEvents, dayMaxEventRows, strictOrder, segHeights, maxContentHeight, cells) {
let hierarchy = new DayGridSegHierarchy((segEntry) => {
// TODO: more DRY with generateSegUid
let segUid = segs[segEntry.index].eventRange.instance.instanceId +
':' + segEntry.span.start +
':' + (segEntry.span.end - 1);
// if no thickness known, assume 1 (if 0, so small it always fits)
return segHeights[segUid] || 1;
});
hierarchy.allowReslicing = true;
hierarchy.strictOrder = strictOrder;
if (dayMaxEvents === true || dayMaxEventRows === true) {
hierarchy.maxCoord = maxContentHeight;
hierarchy.hiddenConsumes = true;
}
else if (typeof dayMaxEvents === 'number') {
hierarchy.maxStackCnt = dayMaxEvents;
}
else if (typeof dayMaxEventRows === 'number') {
hierarchy.maxStackCnt = dayMaxEventRows;
hierarchy.hiddenConsumes = true;
}
// create segInputs only for segs with known heights
let segInputs = [];
let unknownHeightSegs = [];
for (let i = 0; i < segs.length; i += 1) {
let seg = segs[i];
let segUid = generateSegUid(seg);
let eventHeight = segHeights[segUid];
if (eventHeight != null) {
segInputs.push({
index: i,
span: {
start: seg.firstCol,
end: seg.lastCol + 1,
},
});
}
else {
unknownHeightSegs.push(seg);
}
}
let hiddenEntries = hierarchy.addSegs(segInputs);
let segRects = hierarchy.toRects();
let { singleColPlacements, multiColPlacements, leftoverMargins } = placeRects(segRects, segs, cells);
let moreCnts = [];
let moreMarginTops = [];
// add segs with unknown heights
for (let seg of unknownHeightSegs) {
multiColPlacements[seg.firstCol].push({
seg,
isVisible: false,
isAbsolute: true,
absoluteTop: 0,
marginTop: 0,
});
for (let col = seg.firstCol; col <= seg.lastCol; col += 1) {
singleColPlacements[col].push({
seg: resliceSeg(seg, col, col + 1, cells),
isVisible: false,
isAbsolute: false,
absoluteTop: 0,
marginTop: 0,
});
}
}
// add the hidden entries
for (let col = 0; col < cells.length; col += 1) {
moreCnts.push(0);
}
for (let hiddenEntry of hiddenEntries) {
let seg = segs[hiddenEntry.index];
let hiddenSpan = hiddenEntry.span;
multiColPlacements[hiddenSpan.start].push({
seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells),
isVisible: false,
isAbsolute: true,
absoluteTop: 0,
marginTop: 0,
});
for (let col = hiddenSpan.start; col < hiddenSpan.end; col += 1) {
moreCnts[col] += 1;
singleColPlacements[col].push({
seg: resliceSeg(seg, col, col + 1, cells),
isVisible: false,
isAbsolute: false,
absoluteTop: 0,
marginTop: 0,
});
}
}
// deal with leftover margins
for (let col = 0; col < cells.length; col += 1) {
moreMarginTops.push(leftoverMargins[col]);
}
return { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops };
}
// rects ordered by top coord, then left
function placeRects(allRects, segs, cells) {
let rectsByEachCol = groupRectsByEachCol(allRects, cells.length);
let singleColPlacements = [];
let multiColPlacements = [];
let leftoverMargins = [];
for (let col = 0; col < cells.length; col += 1) {
let rects = rectsByEachCol[col];
// compute all static segs in singlePlacements
let singlePlacements = [];
let currentHeight = 0;
let currentMarginTop = 0;
for (let rect of rects) {
let seg = segs[rect.index];
singlePlacements.push({
seg: resliceSeg(seg, col, col + 1, cells),
isVisible: true,
isAbsolute: false,
absoluteTop: rect.levelCoord,
marginTop: rect.levelCoord - currentHeight,
});
currentHeight = rect.levelCoord + rect.thickness;
}
// compute mixed static/absolute segs in multiPlacements
let multiPlacements = [];
currentHeight = 0;
currentMarginTop = 0;
for (let rect of rects) {
let seg = segs[rect.index];
let isAbsolute = rect.span.end - rect.span.start > 1; // multi-column?
let isFirstCol = rect.span.start === col;
currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg
currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg
if (isAbsolute) {
currentMarginTop += rect.thickness;
if (isFirstCol) {
multiPlacements.push({
seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
isVisible: true,
isAbsolute: true,
absoluteTop: rect.levelCoord,
marginTop: 0,
});
}
}
else if (isFirstCol) {
multiPlacements.push({
seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
isVisible: true,
isAbsolute: false,
absoluteTop: rect.levelCoord,
marginTop: currentMarginTop, // claim the margin
});
currentMarginTop = 0;
}
}
singleColPlacements.push(singlePlacements);
multiColPlacements.push(multiPlacements);
leftoverMargins.push(currentMarginTop);
}
return { singleColPlacements, multiColPlacements, leftoverMargins };
}
function groupRectsByEachCol(rects, colCnt) {
let rectsByEachCol = [];
for (let col = 0; col < colCnt; col += 1) {
rectsByEachCol.push([]);
}
for (let rect of rects) {
for (let col = rect.span.start; col < rect.span.end; col += 1) {
rectsByEachCol[col].push(rect);
}
}
return rectsByEachCol;
}
function resliceSeg(seg, spanStart, spanEnd, cells) {
if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
return seg;
}
let eventRange = seg.eventRange;
let origRange = eventRange.range;
let slicedRange = intersectRanges(origRange, {
start: cells[spanStart].date,
end: addDays(cells[spanEnd - 1].date, 1),
});
return Object.assign(Object.assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: {
def: eventRange.def,
ui: Object.assign(Object.assign({}, eventRange.ui), { durationEditable: false }),
instance: eventRange.instance,
range: slicedRange,
}, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() });
}
class DayGridSegHierarchy extends SegHierarchy {
constructor() {
super(...arguments);
// config
this.hiddenConsumes = false;
// allows us to keep hidden entries in the hierarchy so they take up space
this.forceHidden = {};
}
addSegs(segInputs) {
const hiddenSegs = super.addSegs(segInputs);
const { entriesByLevel } = this;
const excludeHidden = (entry) => !this.forceHidden[buildEntryKey(entry)];
// remove the forced-hidden segs
for (let level = 0; level < entriesByLevel.length; level += 1) {
entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden);
}
return hiddenSegs;
}
handleInvalidInsertion(insertion, entry, hiddenEntries) {
const { entriesByLevel, forceHidden } = this;
const { touchingEntry, touchingLevel, touchingLateral } = insertion;
// the entry that the new insertion is touching must be hidden
if (this.hiddenConsumes && touchingEntry) {
const touchingEntryId = buildEntryKey(touchingEntry);
if (!forceHidden[touchingEntryId]) {
if (this.allowReslicing) {
// split up the touchingEntry, reinsert it
const hiddenEntry = Object.assign(Object.assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) });
// reinsert the area that turned into a "more" link (so no other entries try to
// occupy the space) but mark it forced-hidden
const hiddenEntryId = buildEntryKey(hiddenEntry);
forceHidden[hiddenEntryId] = true;
entriesByLevel[touchingLevel][touchingLateral] = hiddenEntry;
hiddenEntries.push(hiddenEntry);
this.splitEntry(touchingEntry, entry, hiddenEntries);
}
else {
forceHidden[touchingEntryId] = true;
hiddenEntries.push(touchingEntry);
}
}
}
// will try to reslice...
super.handleInvalidInsertion(insertion, entry, hiddenEntries);
}
}
class TableRow extends DateComponent {
constructor() {
super(...arguments);
this.cellElRefs = new RefMap(); // the <td>
this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame
this.fgElRefs = new RefMap(); // the fc-daygrid-day-events
this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol"
this.rootElRef = d();
this.state = {
framePositions: null,
maxContentHeight: null,
segHeights: {},
};
this.handleResize = (isForced) => {
if (isForced) {
this.updateSizing(true); // isExternal=true
}
};
}
render() {
let { props, state, context } = this;
let { options } = context;
let colCnt = props.cells.length;
let businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt);
let bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt);
let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt);
let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt);
let { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops } = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.segHeights, state.maxContentHeight, props.cells);
let isForcedInvisible = // TODO: messy way to compute this
(props.eventDrag && props.eventDrag.affectedInstances) ||
(props.eventResize && props.eventResize.affectedInstances) ||
{};
return (y("tr", { ref: this.rootElRef, role: "row" },
props.renderIntro && props.renderIntro(),
props.cells.map((cell, col) => {
let normalFgNodes = this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible);
let mirrorFgNodes = this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false);
return (y(TableCell, { key: cell.key, elRef: this.cellElRefs.createRef(cell.key), innerElRef: this.frameElRefs.createRef(cell.key) /* FF <td> problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraRenderProps: cell.extraRenderProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys
y(_, null,
y(_, null, normalFgNodes),
y(_, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys
y(_, null,
this.renderFillSegs(highlightSegsByCol[col], 'highlight'),
this.renderFillSegs(businessHoursByCol[col], 'non-business'),
this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))), minHeight: props.cellMinHeight }));
})));
}
componentDidMount() {
this.updateSizing(true);
this.context.addResizeHandler(this.handleResize);
}
componentDidUpdate(prevProps, prevState) {
let currentProps = this.props;
this.updateSizing(!isPropsEqual(prevProps, currentProps));
}
componentWillUnmount() {
this.context.removeResizeHandler(this.handleResize);
}
getHighlightSegs() {
let { props } = this;
if (props.eventDrag && props.eventDrag.segs.length) { // messy check
return props.eventDrag.segs;
}
if (props.eventResize && props.eventResize.segs.length) { // messy check
return props.eventResize.segs;
}
return props.dateSelectionSegs;
}
getMirrorSegs() {
let { props } = this;
if (props.eventResize && props.eventResize.segs.length) { // messy check
return props.eventResize.segs;
}
return [];
}
renderFgSegs(col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
let { context } = this;
let { eventSelection } = this.props;
let { framePositions } = this.state;
let defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1
let isMirror = isDragging || isResizing || isDateSelecting;
let nodes = [];
if (framePositions) {
for (let placement of segPlacements) {
let { seg } = placement;
let { instanceId } = seg.eventRange.instance;
let isVisible = placement.isVisible && !isForcedInvisible[instanceId];
let isAbsolute = placement.isAbsolute;
let left = '';
let right = '';
if (isAbsolute) {
if (context.isRtl) {
right = 0;
left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol];
}
else {
left = 0;
right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol];
}
}
/*
known bug: events that are force to be list-item but span multiple days still take up space in later columns
todo: in print view, for multi-day events, don't display title within non-start/end segs
*/
nodes.push(y("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: generateSegKey(seg), ref: isMirror ? null : this.segHarnessRefs.createRef(generateSegUid(seg)), style: {
visibility: isVisible ? '' : 'hidden',
marginTop: isAbsolute ? '' : placement.marginTop,
top: isAbsolute ? placement.absoluteTop : '',
left,
right,
} }, hasListItemDisplay(seg) ? (y(TableListItemEvent, Object.assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (y(TableBlockEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange))))));
}
}
return nodes;
}
renderFillSegs(segs, fillType) {
let { isRtl } = this.context;
let { todayRange } = this.props;
let { framePositions } = this.state;
let nodes = [];
if (framePositions) {
for (let seg of segs) {
let leftRightCss = isRtl ? {
right: 0,
left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol],
} : {
left: 0,
right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol],
};
nodes.push(y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ?
y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, todayRange))) :
renderFill(fillType)));
}
}
return y(_, {}, ...nodes);
}
updateSizing(isExternalSizingChange) {
let { props, state, frameElRefs } = this;
if (!props.forPrint &&
props.clientWidth !== null // positioning ready?
) {
if (isExternalSizingChange) {
let frameEls = props.cells.map((cell) => frameElRefs.currentMap[cell.key]);
if (frameEls.length) {
let originEl = this.rootElRef.current;
let newPositionCache = new PositionCache(originEl, frameEls, true, // isHorizontal
false);
if (!state.framePositions || !state.framePositions.similarTo(newPositionCache)) {
this.setState({
framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal
false),
});
}
}
}
const oldSegHeights = this.state.segHeights;
const newSegHeights = this.querySegHeights();
const limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true;
this.safeSetState({
// HACK to prevent oscillations of events being shown/hidden from max-event-rows
// Essentially, once you compute an element's height, never null-out.
// TODO: always display all events, as visibility:hidden?
segHeights: Object.assign(Object.assign({}, oldSegHeights), newSegHeights),
maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null,
});
}
}
querySegHeights() {
let segElMap = this.segHarnessRefs.currentMap;
let segHeights = {};
// get the max height amongst instance segs
for (let segUid in segElMap) {
let height = Math.round(segElMap[segUid].getBoundingClientRect().height);
segHeights[segUid] = Math.max(segHeights[segUid] || 0, height);
}
return segHeights;
}
computeMaxContentHeight() {
let firstKey = this.props.cells[0].key;
let cellEl = this.cellElRefs.currentMap[firstKey];
let fcContainerEl = this.fgElRefs.currentMap[firstKey];
return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top;
}
getCellEls() {
let elMap = this.cellElRefs.currentMap;
return this.props.cells.map((cell) => elMap[cell.key]);
}
}
TableRow.addStateEquality({
segHeights: isPropsEqual,
});
function buildMirrorPlacements(mirrorSegs, colPlacements) {
if (!mirrorSegs.length) {
return [];
}
let topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render?
return mirrorSegs.map((seg) => ({
seg,
isVisible: true,
isAbsolute: true,
absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId],
marginTop: 0,
}));
}
function buildAbsoluteTopHash(colPlacements) {
let topsByInstanceId = {};
for (let placements of colPlacements) {
for (let placement of placements) {
topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop;
}
}
return topsByInstanceId;
}
class TableRows extends DateComponent {
constructor() {
super(...arguments);
this.splitBusinessHourSegs = memoize(splitSegsByRow);
this.splitBgEventSegs = memoize(splitAllDaySegsByRow);
this.splitFgEventSegs = memoize(splitSegsByRow);
this.splitDateSelectionSegs = memoize(splitSegsByRow);
this.splitEventDrag = memoize(splitInteractionByRow);
this.splitEventResize = memoize(splitInteractionByRow);
this.rowRefs = new RefMap();
}
render() {
let { props, context } = this;
let rowCnt = props.cells.length;
let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt);
let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt);
let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt);
let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt);
let eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt);
let eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt);
// for DayGrid view with many rows, force a min-height on cells so doesn't appear squished
// choose 7 because a month view will have max 6 rows
let cellMinHeight = (rowCnt >= 7 && props.clientWidth) ?
props.clientWidth / context.options.aspectRatio / 6 :
null;
return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y(_, null, props.cells.map((cells, row) => (y(TableRow, { ref: this.rowRefs.createRef(row), key: cells.length
? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */
: row // in case there are no cells (like when resource view is loading)
, showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: props.dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row], fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, cellMinHeight: cellMinHeight, forPrint: props.forPrint })))))));
}
componentDidMount() {
this.registerInteractiveComponent();
}
componentDidUpdate() {
// for if started with zero cells
this.registerInteractiveComponent();
}
registerInteractiveComponent() {
if (!this.rootEl) {
// HACK: need a daygrid wrapper parent to do positioning
// NOTE: a daygrid resource view w/o resources can have zero cells
const firstCellEl = this.rowRefs.currentMap[0].getCellEls()[0];
const rootEl = firstCellEl ? firstCellEl.closest('.fc-daygrid-body') : null;
if (rootEl) {
this.rootEl = rootEl;
this.context.registerInteractiveComponent(this, {
el: rootEl,
isHitComboAllowed: this.props.isHitComboAllowed,
});
}
}
}
componentWillUnmount() {
if (this.rootEl) {
this.context.unregisterInteractiveComponent(this);
this.rootEl = null;
}
}
// Hit System
// ----------------------------------------------------------------------------------------------------
prepareHits() {
this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map((rowObj) => rowObj.getCellEls()[0]), // first cell el in each row. TODO: not optimal
false, true);
this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row
true, // horizontal
false);
}
queryHit(positionLeft, positionTop) {
let { colPositions, rowPositions } = this;
let col = colPositions.leftToIndex(positionLeft);
let row = rowPositions.topToIndex(positionTop);
if (row != null && col != null) {
let cell = this.props.cells[row][col];
return {
dateProfile: this.props.dateProfile,
dateSpan: Object.assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan),
dayEl: this.getCellEl(row, col),
rect: {
left: colPositions.lefts[col],
right: colPositions.rights[col],
top: rowPositions.tops[row],
bottom: rowPositions.bottoms[row],
},
layer: 0,
};
}
return null;
}
getCellEl(row, col) {
return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal
}
getCellRange(row, col) {
let start = this.props.cells[row][col].date;
let end = addDays(start, 1);
return { start, end };
}
}
function splitAllDaySegsByRow(segs, rowCnt) {
return splitSegsByRow(segs.filter(isSegAllDay), rowCnt);
}
function isSegAllDay(seg) {
return seg.eventRange.def.allDay;
}
class Table extends DateComponent {
constructor() {
super(...arguments);
this.elRef = d();
this.needsScrollReset = false;
}
render() {
let { props } = this;
let { dayMaxEventRows, dayMaxEvents, expandRows } = props;
let limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true;
// if rows can't expand to fill fixed height, can't do balanced-height event limit
// TODO: best place to normalize these options?
if (limitViaBalanced && !expandRows) {
limitViaBalanced = false;
dayMaxEventRows = null;
dayMaxEvents = null;
}
let classNames = [
'fc-daygrid-body',
limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced',
expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others?
];
return (y("div", { ref: this.elRef, className: classNames.join(' '), style: {
// these props are important to give this wrapper correct dimensions for interactions
// TODO: if we set it here, can we avoid giving to inner tables?
width: props.clientWidth,
minWidth: props.tableMinWidth,
} },
y("table", { role: "presentation", className: "fc-scrollgrid-sync-table", style: {
width: props.clientWidth,
minWidth: props.tableMinWidth,
height: expandRows ? props.clientHeight : '',
} },
props.colGroupNode,
y("tbody", { role: "presentation" },
y(TableRows, { dateProfile: props.dateProfile, cells: props.cells, renderRowIntro: props.renderRowIntro, showWeekNumbers: props.showWeekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed })))));
}
componentDidMount() {
this.requestScrollReset();
}
componentDidUpdate(prevProps) {
if (prevProps.dateProfile !== this.props.dateProfile) {
this.requestScrollReset();
}
else {
this.flushScrollReset();
}
}
requestScrollReset() {
this.needsScrollReset = true;
this.flushScrollReset();
}
flushScrollReset() {
if (this.needsScrollReset &&
this.props.clientWidth // sizes computed?
) {
const subjectEl = getScrollSubjectEl(this.elRef.current, this.props.dateProfile);
if (subjectEl) {
const originEl = subjectEl.closest('.fc-daygrid-body');
const scrollEl = originEl.closest('.fc-scroller');
const scrollTop = subjectEl.getBoundingClientRect().top -
originEl.getBoundingClientRect().top;
scrollEl.scrollTop = scrollTop ? (scrollTop + 1) : 0; // overcome border
}
this.needsScrollReset = false;
}
}
}
function getScrollSubjectEl(containerEl, dateProfile) {
let el;
if (dateProfile.currentRangeUnit.match(/year|month/)) {
el = containerEl.querySelector(`[data-date="${formatIsoMonthStr(dateProfile.currentDate)}-01"]`);
// even if view is month-based, first-of-month might be hidden...
}
if (!el) {
el = containerEl.querySelector(`[data-date="${formatDayString(dateProfile.currentDate)}"]`);
// could still be hidden if an interior-view hidden day
}
return el;
}
class DayTableSlicer extends Slicer {
constructor() {
super(...arguments);
this.forceDayIfListItem = true;
}
sliceRange(dateRange, dayTableModel) {
return dayTableModel.sliceRange(dateRange);
}
}
class DayTable extends DateComponent {
constructor() {
super(...arguments);
this.slicer = new DayTableSlicer();
this.tableRef = d();
}
render() {
let { props, context } = this;
return (y(Table, Object.assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })));
}
}
class DayTableView extends TableView {
constructor() {
super(...arguments);
this.buildDayTableModel = memoize(buildDayTableModel);
this.headerRef = d();
this.tableRef = d();
// can't override any lifecycle methods from parent
}
render() {
let { options, dateProfileGenerator } = this.context;
let { props } = this;
let dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator);
let headerContent = options.dayHeaders && (y(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 }));
let bodyContent = (contentArg) => (y(DayTable, { ref: this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }));
return options.dayMinWidth
? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth)
: this.renderSimpleLayout(headerContent, bodyContent);
}
}
function buildDayTableModel(dateProfile, dateProfileGenerator) {
let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit));
}
class TableDateProfileGenerator extends DateProfileGenerator {
// Computes the date range that will be rendered
buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
let renderRange = super.buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay);
let { props } = this;
return buildDayTableRenderRange({
currentRange: renderRange,
snapToWeek: /^(year|month)$/.test(currentRangeUnit),
fixedWeekCount: props.fixedWeekCount,
dateEnv: props.dateEnv,
});
}
}
function buildDayTableRenderRange(props) {
let { dateEnv, currentRange } = props;
let { start, end } = currentRange;
let endOfWeek;
// year and month views should be aligned with weeks. this is already done for week
if (props.snapToWeek) {
start = dateEnv.startOfWeek(start);
// make end-of-week if not already
endOfWeek = dateEnv.startOfWeek(end);
if (endOfWeek.valueOf() !== end.valueOf()) {
end = addWeeks(endOfWeek, 1);
}
}
// ensure 6 weeks
if (props.fixedWeekCount) {
// TODO: instead of these date-math gymnastics (for multimonth view),
// compute dateprofiles of all months, then use start of first and end of last.
let lastMonthRenderStart = dateEnv.startOfWeek(dateEnv.startOfMonth(addDays(currentRange.end, -1)));
let rowCnt = Math.ceil(// could be partial weeks due to hiddenDays
diffWeeks(lastMonthRenderStart, end));
end = addWeeks(end, 6 - rowCnt);
}
return { start, end };
}
var internal_css_248z = ":root{--fc-daygrid-event-dot-width:8px}.fc-daygrid-day-events:after,.fc-daygrid-day-events:before,.fc-daygrid-day-frame:after,.fc-daygrid-day-frame:before,.fc-daygrid-event-harness:after,.fc-daygrid-event-harness:before{clear:both;content:\"\";display:table}.fc .fc-daygrid-body{position:relative;z-index:1}.fc .fc-daygrid-day.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-daygrid-day-frame{min-height:100%;position:relative}.fc .fc-daygrid-day-top{display:flex;flex-direction:row-reverse}.fc .fc-day-other .fc-daygrid-day-top{opacity:.3}.fc .fc-daygrid-day-number{padding:4px;position:relative;z-index:4}.fc .fc-daygrid-month-start{font-size:1.1em;font-weight:700}.fc .fc-daygrid-day-events{margin-top:1px}.fc .fc-daygrid-body-balanced .fc-daygrid-day-events{left:0;position:absolute;right:0}.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events{min-height:2em;position:relative}.fc .fc-daygrid-body-natural .fc-daygrid-day-events{margin-bottom:1em}.fc .fc-daygrid-event-harness{position:relative}.fc .fc-daygrid-event-harness-abs{left:0;position:absolute;right:0;top:0}.fc .fc-daygrid-bg-harness{bottom:0;position:absolute;top:0}.fc .fc-daygrid-day-bg .fc-non-business{z-index:1}.fc .fc-daygrid-day-bg .fc-bg-event{z-index:2}.fc .fc-daygrid-day-bg .fc-highlight{z-index:3}.fc .fc-daygrid-event{margin-top:1px;z-index:6}.fc .fc-daygrid-event.fc-event-mirror{z-index:7}.fc .fc-daygrid-day-bottom{font-size:.85em;margin:0 2px}.fc .fc-daygrid-day-bottom:after,.fc .fc-daygrid-day-bottom:before{clear:both;content:\"\";display:table}.fc .fc-daygrid-more-link{border-radius:3px;cursor:pointer;line-height:1;margin-top:1px;max-width:100%;overflow:hidden;padding:2px;position:relative;white-space:nowrap;z-index:4}.fc .fc-daygrid-more-link:hover{background-color:rgba(0,0,0,.1)}.fc .fc-daygrid-week-number{background-color:var(--fc-neutral-bg-color);color:var(--fc-neutral-text-color);min-width:1.5em;padding:2px;position:absolute;text-align:center;top:0;z-index:5}.fc .fc-more-popover .fc-popover-body{min-width:220px;padding:10px}.fc-direction-ltr .fc-daygrid-event.fc-event-start,.fc-direction-rtl .fc-daygrid-event.fc-event-end{margin-left:2px}.fc-direction-ltr .fc-daygrid-event.fc-event-end,.fc-direction-rtl .fc-daygrid-event.fc-event-start{margin-right:2px}.fc-direction-ltr .fc-daygrid-more-link{float:left}.fc-direction-ltr .fc-daygrid-week-number{border-radius:0 0 3px 0;left:0}.fc-direction-rtl .fc-daygrid-more-link{float:right}.fc-direction-rtl .fc-daygrid-week-number{border-radius:0 0 0 3px;right:0}.fc-liquid-hack .fc-daygrid-day-frame{position:static}.fc-daygrid-event{border-radius:3px;font-size:var(--fc-small-font-size);position:relative;white-space:nowrap}.fc-daygrid-block-event .fc-event-time{font-weight:700}.fc-daygrid-block-event .fc-event-time,.fc-daygrid-block-event .fc-event-title{padding:1px}.fc-daygrid-dot-event{align-items:center;display:flex;padding:2px 0}.fc-daygrid-dot-event .fc-event-title{flex-grow:1;flex-shrink:1;font-weight:700;min-width:0;overflow:hidden}.fc-daygrid-dot-event.fc-event-mirror,.fc-daygrid-dot-event:hover{background:rgba(0,0,0,.1)}.fc-daygrid-dot-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-daygrid-event-dot{border:calc(var(--fc-daygrid-event-dot-width)/2) solid var(--fc-event-border-color);border-radius:calc(var(--fc-daygrid-event-dot-width)/2);box-sizing:content-box;height:0;margin:0 4px;width:0}.fc-direction-ltr .fc-daygrid-event .fc-event-time{margin-right:3px}.fc-direction-rtl .fc-daygrid-event .fc-event-time{margin-left:3px}";
injectStyles(internal_css_248z);
;// ../../node_modules/@fullcalendar/daygrid/index.js
var daygrid_index = createPlugin({
name: '@fullcalendar/daygrid',
initialView: 'dayGridMonth',
views: {
dayGrid: {
component: DayTableView,
dateProfileGeneratorClass: TableDateProfileGenerator,
},
dayGridDay: {
type: 'dayGrid',
duration: { days: 1 },
},
dayGridWeek: {
type: 'dayGrid',
duration: { weeks: 1 },
},
dayGridMonth: {
type: 'dayGrid',
duration: { months: 1 },
fixedWeekCount: true,
},
dayGridYear: {
type: 'dayGrid',
duration: { years: 1 },
},
},
});
;// ../../node_modules/@fullcalendar/timegrid/internal.js
class AllDaySplitter extends Splitter {
getKeyInfo() {
return {
allDay: {},
timed: {},
};
}
getKeysForDateSpan(dateSpan) {
if (dateSpan.allDay) {
return ['allDay'];
}
return ['timed'];
}
getKeysForEventDef(eventDef) {
if (!eventDef.allDay) {
return ['timed'];
}
if (hasBgRendering(eventDef)) {
return ['timed', 'allDay'];
}
return ['allDay'];
}
}
const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: true,
meridiem: 'short',
});
function TimeColsAxisCell(props) {
let classNames = [
'fc-timegrid-slot',
'fc-timegrid-slot-label',
props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
];
return (y(ViewContextType.Consumer, null, (context) => {
if (!props.isLabeled) {
return (y("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
}
let { dateEnv, options, viewApi } = context;
let labelFormat = // TODO: fully pre-parse
options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
createFormatter(options.slotLabelFormat);
let renderProps = {
level: 0,
time: props.time,
date: dateEnv.toDate(props.date),
view: viewApi,
text: dateEnv.format(props.date, labelFormat),
};
return (y(ContentContainer, { elTag: "td", elClasses: classNames, elAttrs: {
'data-time': props.isoTimeStr,
}, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: timegrid_internal_renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (y("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
y(InnerContent, { elTag: "div", elClasses: [
'fc-timegrid-slot-label-cushion',
'fc-scrollgrid-shrink-cushion',
] })))));
}));
}
function timegrid_internal_renderInnerContent(props) {
return props.text;
}
class TimeBodyAxis extends BaseComponent {
render() {
return this.props.slatMetas.map((slatMeta) => (y("tr", { key: slatMeta.key },
y(TimeColsAxisCell, Object.assign({}, slatMeta)))));
}
}
const internal_DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
class TimeColsView extends DateComponent {
constructor() {
super(...arguments);
this.allDaySplitter = new AllDaySplitter(); // for use by subclasses
this.headerElRef = d();
this.rootElRef = d();
this.scrollerElRef = d();
this.state = {
slatCoords: null,
};
this.handleScrollTopRequest = (scrollTop) => {
let scrollerEl = this.scrollerElRef.current;
if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer
scrollerEl.scrollTop = scrollTop;
}
};
/* Header Render Methods
------------------------------------------------------------------------------------------------------------------*/
this.renderHeadAxis = (rowKey, frameHeight = '') => {
let { options } = this.context;
let { dateProfile } = this.props;
let range = dateProfile.renderRange;
let dayCnt = diffDays(range.start, range.end);
// only do in day views (to avoid doing in week views that dont need it)
let navLinkAttrs = (dayCnt === 1)
? buildNavLinkAttrs(this.context, range.start, 'week')
: {};
if (options.weekNumbers && rowKey === 'day') {
return (y(WeekNumberContainer, { elTag: "th", elClasses: [
'fc-timegrid-axis',
'fc-scrollgrid-shrink',
], elAttrs: {
'aria-hidden': true,
}, date: range.start, defaultFormat: internal_DEFAULT_WEEK_NUM_FORMAT }, (InnerContent) => (y("div", { className: [
'fc-timegrid-axis-frame',
'fc-scrollgrid-shrink-frame',
'fc-timegrid-axis-frame-liquid',
].join(' '), style: { height: frameHeight } },
y(InnerContent, { elTag: "a", elClasses: [
'fc-timegrid-axis-cushion',
'fc-scrollgrid-shrink-cushion',
'fc-scrollgrid-sync-inner',
], elAttrs: navLinkAttrs })))));
}
return (y("th", { "aria-hidden": true, className: "fc-timegrid-axis" },
y("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
};
/* Table Component Render Methods
------------------------------------------------------------------------------------------------------------------*/
// only a one-way height sync. we don't send the axis inner-content height to the DayGrid,
// but DayGrid still needs to have classNames on inner elements in order to measure.
this.renderTableRowAxis = (rowHeight) => {
let { options, viewApi } = this.context;
let renderProps = {
text: options.allDayText,
view: viewApi,
};
return (
// TODO: make reusable hook. used in list view too
y(ContentContainer, { elTag: "td", elClasses: [
'fc-timegrid-axis',
'fc-scrollgrid-shrink',
], elAttrs: {
'aria-hidden': true,
}, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, (InnerContent) => (y("div", { className: [
'fc-timegrid-axis-frame',
'fc-scrollgrid-shrink-frame',
rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : '',
].join(' '), style: { height: rowHeight } },
y(InnerContent, { elTag: "span", elClasses: [
'fc-timegrid-axis-cushion',
'fc-scrollgrid-shrink-cushion',
'fc-scrollgrid-sync-inner',
] })))));
};
this.handleSlatCoords = (slatCoords) => {
this.setState({ slatCoords });
};
}
// rendering
// ----------------------------------------------------------------------------------------------------
renderSimpleLayout(headerRowContent, allDayContent, timeContent) {
let { context, props } = this;
let sections = [];
let stickyHeaderDates = getStickyHeaderDates(context.options);
if (headerRowContent) {
sections.push({
type: 'header',
key: 'header',
isSticky: stickyHeaderDates,
chunk: {
elRef: this.headerElRef,
tableClassName: 'fc-col-header',
rowContent: headerRowContent,
},
});
}
if (allDayContent) {
sections.push({
type: 'body',
key: 'all-day',
chunk: { content: allDayContent },
});
sections.push({
type: 'body',
key: 'all-day-divider',
outerContent: ( // TODO: rename to cellContent so don't need to define <tr>?
y("tr", { role: "presentation", className: "fc-scrollgrid-section" },
y("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
});
}
sections.push({
type: 'body',
key: 'body',
liquid: true,
expandRows: Boolean(context.options.expandRows),
chunk: {
scrollerElRef: this.scrollerElRef,
content: timeContent,
},
});
return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections })));
}
renderHScrollLayout(headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) {
let ScrollGrid = this.context.pluginHooks.scrollGridImpl;
if (!ScrollGrid) {
throw new Error('No ScrollGrid implementation');
}
let { context, props } = this;
let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
let sections = [];
if (headerRowContent) {
sections.push({
type: 'header',
key: 'header',
isSticky: stickyHeaderDates,
syncRowHeights: true,
chunks: [
{
key: 'axis',
rowContent: (arg) => (y("tr", { role: "presentation" }, this.renderHeadAxis('day', arg.rowSyncHeights[0]))),
},
{
key: 'cols',
elRef: this.headerElRef,
tableClassName: 'fc-col-header',
rowContent: headerRowContent,
},
],
});
}
if (allDayContent) {
sections.push({
type: 'body',
key: 'all-day',
syncRowHeights: true,
chunks: [
{
key: 'axis',
rowContent: (contentArg) => (y("tr", { role: "presentation" }, this.renderTableRowAxis(contentArg.rowSyncHeights[0]))),
},
{
key: 'cols',
content: allDayContent,
},
],
});
sections.push({
key: 'all-day-divider',
type: 'body',
outerContent: ( // TODO: rename to cellContent so don't need to define <tr>?
y("tr", { role: "presentation", className: "fc-scrollgrid-section" },
y("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
});
}
let isNowIndicator = context.options.nowIndicator;
sections.push({
type: 'body',
key: 'body',
liquid: true,
expandRows: Boolean(context.options.expandRows),
chunks: [
{
key: 'axis',
content: (arg) => (
// TODO: make this now-indicator arrow more DRY with TimeColsContent
y("div", { className: "fc-timegrid-axis-chunk" },
y("table", { "aria-hidden": true, style: { height: arg.expandRows ? arg.clientHeight : '' } },
arg.tableColGroupNode,
y("tbody", null,
y(TimeBodyAxis, { slatMetas: slatMetas }))),
y("div", { className: "fc-timegrid-now-indicator-container" },
y(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, (nowDate) => {
let nowIndicatorTop = isNowIndicator &&
slatCoords &&
slatCoords.safeComputeTop(nowDate); // might return void
if (typeof nowIndicatorTop === 'number') {
return (y(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: nowDate }));
}
return null;
})))),
},
{
key: 'cols',
scrollerElRef: this.scrollerElRef,
content: timeContent,
},
],
});
if (stickyFooterScrollbar) {
sections.push({
key: 'footer',
type: 'footer',
isSticky: true,
chunks: [
{
key: 'axis',
content: renderScrollShim,
},
{
key: 'cols',
content: renderScrollShim,
},
],
});
}
return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: false, colGroups: [
{ width: 'shrink', cols: [{ width: 'shrink' }] },
{ cols: [{ span: colCnt, minWidth: dayMinWidth }] },
], sections: sections })));
}
/* Dimensions
------------------------------------------------------------------------------------------------------------------*/
getAllDayMaxEventProps() {
let { dayMaxEvents, dayMaxEventRows } = this.context.options;
if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto?
dayMaxEvents = undefined;
dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number
}
return { dayMaxEvents, dayMaxEventRows };
}
}
function renderAllDayInner(renderProps) {
return renderProps.text;
}
class TimeColsSlatsCoords {
constructor(positions, dateProfile, slotDuration) {
this.positions = positions;
this.dateProfile = dateProfile;
this.slotDuration = slotDuration;
}
safeComputeTop(date) {
let { dateProfile } = this;
if (rangeContainsMarker(dateProfile.currentRange, date)) {
let startOfDayDate = startOfDay(date);
let timeMs = date.valueOf() - startOfDayDate.valueOf();
if (timeMs >= asRoughMs(dateProfile.slotMinTime) &&
timeMs < asRoughMs(dateProfile.slotMaxTime)) {
return this.computeTimeTop(createDuration(timeMs));
}
}
return null;
}
// Computes the top coordinate, relative to the bounds of the grid, of the given date.
// A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
computeDateTop(when, startOfDayDate) {
if (!startOfDayDate) {
startOfDayDate = startOfDay(when);
}
return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf()));
}
// Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
// This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform.
// Eventually allow computation with arbirary slat dates.
computeTimeTop(duration) {
let { positions, dateProfile } = this;
let len = positions.els.length;
// floating-point value of # of slots covered
let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration);
let slatIndex;
let slatRemainder;
// compute a floating-point number for how many slats should be progressed through.
// from 0 to number of slats (inclusive)
// constrained because slotMinTime/slotMaxTime might be customized.
slatCoverage = Math.max(0, slatCoverage);
slatCoverage = Math.min(len, slatCoverage);
// an integer index of the furthest whole slat
// from 0 to number slats (*exclusive*, so len-1)
slatIndex = Math.floor(slatCoverage);
slatIndex = Math.min(slatIndex, len - 1);
// how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition.
// could be 1.0 if slatCoverage is covering *all* the slots
slatRemainder = slatCoverage - slatIndex;
return positions.tops[slatIndex] +
positions.getHeight(slatIndex) * slatRemainder;
}
}
class TimeColsSlatsBody extends BaseComponent {
render() {
let { props, context } = this;
let { options } = context;
let { slatElRefs } = props;
return (y("tbody", null, props.slatMetas.map((slatMeta, i) => {
let renderProps = {
time: slatMeta.time,
date: context.dateEnv.toDate(slatMeta.date),
view: context.viewApi,
};
return (y("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) },
props.axis && (y(TimeColsAxisCell, Object.assign({}, slatMeta))),
y(ContentContainer, { elTag: "td", elClasses: [
'fc-timegrid-slot',
'fc-timegrid-slot-lane',
!slatMeta.isLabeled && 'fc-timegrid-slot-minor',
], elAttrs: {
'data-time': slatMeta.isoTimeStr,
}, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })));
})));
}
}
/*
for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
*/
class TimeColsSlats extends BaseComponent {
constructor() {
super(...arguments);
this.rootElRef = d();
this.slatElRefs = new RefMap();
}
render() {
let { props, context } = this;
return (y("div", { ref: this.rootElRef, className: "fc-timegrid-slots" },
y("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: {
minWidth: props.tableMinWidth,
width: props.clientWidth,
height: props.minHeight,
} },
props.tableColGroupNode /* relies on there only being a single <col> for the axis */,
y(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas }))));
}
componentDidMount() {
this.updateSizing();
}
componentDidUpdate() {
this.updateSizing();
}
componentWillUnmount() {
if (this.props.onCoords) {
this.props.onCoords(null);
}
}
updateSizing() {
let { context, props } = this;
if (props.onCoords &&
props.clientWidth !== null // means sizing has stabilized
) {
let rootEl = this.rootElRef.current;
if (rootEl.offsetHeight) { // not hidden by css
props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration));
}
}
}
}
function collectSlatEls(elMap, slatMetas) {
return slatMetas.map((slatMeta) => elMap[slatMeta.key]);
}
function splitSegsByCol(segs, colCnt) {
let segsByCol = [];
let i;
for (i = 0; i < colCnt; i += 1) {
segsByCol.push([]);
}
if (segs) {
for (i = 0; i < segs.length; i += 1) {
segsByCol[segs[i].col].push(segs[i]);
}
}
return segsByCol;
}
function splitInteractionByCol(ui, colCnt) {
let byRow = [];
if (!ui) {
for (let i = 0; i < colCnt; i += 1) {
byRow[i] = null;
}
}
else {
for (let i = 0; i < colCnt; i += 1) {
byRow[i] = {
affectedInstances: ui.affectedInstances,
isEvent: ui.isEvent,
segs: [],
};
}
for (let seg of ui.segs) {
byRow[seg.col].segs.push(seg);
}
}
return byRow;
}
class TimeColMoreLink extends BaseComponent {
render() {
let { props } = this;
return (y(MoreLinkContainer, { elClasses: ['fc-timegrid-more-link'], elStyle: {
top: props.top,
bottom: props.bottom,
}, 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: internal_renderMoreLinkInner, forceTimed: true }, (InnerContent) => (y(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-more-link-inner', 'fc-sticky'] }))));
}
}
function internal_renderMoreLinkInner(props) {
return props.shortText;
}
// segInputs assumed sorted
function buildPositioning(segInputs, strictOrder, maxStackCnt) {
let hierarchy = new SegHierarchy();
if (strictOrder != null) {
hierarchy.strictOrder = strictOrder;
}
if (maxStackCnt != null) {
hierarchy.maxStackCnt = maxStackCnt;
}
let hiddenEntries = hierarchy.addSegs(segInputs);
let hiddenGroups = groupIntersectingEntries(hiddenEntries);
let web = buildWeb(hierarchy);
web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0
let segRects = webToRects(web);
return { segRects, hiddenGroups };
}
function buildWeb(hierarchy) {
const { entriesByLevel } = hierarchy;
const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => {
let siblingRange = findNextLevelSegs(hierarchy, level, lateral);
let nextLevelRes = buildNodes(siblingRange, buildNode);
let entry = entriesByLevel[level][lateral];
return [
Object.assign(Object.assign({}, entry), { nextLevelNodes: nextLevelRes[0] }),
entry.thickness + nextLevelRes[1], // the pressure builds
];
});
return buildNodes(entriesByLevel.length
? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length }
: null, buildNode)[0];
}
function buildNodes(siblingRange, buildNode) {
if (!siblingRange) {
return [[], 0];
}
let { level, lateralStart, lateralEnd } = siblingRange;
let lateral = lateralStart;
let pairs = [];
while (lateral < lateralEnd) {
pairs.push(buildNode(level, lateral));
lateral += 1;
}
pairs.sort(cmpDescPressures);
return [
pairs.map(extractNode),
pairs[0][1], // first item's pressure
];
}
function cmpDescPressures(a, b) {
return b[1] - a[1];
}
function extractNode(a) {
return a[0];
}
function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) {
let { levelCoords, entriesByLevel } = hierarchy;
let subjectEntry = entriesByLevel[subjectLevel][subjectLateral];
let afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness;
let levelCnt = levelCoords.length;
let level = subjectLevel;
// skip past levels that are too high up
for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1)
; // do nothing
for (; level < levelCnt; level += 1) {
let entries = entriesByLevel[level];
let entry;
let searchIndex = internal_common_binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd);
let lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one
let lateralEnd = lateralStart;
while ( // loop through entries that horizontally intersect
(entry = entries[lateralEnd]) && // but not past the whole seg list
entry.span.start < subjectEntry.span.end) {
lateralEnd += 1;
}
if (lateralStart < lateralEnd) {
return { level, lateralStart, lateralEnd };
}
}
return null;
}
function stretchWeb(topLevelNodes, totalThickness) {
const stretchNode = cacheable((node, startCoord, prevThickness) => buildEntryKey(node), (node, startCoord, prevThickness) => {
let { nextLevelNodes, thickness } = node;
let allThickness = thickness + prevThickness;
let thicknessFraction = thickness / allThickness;
let endCoord;
let newChildren = [];
if (!nextLevelNodes.length) {
endCoord = totalThickness;
}
else {
for (let childNode of nextLevelNodes) {
if (endCoord === undefined) {
let res = stretchNode(childNode, startCoord, allThickness);
endCoord = res[0];
newChildren.push(res[1]);
}
else {
let res = stretchNode(childNode, endCoord, 0);
newChildren.push(res[1]);
}
}
}
let newThickness = (endCoord - startCoord) * thicknessFraction;
return [endCoord - newThickness, Object.assign(Object.assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })];
});
return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]);
}
// not sorted in any particular order
function webToRects(topLevelNodes) {
let rects = [];
const processNode = cacheable((node, levelCoord, stackDepth) => buildEntryKey(node), (node, levelCoord, stackDepth) => {
let rect = Object.assign(Object.assign({}, node), { levelCoord,
stackDepth, stackForward: 0 });
rects.push(rect);
return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1);
});
function processNodes(nodes, levelCoord, stackDepth) {
let stackForward = 0;
for (let node of nodes) {
stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward);
}
return stackForward;
}
processNodes(topLevelNodes, 0, 0);
return rects; // TODO: sort rects by levelCoord to be consistent with toRects?
}
// TODO: move to general util
function cacheable(keyFunc, workFunc) {
const cache = {};
return (...args) => {
let key = keyFunc(...args);
return (key in cache)
? cache[key]
: (cache[key] = workFunc(...args));
};
}
function computeSegVCoords(segs, colDate, slatCoords = null, eventMinHeight = 0) {
let vcoords = [];
if (slatCoords) {
for (let i = 0; i < segs.length; i += 1) {
let seg = segs[i];
let spanStart = slatCoords.computeDateTop(seg.start, colDate);
let spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :(
slatCoords.computeDateTop(seg.end, colDate));
vcoords.push({
start: Math.round(spanStart),
end: Math.round(spanEnd), //
});
}
}
return vcoords;
}
function computeFgSegPlacements(segs, segVCoords, // might not have for every seg
eventOrderStrict, eventMaxStack) {
let segInputs = [];
let dumbSegs = []; // segs without coords
for (let i = 0; i < segs.length; i += 1) {
let vcoords = segVCoords[i];
if (vcoords) {
segInputs.push({
index: i,
thickness: 1,
span: vcoords,
});
}
else {
dumbSegs.push(segs[i]);
}
}
let { segRects, hiddenGroups } = buildPositioning(segInputs, eventOrderStrict, eventMaxStack);
let segPlacements = [];
for (let segRect of segRects) {
segPlacements.push({
seg: segs[segRect.index],
rect: segRect,
});
}
for (let dumbSeg of dumbSegs) {
segPlacements.push({ seg: dumbSeg, rect: null });
}
return { segPlacements, hiddenGroups };
}
const DEFAULT_TIME_FORMAT = createFormatter({
hour: 'numeric',
minute: '2-digit',
meridiem: false,
});
class TimeColEvent extends BaseComponent {
render() {
return (y(StandardEvent, Object.assign({}, this.props, { elClasses: [
'fc-timegrid-event',
'fc-v-event',
this.props.isShort && 'fc-timegrid-event-short',
], defaultTimeFormat: DEFAULT_TIME_FORMAT })));
}
}
class TimeCol extends BaseComponent {
constructor() {
super(...arguments);
this.sortEventSegs = memoize(sortEventSegs);
}
// TODO: memoize event-placement?
render() {
let { props, context } = this;
let { options } = context;
let isSelectMirror = options.selectMirror;
let mirrorSegs = // yuck
(props.eventDrag && props.eventDrag.segs) ||
(props.eventResize && props.eventResize.segs) ||
(isSelectMirror && props.dateSelectionSegs) ||
[];
let interactionAffectedInstances = // TODO: messy way to compute this
(props.eventDrag && props.eventDrag.affectedInstances) ||
(props.eventResize && props.eventResize.affectedInstances) ||
{};
let sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
return (y(DayCellContainer, { elTag: "td", elRef: props.elRef, elClasses: [
'fc-timegrid-col',
...(props.extraClassNames || []),
], elAttrs: Object.assign({ role: 'gridcell' }, props.extraDataAttrs), date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraRenderProps: props.extraRenderProps }, (InnerContent) => (y("div", { className: "fc-timegrid-col-frame" },
y("div", { className: "fc-timegrid-col-bg" },
this.renderFillSegs(props.businessHourSegs, 'non-business'),
this.renderFillSegs(props.bgEventSegs, 'bg-event'),
this.renderFillSegs(props.dateSelectionSegs, 'highlight')),
y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)),
y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror), 'mirror')),
y("div", { className: "fc-timegrid-now-indicator-container" }, this.renderNowIndicator(props.nowIndicatorSegs)),
hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-col-misc'] }))))));
}
renderFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) {
let { props } = this;
if (props.forPrint) {
return renderPlainFgSegs(sortedFgSegs, props);
}
return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey);
}
renderPositionedFgSegs(segs, // if not mirror, needs to be sorted
segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) {
let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = this.context.options;
let { date, slatCoords, eventSelection, todayRange, nowDate } = this.props;
let isMirror = isDragging || isResizing || isDateSelecting;
let segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight);
let { segPlacements, hiddenGroups } = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack);
return (y(_, null,
this.renderHiddenGroups(hiddenGroups, segs),
segPlacements.map((segPlacement) => {
let { seg, rect } = segPlacement;
let instanceId = seg.eventRange.instance.instanceId;
let isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect);
let vStyle = computeSegVStyle(rect && rect.span);
let hStyle = (!isMirror && rect) ? this.computeSegHStyle(rect) : { left: 0, right: 0 };
let isInset = Boolean(rect) && rect.stackForward > 0;
let isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem
return (y("div", { className: 'fc-timegrid-event-harness' +
(isInset ? ' fc-timegrid-event-harness-inset' : ''), key: forcedKey || instanceId, style: Object.assign(Object.assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) },
y(TimeColEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate)))));
})));
}
// will already have eventMinHeight applied because segInputs already had it
renderHiddenGroups(hiddenGroups, segs) {
let { extraDateSpan, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize } = this.props;
return (y(_, null, hiddenGroups.map((hiddenGroup) => {
let positionCss = computeSegVStyle(hiddenGroup.span);
let hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs);
return (y(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize }));
})));
}
renderFillSegs(segs, fillType) {
let { props, context } = this;
let segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated
let children = segVCoords.map((vcoords, i) => {
let seg = segs[i];
return (y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ?
y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) :
renderFill(fillType)));
});
return y(_, null, children);
}
renderNowIndicator(segs) {
let { slatCoords, date } = this.props;
if (!slatCoords) {
return null;
}
return segs.map((seg, i) => (y(NowIndicatorContainer
// key doesn't matter. will only ever be one
, {
// key doesn't matter. will only ever be one
key: i, elClasses: ['fc-timegrid-now-indicator-line'], elStyle: {
top: slatCoords.computeDateTop(seg.start, date),
}, isAxis: false, date: date })));
}
computeSegHStyle(segHCoords) {
let { isRtl, options } = this.context;
let shouldOverlap = options.slotEventOverlap;
let nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point
let farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point
let left; // amount of space from left edge, a fraction of the total width
let right; // amount of space from right edge, a fraction of the total width
if (shouldOverlap) {
// double the width, but don't go beyond the maximum forward coordinate (1.0)
farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2);
}
if (isRtl) {
left = 1 - farCoord;
right = nearCoord;
}
else {
left = nearCoord;
right = 1 - farCoord;
}
let props = {
zIndex: segHCoords.stackDepth + 1,
left: left * 100 + '%',
right: right * 100 + '%',
};
if (shouldOverlap && !segHCoords.stackForward) {
// add padding to the edge so that forward stacked events don't cover the resizer's icon
props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
}
return props;
}
}
function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }) {
let hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) ||
(eventResize ? eventResize.affectedInstances : null) ||
{};
return (y(_, null, sortedFgSegs.map((seg) => {
let instanceId = seg.eventRange.instance.instanceId;
return (y("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } },
y(TimeColEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate)))));
})));
}
function computeSegVStyle(segVCoords) {
if (!segVCoords) {
return { top: '', bottom: '' };
}
return {
top: segVCoords.start,
bottom: -segVCoords.end,
};
}
function compileSegsFromEntries(segEntries, allSegs) {
return segEntries.map((segEntry) => allSegs[segEntry.index]);
}
class TimeColsContent extends BaseComponent {
constructor() {
super(...arguments);
this.splitFgEventSegs = memoize(splitSegsByCol);
this.splitBgEventSegs = memoize(splitSegsByCol);
this.splitBusinessHourSegs = memoize(splitSegsByCol);
this.splitNowIndicatorSegs = memoize(splitSegsByCol);
this.splitDateSelectionSegs = memoize(splitSegsByCol);
this.splitEventDrag = memoize(splitInteractionByCol);
this.splitEventResize = memoize(splitInteractionByCol);
this.rootElRef = d();
this.cellElRefs = new RefMap();
}
render() {
let { props, context } = this;
let nowIndicatorTop = context.options.nowIndicator &&
props.slatCoords &&
props.slatCoords.safeComputeTop(props.nowDate); // might return void
let colCnt = props.cells.length;
let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt);
let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt);
let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt);
let nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt);
let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt);
let eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt);
let eventResizeByRow = this.splitEventResize(props.eventResize, colCnt);
return (y("div", { className: "fc-timegrid-cols", ref: this.rootElRef },
y("table", { role: "presentation", style: {
minWidth: props.tableMinWidth,
width: props.clientWidth,
} },
props.tableColGroupNode,
y("tbody", { role: "presentation" },
y("tr", { role: "row" },
props.axis && (y("td", { "aria-hidden": true, className: "fc-timegrid-col fc-timegrid-axis" },
y("div", { className: "fc-timegrid-col-frame" },
y("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (y(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: props.nowDate })))))),
props.cells.map((cell, i) => (y(TimeCol, { key: cell.key, elRef: this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraRenderProps: cell.extraRenderProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint }))))))));
}
componentDidMount() {
this.updateCoords();
}
componentDidUpdate() {
this.updateCoords();
}
updateCoords() {
let { props } = this;
if (props.onColCoords &&
props.clientWidth !== null // means sizing has stabilized
) {
props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal
false));
}
}
}
function collectCellEls(elMap, cells) {
return cells.map((cell) => elMap[cell.key]);
}
/* A component that renders one or more columns of vertical time slots
----------------------------------------------------------------------------------------------------------------------*/
class TimeCols extends DateComponent {
constructor() {
super(...arguments);
this.processSlotOptions = memoize(processSlotOptions);
this.state = {
slatCoords: null,
};
this.handleRootEl = (el) => {
if (el) {
this.context.registerInteractiveComponent(this, {
el,
isHitComboAllowed: this.props.isHitComboAllowed,
});
}
else {
this.context.unregisterInteractiveComponent(this);
}
};
this.handleScrollRequest = (request) => {
let { onScrollTopRequest } = this.props;
let { slatCoords } = this.state;
if (onScrollTopRequest && slatCoords) {
if (request.time) {
let top = slatCoords.computeTimeTop(request.time);
top = Math.ceil(top); // zoom can give weird floating-point values. rather scroll a little bit further
if (top) {
top += 1; // to overcome top border that slots beyond the first have. looks better
}
onScrollTopRequest(top);
}
return true;
}
return false;
};
this.handleColCoords = (colCoords) => {
this.colCoords = colCoords;
};
this.handleSlatCoords = (slatCoords) => {
this.setState({ slatCoords });
if (this.props.onSlatCoords) {
this.props.onSlatCoords(slatCoords);
}
};
}
render() {
let { props, state } = this;
return (y("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: {
// these props are important to give this wrapper correct dimensions for interactions
// TODO: if we set it here, can we avoid giving to inner tables?
width: props.clientWidth,
minWidth: props.tableMinWidth,
} },
y(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }),
y(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint })));
}
componentDidMount() {
this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest);
}
componentDidUpdate(prevProps) {
this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile);
}
componentWillUnmount() {
this.scrollResponder.detach();
}
queryHit(positionLeft, positionTop) {
let { dateEnv, options } = this.context;
let { colCoords } = this;
let { dateProfile } = this.props;
let { slatCoords } = this.state;
let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration);
let colIndex = colCoords.leftToIndex(positionLeft);
let slatIndex = slatCoords.positions.topToIndex(positionTop);
if (colIndex != null && slatIndex != null) {
let cell = this.props.cells[colIndex];
let slatTop = slatCoords.positions.tops[slatIndex];
let slatHeight = slatCoords.positions.getHeight(slatIndex);
let partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1
let localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
let snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
let dayDate = this.props.cells[colIndex].date;
let time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex));
let start = dateEnv.add(dayDate, time);
let end = dateEnv.add(start, snapDuration);
return {
dateProfile,
dateSpan: Object.assign({ range: { start, end }, allDay: false }, cell.extraDateSpan),
dayEl: colCoords.els[colIndex],
rect: {
left: colCoords.lefts[colIndex],
right: colCoords.rights[colIndex],
top: slatTop,
bottom: slatTop + slatHeight,
},
layer: 0,
};
}
return null;
}
}
function processSlotOptions(slotDuration, snapDurationOverride) {
let snapDuration = snapDurationOverride || slotDuration;
let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration);
if (snapsPerSlot === null) {
snapDuration = slotDuration;
snapsPerSlot = 1;
// TODO: say warning?
}
return { snapDuration, snapsPerSlot };
}
class DayTimeColsSlicer extends Slicer {
sliceRange(range, dayRanges) {
let segs = [];
for (let col = 0; col < dayRanges.length; col += 1) {
let segRange = intersectRanges(range, dayRanges[col]);
if (segRange) {
segs.push({
start: segRange.start,
end: segRange.end,
isStart: segRange.start.valueOf() === range.start.valueOf(),
isEnd: segRange.end.valueOf() === range.end.valueOf(),
col,
});
}
}
return segs;
}
}
class DayTimeCols extends DateComponent {
constructor() {
super(...arguments);
this.buildDayRanges = memoize(buildDayRanges);
this.slicer = new DayTimeColsSlicer();
this.timeColsRef = d();
}
render() {
let { props, context } = this;
let { dateProfile, dayTableModel } = props;
let { nowIndicator, nextDayThreshold } = context.options;
let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv);
// give it the first row of cells
// TODO: would move this further down hierarchy, but sliceNowDate needs it
return (y(NowTimer, { unit: nowIndicator ? 'minute' : 'day' }, (nowDate, todayRange) => (y(TimeCols, Object.assign({ ref: this.timeColsRef }, this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: nowIndicator && this.slicer.sliceNowDate(nowDate, dateProfile, nextDayThreshold, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords })))));
}
}
function buildDayRanges(dayTableModel, dateProfile, dateEnv) {
let ranges = [];
for (let date of dayTableModel.headerDates) {
ranges.push({
start: dateEnv.add(date, dateProfile.slotMinTime),
end: dateEnv.add(date, dateProfile.slotMaxTime),
});
}
return ranges;
}
// potential nice values for the slot-duration and interval-duration
// from largest to smallest
const STOCK_SUB_DURATIONS = [
{ hours: 1 },
{ minutes: 30 },
{ minutes: 15 },
{ seconds: 30 },
{ seconds: 15 },
];
function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) {
let dayStart = new Date(0);
let slatTime = slotMinTime;
let slatIterator = createDuration(0);
let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration);
let metas = [];
while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) {
let date = dateEnv.add(dayStart, slatTime);
let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null;
metas.push({
date,
time: slatTime,
key: date.toISOString(),
isoTimeStr: formatIsoTimeString(date),
isLabeled,
});
slatTime = addDurations(slatTime, slotDuration);
slatIterator = addDurations(slatIterator, slotDuration);
}
return metas;
}
// Computes an automatic value for slotLabelInterval
function computeLabelInterval(slotDuration) {
let i;
let labelInterval;
let slotsPerLabel;
// find the smallest stock label interval that results in more than one slots-per-label
for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) {
labelInterval = createDuration(STOCK_SUB_DURATIONS[i]);
slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration);
if (slotsPerLabel !== null && slotsPerLabel > 1) {
return labelInterval;
}
}
return slotDuration; // fall back
}
class DayTimeColsView extends TimeColsView {
constructor() {
super(...arguments);
this.buildTimeColsModel = memoize(buildTimeColsModel);
this.buildSlatMetas = memoize(buildSlatMetas);
}
render() {
let { options, dateEnv, dateProfileGenerator } = this.context;
let { props } = this;
let { dateProfile } = props;
let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator);
let splitProps = this.allDaySplitter.splitProps(props);
let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv);
let { dayMinWidth } = options;
let hasAttachedAxis = !dayMinWidth;
let hasDetachedAxis = dayMinWidth;
let headerContent = options.dayHeaders && (y(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null }));
let allDayContent = (options.allDaySlot !== false) && ((contentArg) => (y(DayTable, Object.assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, this.getAllDayMaxEventProps()))));
let timeGridContent = (contentArg) => (y(DayTimeCols, Object.assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: this.handleScrollTopRequest })));
return hasDetachedAxis
? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords)
: this.renderSimpleLayout(headerContent, allDayContent, timeGridContent);
}
}
function buildTimeColsModel(dateProfile, dateProfileGenerator) {
let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
return new DayTableModel(daySeries, false);
}
var timegrid_internal_css_248z = ".fc-v-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-v-event .fc-event-main{color:var(--fc-event-text-color);height:100%}.fc-v-event .fc-event-main-frame{display:flex;flex-direction:column;height:100%}.fc-v-event .fc-event-time{flex-grow:0;flex-shrink:0;max-height:100%;overflow:hidden}.fc-v-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-height:0}.fc-v-event .fc-event-title{bottom:0;max-height:100%;overflow:hidden;top:0}.fc-v-event:not(.fc-event-start){border-top-left-radius:0;border-top-right-radius:0;border-top-width:0}.fc-v-event:not(.fc-event-end){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:0}.fc-v-event.fc-event-selected:before{left:-10px;right:-10px}.fc-v-event .fc-event-resizer-start{cursor:n-resize}.fc-v-event .fc-event-resizer-end{cursor:s-resize}.fc-v-event:not(.fc-event-selected) .fc-event-resizer{height:var(--fc-event-resizer-thickness);left:0;right:0}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start{top:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer{left:50%;margin-left:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-start{top:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc .fc-timegrid .fc-daygrid-body{z-index:2}.fc .fc-timegrid-divider{padding:0 0 2px}.fc .fc-timegrid-body{min-height:100%;position:relative;z-index:1}.fc .fc-timegrid-axis-chunk{position:relative}.fc .fc-timegrid-axis-chunk>table,.fc .fc-timegrid-slots{position:relative;z-index:1}.fc .fc-timegrid-slot{border-bottom:0;height:1.5em}.fc .fc-timegrid-slot:empty:before{content:\"\\00a0\"}.fc .fc-timegrid-slot-minor{border-top-style:dotted}.fc .fc-timegrid-slot-label-cushion{display:inline-block;white-space:nowrap}.fc .fc-timegrid-slot-label{vertical-align:middle}.fc .fc-timegrid-axis-cushion,.fc .fc-timegrid-slot-label-cushion{padding:0 4px}.fc .fc-timegrid-axis-frame-liquid{height:100%}.fc .fc-timegrid-axis-frame{align-items:center;display:flex;justify-content:flex-end;overflow:hidden}.fc .fc-timegrid-axis-cushion{flex-shrink:0;max-width:60px}.fc-direction-ltr .fc-timegrid-slot-label-frame{text-align:right}.fc-direction-rtl .fc-timegrid-slot-label-frame{text-align:left}.fc-liquid-hack .fc-timegrid-axis-frame-liquid{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-timegrid-col-frame{min-height:100%;position:relative}.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols{bottom:0;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols>table{height:100%}.fc-media-screen .fc-timegrid-col-bg,.fc-media-screen .fc-timegrid-col-events,.fc-media-screen .fc-timegrid-now-indicator-container{left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col-bg{z-index:2}.fc .fc-timegrid-col-bg .fc-non-business{z-index:1}.fc .fc-timegrid-col-bg .fc-bg-event{z-index:2}.fc .fc-timegrid-col-bg .fc-highlight{z-index:3}.fc .fc-timegrid-bg-harness{left:0;position:absolute;right:0}.fc .fc-timegrid-col-events{z-index:3}.fc .fc-timegrid-now-indicator-container{bottom:0;overflow:hidden}.fc-direction-ltr .fc-timegrid-col-events{margin:0 2.5% 0 2px}.fc-direction-rtl .fc-timegrid-col-events{margin:0 2px 0 2.5%}.fc-timegrid-event-harness{position:absolute}.fc-timegrid-event-harness>.fc-timegrid-event{bottom:0;left:0;position:absolute;right:0;top:0}.fc-timegrid-event-harness-inset .fc-timegrid-event,.fc-timegrid-event.fc-event-mirror,.fc-timegrid-more-link{box-shadow:0 0 0 1px var(--fc-page-bg-color)}.fc-timegrid-event,.fc-timegrid-more-link{border-radius:3px;font-size:var(--fc-small-font-size)}.fc-timegrid-event{margin-bottom:1px}.fc-timegrid-event .fc-event-main{padding:1px 1px 0}.fc-timegrid-event .fc-event-time{font-size:var(--fc-small-font-size);margin-bottom:1px;white-space:nowrap}.fc-timegrid-event-short .fc-event-main-frame{flex-direction:row;overflow:hidden}.fc-timegrid-event-short .fc-event-time:after{content:\"\\00a0-\\00a0\"}.fc-timegrid-event-short .fc-event-title{font-size:var(--fc-small-font-size)}.fc-timegrid-more-link{background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;margin-bottom:1px;position:absolute;z-index:9999}.fc-timegrid-more-link-inner{padding:3px 2px;top:0}.fc-direction-ltr .fc-timegrid-more-link{right:0}.fc-direction-rtl .fc-timegrid-more-link{left:0}.fc .fc-timegrid-now-indicator-arrow,.fc .fc-timegrid-now-indicator-line{pointer-events:none}.fc .fc-timegrid-now-indicator-line{border-color:var(--fc-now-indicator-color);border-style:solid;border-width:1px 0 0;left:0;position:absolute;right:0;z-index:4}.fc .fc-timegrid-now-indicator-arrow{border-color:var(--fc-now-indicator-color);border-style:solid;margin-top:-5px;position:absolute;z-index:4}.fc-direction-ltr .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 0 5px 6px;left:0}.fc-direction-rtl .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 6px 5px 0;right:0}";
injectStyles(timegrid_internal_css_248z);
;// ../../node_modules/@fullcalendar/timegrid/index.js
const timegrid_OPTION_REFINERS = {
allDaySlot: Boolean,
};
var timegrid_index = createPlugin({
name: '@fullcalendar/timegrid',
initialView: 'timeGridWeek',
optionRefiners: timegrid_OPTION_REFINERS,
views: {
timeGrid: {
component: DayTimeColsView,
usesMinMaxTime: true,
allDaySlot: true,
slotDuration: '00:30:00',
slotEventOverlap: true, // a bad name. confused with overlap/constraint system
},
timeGridDay: {
type: 'timeGrid',
duration: { days: 1 },
},
timeGridWeek: {
type: 'timeGrid',
duration: { weeks: 1 },
},
},
});
;// ../../node_modules/@fullcalendar/list/internal.js
class ListViewHeaderRow extends BaseComponent {
constructor() {
super(...arguments);
this.state = {
textId: getUniqueDomId(),
};
}
render() {
let { theme, dateEnv, options, viewApi } = this.context;
let { cellId, dayDate, todayRange } = this.props;
let { textId } = this.state;
let dayMeta = getDateMeta(dayDate, todayRange);
// will ever be falsy?
let text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : '';
// will ever be falsy? also, BAD NAME "alt"
let sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : '';
let renderProps = Object.assign({ date: dateEnv.toDate(dayDate), view: viewApi, textId,
text,
sideText, navLinkAttrs: buildNavLinkAttrs(this.context, dayDate), sideNavLinkAttrs: buildNavLinkAttrs(this.context, dayDate, 'day', false) }, dayMeta);
// TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too)
return (y(ContentContainer, { elTag: "tr", elClasses: [
'fc-list-day',
...getDayClassNames(dayMeta, theme),
], elAttrs: {
'data-date': formatDayString(dayDate),
}, renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: list_internal_renderInnerContent, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => ( // TODO: force-hide top border based on :first-child
y("th", { scope: "colgroup", colSpan: 3, id: cellId, "aria-labelledby": textId },
y(InnerContent, { elTag: "div", elClasses: [
'fc-list-day-cushion',
theme.getClass('tableCellShaded'),
] })))));
}
}
function list_internal_renderInnerContent(props) {
return (y(_, null,
props.text && (y("a", Object.assign({ id: props.textId, className: "fc-list-day-text" }, props.navLinkAttrs), props.text)),
props.sideText && ( /* not keyboard tabbable */y("a", Object.assign({ "aria-hidden": true, className: "fc-list-day-side-text" }, props.sideNavLinkAttrs), props.sideText))));
}
const internal_DEFAULT_TIME_FORMAT = createFormatter({
hour: 'numeric',
minute: '2-digit',
meridiem: 'short',
});
class ListViewEventRow extends BaseComponent {
render() {
let { props, context } = this;
let { options } = context;
let { seg, timeHeaderId, eventHeaderId, dateHeaderId } = props;
let timeFormat = options.eventTimeFormat || internal_DEFAULT_TIME_FORMAT;
return (y(EventContainer, Object.assign({}, props, { elTag: "tr", elClasses: [
'fc-list-event',
seg.eventRange.def.url && 'fc-event-forced-url',
], defaultGenerator: () => renderEventInnerContent(seg, context) /* weird */, seg: seg, timeText: "", disableDragging: true, disableResizing: true }), (InnerContent, eventContentArg) => (y(_, null,
buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId),
y("td", { "aria-hidden": true, className: "fc-list-event-graphic" },
y("span", { className: "fc-list-event-dot", style: {
borderColor: eventContentArg.borderColor || eventContentArg.backgroundColor,
} })),
y(InnerContent, { elTag: "td", elClasses: ['fc-list-event-title'], elAttrs: { headers: `${eventHeaderId} ${dateHeaderId}` } })))));
}
}
function renderEventInnerContent(seg, context) {
let interactiveAttrs = getSegAnchorAttrs(seg, context);
return (y("a", Object.assign({}, interactiveAttrs), seg.eventRange.def.title));
}
function buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId) {
let { options } = context;
if (options.displayEventTime !== false) {
let eventDef = seg.eventRange.def;
let eventInstance = seg.eventRange.instance;
let doAllDay = false;
let timeText;
if (eventDef.allDay) {
doAllDay = true;
}
else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead?
if (seg.isStart) {
timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end);
}
else if (seg.isEnd) {
timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end);
}
else {
doAllDay = true;
}
}
else {
timeText = buildSegTimeText(seg, timeFormat, context);
}
if (doAllDay) {
let renderProps = {
text: context.options.allDayText,
view: context.viewApi,
};
return (y(ContentContainer, { elTag: "td", elClasses: ['fc-list-event-time'], elAttrs: {
headers: `${timeHeaderId} ${dateHeaderId}`,
}, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: internal_renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }));
}
return (y("td", { className: "fc-list-event-time" }, timeText));
}
return null;
}
function internal_renderAllDayInner(renderProps) {
return renderProps.text;
}
/*
Responsible for the scroller, and forwarding event-related actions into the "grid".
*/
class ListView extends DateComponent {
constructor() {
super(...arguments);
this.computeDateVars = memoize(computeDateVars);
this.eventStoreToSegs = memoize(this._eventStoreToSegs);
this.state = {
timeHeaderId: getUniqueDomId(),
eventHeaderId: getUniqueDomId(),
dateHeaderIdRoot: getUniqueDomId(),
};
this.setRootEl = (rootEl) => {
if (rootEl) {
this.context.registerInteractiveComponent(this, {
el: rootEl,
});
}
else {
this.context.unregisterInteractiveComponent(this);
}
};
}
render() {
let { props, context } = this;
let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile);
let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges);
return (y(ViewContainer, { elRef: this.setRootEl, elClasses: [
'fc-list',
context.theme.getClass('table'),
context.options.stickyHeaderDates !== false ?
'fc-list-sticky' :
'',
], viewSpec: context.viewSpec },
y(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ?
this.renderSegList(eventSegs, dayDates) :
this.renderEmptyMessage())));
}
renderEmptyMessage() {
let { options, viewApi } = this.context;
let renderProps = {
text: options.noEventsText,
view: viewApi,
};
return (y(ContentContainer, { elTag: "div", elClasses: ['fc-list-empty'], renderProps: renderProps, generatorName: "noEventsContent", customGenerator: options.noEventsContent, defaultGenerator: renderNoEventsInner, classNameGenerator: options.noEventsClassNames, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, (InnerContent) => (y(InnerContent, { elTag: "div", elClasses: ['fc-list-empty-cushion'] }))));
}
renderSegList(allSegs, dayDates) {
let { theme, options } = this.context;
let { timeHeaderId, eventHeaderId, dateHeaderIdRoot } = this.state;
let segsByDay = groupSegsByDay(allSegs); // sparse array
return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => {
let innerNodes = [];
for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) {
let daySegs = segsByDay[dayIndex];
if (daySegs) { // sparse array, so might be undefined
let dayStr = formatDayString(dayDates[dayIndex]);
let dateHeaderId = dateHeaderIdRoot + '-' + dayStr;
// append a day header
innerNodes.push(y(ListViewHeaderRow, { key: dayStr, cellId: dateHeaderId, dayDate: dayDates[dayIndex], todayRange: todayRange }));
daySegs = sortEventSegs(daySegs, options.eventOrder);
for (let seg of daySegs) {
innerNodes.push(y(ListViewEventRow, Object.assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, timeHeaderId: timeHeaderId, eventHeaderId: eventHeaderId, dateHeaderId: dateHeaderId }, getSegMeta(seg, todayRange, nowDate))));
}
}
}
return (y("table", { className: 'fc-list-table ' + theme.getClass('table') },
y("thead", null,
y("tr", null,
y("th", { scope: "col", id: timeHeaderId }, options.timeHint),
y("th", { scope: "col", "aria-hidden": true }),
y("th", { scope: "col", id: eventHeaderId }, options.eventHint))),
y("tbody", null, innerNodes)));
}));
}
_eventStoreToSegs(eventStore, eventUiBases, dayRanges) {
return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges);
}
eventRangesToSegs(eventRanges, dayRanges) {
let segs = [];
for (let eventRange of eventRanges) {
segs.push(...this.eventRangeToSegs(eventRange, dayRanges));
}
return segs;
}
eventRangeToSegs(eventRange, dayRanges) {
let { dateEnv } = this.context;
let { nextDayThreshold } = this.context.options;
let range = eventRange.range;
let allDay = eventRange.def.allDay;
let dayIndex;
let segRange;
let seg;
let segs = [];
for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) {
segRange = intersectRanges(range, dayRanges[dayIndex]);
if (segRange) {
seg = {
component: this,
eventRange,
start: segRange.start,
end: segRange.end,
isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(),
isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(),
dayIndex,
};
segs.push(seg);
// detect when range won't go fully into the next day,
// and mutate the latest seg to the be the end.
if (!seg.isEnd && !allDay &&
dayIndex + 1 < dayRanges.length &&
range.end <
dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) {
seg.end = range.end;
seg.isEnd = true;
break;
}
}
}
return segs;
}
}
function renderNoEventsInner(renderProps) {
return renderProps.text;
}
function computeDateVars(dateProfile) {
let dayStart = startOfDay(dateProfile.renderRange.start);
let viewEnd = dateProfile.renderRange.end;
let dayDates = [];
let dayRanges = [];
while (dayStart < viewEnd) {
dayDates.push(dayStart);
dayRanges.push({
start: dayStart,
end: addDays(dayStart, 1),
});
dayStart = addDays(dayStart, 1);
}
return { dayDates, dayRanges };
}
// Returns a sparse array of arrays, segs grouped by their dayIndex
function groupSegsByDay(segs) {
let segsByDay = []; // sparse array
let i;
let seg;
for (i = 0; i < segs.length; i += 1) {
seg = segs[i];
(segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = []))
.push(seg);
}
return segsByDay;
}
var list_internal_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}";
injectStyles(list_internal_css_248z);
;// ../../node_modules/@fullcalendar/list/index.js
const list_OPTION_REFINERS = {
listDayFormat: createFalsableFormatter,
listDaySideFormat: createFalsableFormatter,
noEventsClassNames: identity,
noEventsContent: identity,
noEventsDidMount: identity,
noEventsWillUnmount: identity,
// noEventsText is defined in base options
};
function createFalsableFormatter(input) {
return input === false ? null : createFormatter(input);
}
var list_index = createPlugin({
name: '@fullcalendar/list',
optionRefiners: list_OPTION_REFINERS,
views: {
list: {
component: ListView,
buttonTextKey: 'list',
listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016"
},
listDay: {
type: 'list',
duration: { days: 1 },
listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar
},
listWeek: {
type: 'list',
duration: { weeks: 1 },
listDayFormat: { weekday: 'long' },
listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' },
},
listMonth: {
type: 'list',
duration: { month: 1 },
listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have
},
listYear: {
type: 'list',
duration: { year: 1 },
listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have
},
},
});
;// ../assets/scripts/fullcalendar/index.js
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
// element found in dom ?
if (calendarEl == null) {
return;
}
const calendar = new Calendar(calendarEl, {
plugins: [interaction_index, daygrid_index, timegrid_index, list_index],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialDate: date.format(date.now(), 'YYYY-MM-DD'),
navLinks: true,
// can click day/week names to navigate views
editable: true,
dayMaxEvents: true,
// allow "more" link when too many events
events: [{
title: 'All Day Event',
start: date.format(date.now(), 'YYYY-MM-DD')
}, {
title: 'Long Event',
start: date.format(date.add(date.now(), 1, 'day'), 'YYYY-MM-DD'),
end: date.format(date.add(date.now(), 4, 'day'), 'YYYY-MM-DD')
}, {
groupId: 999,
title: 'Repeating Event',
start: date.format(date.add(date.now(), 2, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00')
}, {
groupId: 999,
title: 'Repeating Event',
start: date.format(date.add(date.now(), 9, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00')
}, {
title: 'Conference',
start: date.format(date.add(date.now(), 5, 'day'), 'YYYY-MM-DD'),
end: date.format(date.add(date.now(), 7, 'day'), 'YYYY-MM-DD')
}, {
title: 'Meeting',
start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T10:30'),
end: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:30')
}, {
title: 'Lunch',
start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:00')
}, {
title: 'Meeting',
start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T14:30')
}, {
title: 'Happy Hour',
start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T17:30')
}, {
title: 'Dinner',
start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T20:00')
}, {
title: 'Birthday Party',
start: date.format(date.add(date.now(), 4, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T07:00')
}, {
title: 'Click for Google',
url: 'http://google.com/',
start: date.format(date.add(date.now(), 14, 'day'), 'YYYY-MM-DD')
}]
});
calendar.render();
});
// EXTERNAL MODULE: ../../node_modules/masonry-layout/masonry.js
var masonry = __webpack_require__(201);
var masonry_default = /*#__PURE__*/__webpack_require__.n(masonry);
;// ../assets/scripts/masonry/index.js
/* harmony default export */ const scripts_masonry = ((function () {
window.addEventListener('load', () => {
const masonryElement = document.querySelector('.masonry');
if (masonryElement) {
new (masonry_default())(masonryElement, {
itemSelector: '.masonry-item',
columnWidth: '.masonry-sizer',
percentPosition: true
});
}
});
})());
;// ../assets/scripts/popover/index.js
// Simple vanilla JS tooltip and popover implementation
/* harmony default export */ const popover = ((function () {
// Simple tooltip implementation
function initTooltips() {
const tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipElements.forEach(element => {
const tooltipText = element.getAttribute('data-bs-title') || element.getAttribute('title');
if (tooltipText) {
element.addEventListener('mouseenter', function () {
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip';
tooltip.textContent = tooltipText;
tooltip.style.cssText = `
position: absolute;
background: #000;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
z-index: 1050;
pointer-events: none;
white-space: nowrap;
`;
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`;
tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`;
element._tooltip = tooltip;
});
element.addEventListener('mouseleave', function () {
if (element._tooltip) {
element._tooltip.remove();
element._tooltip = null;
}
});
}
});
}
// Simple popover implementation
function initPopovers() {
const popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]');
popoverElements.forEach(element => {
const popoverContent = element.getAttribute('data-bs-content');
const popoverTitle = element.getAttribute('data-bs-title');
if (popoverContent) {
element.addEventListener('click', function (e) {
e.preventDefault();
// Remove existing popover
if (element._popover) {
element._popover.remove();
element._popover = null;
return;
}
const popover = document.createElement('div');
popover.className = 'custom-popover';
popover.innerHTML = `
${popoverTitle ? `<div class="popover-title">${popoverTitle}</div>` : ''}
<div class="popover-content">${popoverContent}</div>
`;
popover.style.cssText = `
position: absolute;
background: #fff;
border: 1px solid #ccc;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
z-index: 1050;
min-width: 200px;
max-width: 300px;
`;
document.body.appendChild(popover);
const rect = element.getBoundingClientRect();
popover.style.left = `${rect.left}px`;
popover.style.top = `${rect.bottom + 5}px`;
element._popover = popover;
});
}
});
}
// Initialize both
initTooltips();
initPopovers();
// Close popovers when clicking outside
document.addEventListener('click', function (e) {
const popovers = document.querySelectorAll('.custom-popover');
popovers.forEach(popover => {
if (!popover.contains(e.target)) {
popover.remove();
}
});
});
})());
;// ../../node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js
/*!
* perfect-scrollbar v1.5.6
* Copyright 2024 Hyunje Jun, MDBootstrap and Contributors
* Licensed under MIT
*/
function get(element) {
return getComputedStyle(element);
}
function perfect_scrollbar_esm_set(element, obj) {
for (var key in obj) {
var val = obj[key];
if (typeof val === 'number') {
val = val + "px";
}
element.style[key] = val;
}
return element;
}
function div(className) {
var div = document.createElement('div');
div.className = className;
return div;
}
var elMatches =
typeof Element !== 'undefined' &&
(Element.prototype.matches ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector);
function matches(element, query) {
if (!elMatches) {
throw new Error('No element matching method supported');
}
return elMatches.call(element, query);
}
function remove(element) {
if (element.remove) {
element.remove();
} else {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
}
function queryChildren(element, selector) {
return Array.prototype.filter.call(element.children, function (child) { return matches(child, selector); }
);
}
var cls = {
main: 'ps',
rtl: 'ps__rtl',
element: {
thumb: function (x) { return ("ps__thumb-" + x); },
rail: function (x) { return ("ps__rail-" + x); },
consuming: 'ps__child--consume',
},
state: {
focus: 'ps--focus',
clicking: 'ps--clicking',
active: function (x) { return ("ps--active-" + x); },
scrolling: function (x) { return ("ps--scrolling-" + x); },
},
};
/*
* Helper methods
*/
var scrollingClassTimeout = { x: null, y: null };
function addScrollingClass(i, x) {
var classList = i.element.classList;
var className = cls.state.scrolling(x);
if (classList.contains(className)) {
clearTimeout(scrollingClassTimeout[x]);
} else {
classList.add(className);
}
}
function removeScrollingClass(i, x) {
scrollingClassTimeout[x] = setTimeout(
function () { return i.isAlive && i.element.classList.remove(cls.state.scrolling(x)); },
i.settings.scrollingThreshold
);
}
function setScrollingClassInstantly(i, x) {
addScrollingClass(i, x);
removeScrollingClass(i, x);
}
var EventElement = function EventElement(element) {
this.element = element;
this.handlers = {};
};
var prototypeAccessors = { isEmpty: { configurable: true } };
EventElement.prototype.bind = function bind (eventName, handler) {
if (typeof this.handlers[eventName] === 'undefined') {
this.handlers[eventName] = [];
}
this.handlers[eventName].push(handler);
this.element.addEventListener(eventName, handler, false);
};
EventElement.prototype.unbind = function unbind (eventName, target) {
var this$1 = this;
this.handlers[eventName] = this.handlers[eventName].filter(function (handler) {
if (target && handler !== target) {
return true;
}
this$1.element.removeEventListener(eventName, handler, false);
return false;
});
};
EventElement.prototype.unbindAll = function unbindAll () {
for (var name in this.handlers) {
this.unbind(name);
}
};
prototypeAccessors.isEmpty.get = function () {
var this$1 = this;
return Object.keys(this.handlers).every(
function (key) { return this$1.handlers[key].length === 0; }
);
};
Object.defineProperties( EventElement.prototype, prototypeAccessors );
var EventManager = function EventManager() {
this.eventElements = [];
};
EventManager.prototype.eventElement = function eventElement (element) {
var ee = this.eventElements.filter(function (ee) { return ee.element === element; })[0];
if (!ee) {
ee = new EventElement(element);
this.eventElements.push(ee);
}
return ee;
};
EventManager.prototype.bind = function bind (element, eventName, handler) {
this.eventElement(element).bind(eventName, handler);
};
EventManager.prototype.unbind = function unbind (element, eventName, handler) {
var ee = this.eventElement(element);
ee.unbind(eventName, handler);
if (ee.isEmpty) {
// remove
this.eventElements.splice(this.eventElements.indexOf(ee), 1);
}
};
EventManager.prototype.unbindAll = function unbindAll () {
this.eventElements.forEach(function (e) { return e.unbindAll(); });
this.eventElements = [];
};
EventManager.prototype.once = function once (element, eventName, handler) {
var ee = this.eventElement(element);
var onceHandler = function (evt) {
ee.unbind(eventName, onceHandler);
handler(evt);
};
ee.bind(eventName, onceHandler);
};
function createEvent(name) {
if (typeof window.CustomEvent === 'function') {
return new CustomEvent(name);
}
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(name, false, false, undefined);
return evt;
}
function processScrollDiff (i, axis, diff, useScrollingClass, forceFireReachEvent) {
if ( useScrollingClass === void 0 ) useScrollingClass = true;
if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false;
var fields;
if (axis === 'top') {
fields = ['contentHeight', 'containerHeight', 'scrollTop', 'y', 'up', 'down'];
} else if (axis === 'left') {
fields = ['contentWidth', 'containerWidth', 'scrollLeft', 'x', 'left', 'right'];
} else {
throw new Error('A proper axis should be provided');
}
processScrollDiff$1(i, diff, fields, useScrollingClass, forceFireReachEvent);
}
function processScrollDiff$1(
i,
diff,
ref,
useScrollingClass,
forceFireReachEvent
) {
var contentHeight = ref[0];
var containerHeight = ref[1];
var scrollTop = ref[2];
var y = ref[3];
var up = ref[4];
var down = ref[5];
if ( useScrollingClass === void 0 ) useScrollingClass = true;
if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false;
var element = i.element;
// reset reach
i.reach[y] = null;
// 1 for subpixel rounding
if (element[scrollTop] < 1) {
i.reach[y] = 'start';
}
// 1 for subpixel rounding
if (element[scrollTop] > i[contentHeight] - i[containerHeight] - 1) {
i.reach[y] = 'end';
}
if (diff) {
element.dispatchEvent(createEvent(("ps-scroll-" + y)));
if (diff < 0) {
element.dispatchEvent(createEvent(("ps-scroll-" + up)));
} else if (diff > 0) {
element.dispatchEvent(createEvent(("ps-scroll-" + down)));
}
if (useScrollingClass) {
setScrollingClassInstantly(i, y);
}
}
if (i.reach[y] && (diff || forceFireReachEvent)) {
element.dispatchEvent(createEvent(("ps-" + y + "-reach-" + (i.reach[y]))));
}
}
function toInt(x) {
return parseInt(x, 10) || 0;
}
function isEditable(el) {
return (
matches(el, 'input,[contenteditable]') ||
matches(el, 'select,[contenteditable]') ||
matches(el, 'textarea,[contenteditable]') ||
matches(el, 'button,[contenteditable]')
);
}
function perfect_scrollbar_esm_outerWidth(element) {
var styles = get(element);
return (
toInt(styles.width) +
toInt(styles.paddingLeft) +
toInt(styles.paddingRight) +
toInt(styles.borderLeftWidth) +
toInt(styles.borderRightWidth)
);
}
var env = {
isWebKit:
typeof document !== 'undefined' &&
'WebkitAppearance' in document.documentElement.style,
supportsTouch:
typeof window !== 'undefined' &&
('ontouchstart' in window ||
('maxTouchPoints' in window.navigator &&
window.navigator.maxTouchPoints > 0) ||
(window.DocumentTouch && document instanceof window.DocumentTouch)),
supportsIePointer:
typeof navigator !== 'undefined' && navigator.msMaxTouchPoints,
isChrome:
typeof navigator !== 'undefined' &&
/Chrome/i.test(navigator && navigator.userAgent),
};
/* eslint-disable no-lonely-if */
function updateGeometry (i) {
var element = i.element;
var roundedScrollTop = Math.floor(element.scrollTop);
var rect = element.getBoundingClientRect();
i.containerWidth = Math.floor(rect.width);
i.containerHeight = Math.floor(rect.height);
i.contentWidth = element.scrollWidth;
i.contentHeight = element.scrollHeight;
if (!element.contains(i.scrollbarXRail)) {
// clean up and append
queryChildren(element, cls.element.rail('x')).forEach(function (el) { return remove(el); });
element.appendChild(i.scrollbarXRail);
}
if (!element.contains(i.scrollbarYRail)) {
// clean up and append
queryChildren(element, cls.element.rail('y')).forEach(function (el) { return remove(el); });
element.appendChild(i.scrollbarYRail);
}
if (
!i.settings.suppressScrollX &&
i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth
) {
i.scrollbarXActive = true;
i.railXWidth = i.containerWidth - i.railXMarginWidth;
i.railXRatio = i.containerWidth / i.railXWidth;
i.scrollbarXWidth = getThumbSize(i, toInt((i.railXWidth * i.containerWidth) / i.contentWidth));
i.scrollbarXLeft = toInt(
((i.negativeScrollAdjustment + element.scrollLeft) * (i.railXWidth - i.scrollbarXWidth)) /
(i.contentWidth - i.containerWidth)
);
} else {
i.scrollbarXActive = false;
}
if (
!i.settings.suppressScrollY &&
i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight
) {
i.scrollbarYActive = true;
i.railYHeight = i.containerHeight - i.railYMarginHeight;
i.railYRatio = i.containerHeight / i.railYHeight;
i.scrollbarYHeight = getThumbSize(
i,
toInt((i.railYHeight * i.containerHeight) / i.contentHeight)
);
i.scrollbarYTop = toInt(
(roundedScrollTop * (i.railYHeight - i.scrollbarYHeight)) /
(i.contentHeight - i.containerHeight)
);
} else {
i.scrollbarYActive = false;
}
if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
}
if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
}
updateCss(element, i);
if (i.scrollbarXActive) {
element.classList.add(cls.state.active('x'));
} else {
element.classList.remove(cls.state.active('x'));
i.scrollbarXWidth = 0;
i.scrollbarXLeft = 0;
element.scrollLeft = i.isRtl === true ? i.contentWidth : 0;
}
if (i.scrollbarYActive) {
element.classList.add(cls.state.active('y'));
} else {
element.classList.remove(cls.state.active('y'));
i.scrollbarYHeight = 0;
i.scrollbarYTop = 0;
element.scrollTop = 0;
}
}
function getThumbSize(i, thumbSize) {
if (i.settings.minScrollbarLength) {
thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
}
if (i.settings.maxScrollbarLength) {
thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
}
return thumbSize;
}
function updateCss(element, i) {
var xRailOffset = { width: i.railXWidth };
var roundedScrollTop = Math.floor(element.scrollTop);
if (i.isRtl) {
xRailOffset.left =
i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth - i.contentWidth;
} else {
xRailOffset.left = element.scrollLeft;
}
if (i.isScrollbarXUsingBottom) {
xRailOffset.bottom = i.scrollbarXBottom - roundedScrollTop;
} else {
xRailOffset.top = i.scrollbarXTop + roundedScrollTop;
}
perfect_scrollbar_esm_set(i.scrollbarXRail, xRailOffset);
var yRailOffset = { top: roundedScrollTop, height: i.railYHeight };
if (i.isScrollbarYUsingRight) {
if (i.isRtl) {
yRailOffset.right =
i.contentWidth -
(i.negativeScrollAdjustment + element.scrollLeft) -
i.scrollbarYRight -
i.scrollbarYOuterWidth -
9;
} else {
yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
}
} else {
if (i.isRtl) {
yRailOffset.left =
i.negativeScrollAdjustment +
element.scrollLeft +
i.containerWidth * 2 -
i.contentWidth -
i.scrollbarYLeft -
i.scrollbarYOuterWidth;
} else {
yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
}
}
perfect_scrollbar_esm_set(i.scrollbarYRail, yRailOffset);
perfect_scrollbar_esm_set(i.scrollbarX, {
left: i.scrollbarXLeft,
width: i.scrollbarXWidth - i.railBorderXWidth,
});
perfect_scrollbar_esm_set(i.scrollbarY, {
top: i.scrollbarYTop,
height: i.scrollbarYHeight - i.railBorderYWidth,
});
}
/* eslint-disable */
function clickRail (i) {
// const element = i.element;
i.event.bind(i.scrollbarY, 'mousedown', function (e) { return e.stopPropagation(); });
i.event.bind(i.scrollbarYRail, 'mousedown', function (e) {
var positionTop = e.pageY - window.pageYOffset - i.scrollbarYRail.getBoundingClientRect().top;
var direction = positionTop > i.scrollbarYTop ? 1 : -1;
i.element.scrollTop += direction * i.containerHeight;
updateGeometry(i);
e.stopPropagation();
});
i.event.bind(i.scrollbarX, 'mousedown', function (e) { return e.stopPropagation(); });
i.event.bind(i.scrollbarXRail, 'mousedown', function (e) {
var positionLeft =
e.pageX - window.pageXOffset - i.scrollbarXRail.getBoundingClientRect().left;
var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;
i.element.scrollLeft += direction * i.containerWidth;
updateGeometry(i);
e.stopPropagation();
});
}
var activeSlider = null; // Variable to track the currently active slider
function setupScrollHandlers(i) {
bindMouseScrollHandler(i, [
'containerHeight',
'contentHeight',
'pageY',
'railYHeight',
'scrollbarY',
'scrollbarYHeight',
'scrollTop',
'y',
'scrollbarYRail' ]);
bindMouseScrollHandler(i, [
'containerWidth',
'contentWidth',
'pageX',
'railXWidth',
'scrollbarX',
'scrollbarXWidth',
'scrollLeft',
'x',
'scrollbarXRail' ]);
}
function bindMouseScrollHandler(
i,
ref
) {
var containerDimension = ref[0];
var contentDimension = ref[1];
var pageAxis = ref[2];
var railDimension = ref[3];
var scrollbarAxis = ref[4];
var scrollbarDimension = ref[5];
var scrollAxis = ref[6];
var axis = ref[7];
var scrollbarRail = ref[8];
var element = i.element;
var startingScrollPosition = null;
var startingMousePagePosition = null;
var scrollBy = null;
function moveHandler(e) {
if (e.touches && e.touches[0]) {
e[pageAxis] = e.touches[0][("page" + (axis.toUpperCase()))];
}
// Only move if the active slider is the one we started with
if (activeSlider === scrollbarAxis) {
element[scrollAxis] =
startingScrollPosition + scrollBy * (e[pageAxis] - startingMousePagePosition);
addScrollingClass(i, axis);
updateGeometry(i);
e.stopPropagation();
e.preventDefault();
}
}
function endHandler() {
removeScrollingClass(i, axis);
i[scrollbarRail].classList.remove(cls.state.clicking);
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', endHandler);
document.removeEventListener('touchmove', moveHandler);
document.removeEventListener('touchend', endHandler);
activeSlider = null; // Reset active slider when interaction ends
}
function bindMoves(e) {
if (activeSlider === null) {
// Only bind if no slider is currently active
activeSlider = scrollbarAxis; // Set current slider as active
startingScrollPosition = element[scrollAxis];
if (e.touches) {
e[pageAxis] = e.touches[0][("page" + (axis.toUpperCase()))];
}
startingMousePagePosition = e[pageAxis];
scrollBy =
(i[contentDimension] - i[containerDimension]) / (i[railDimension] - i[scrollbarDimension]);
if (!e.touches) {
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', endHandler);
} else {
document.addEventListener('touchmove', moveHandler, { passive: false });
document.addEventListener('touchend', endHandler);
}
i[scrollbarRail].classList.add(cls.state.clicking);
}
e.stopPropagation();
if (e.cancelable) {
e.preventDefault();
}
}
i[scrollbarAxis].addEventListener('mousedown', bindMoves);
i[scrollbarAxis].addEventListener('touchstart', bindMoves);
}
/* eslint-disable */
function keyboard (i) {
var element = i.element;
var elementHovered = function () { return matches(element, ':hover'); };
var scrollbarFocused = function () { return matches(i.scrollbarX, ':focus') || matches(i.scrollbarY, ':focus'); };
function shouldPreventDefault(deltaX, deltaY) {
var scrollTop = Math.floor(element.scrollTop);
if (deltaX === 0) {
if (!i.scrollbarYActive) {
return false;
}
if (
(scrollTop === 0 && deltaY > 0) ||
(scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)
) {
return !i.settings.wheelPropagation;
}
}
var scrollLeft = element.scrollLeft;
if (deltaY === 0) {
if (!i.scrollbarXActive) {
return false;
}
if (
(scrollLeft === 0 && deltaX < 0) ||
(scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)
) {
return !i.settings.wheelPropagation;
}
}
return true;
}
i.event.bind(i.ownerDocument, 'keydown', function (e) {
if ((e.isDefaultPrevented && e.isDefaultPrevented()) || e.defaultPrevented) {
return;
}
if (!elementHovered() && !scrollbarFocused()) {
return;
}
var activeElement = document.activeElement
? document.activeElement
: i.ownerDocument.activeElement;
if (activeElement) {
if (activeElement.tagName === 'IFRAME') {
activeElement = activeElement.contentDocument.activeElement;
} else {
// go deeper if element is a webcomponent
while (activeElement.shadowRoot) {
activeElement = activeElement.shadowRoot.activeElement;
}
}
if (isEditable(activeElement)) {
return;
}
}
var deltaX = 0;
var deltaY = 0;
switch (e.which) {
case 37: // left
if (e.metaKey) {
deltaX = -i.contentWidth;
} else if (e.altKey) {
deltaX = -i.containerWidth;
} else {
deltaX = -30;
}
break;
case 38: // up
if (e.metaKey) {
deltaY = i.contentHeight;
} else if (e.altKey) {
deltaY = i.containerHeight;
} else {
deltaY = 30;
}
break;
case 39: // right
if (e.metaKey) {
deltaX = i.contentWidth;
} else if (e.altKey) {
deltaX = i.containerWidth;
} else {
deltaX = 30;
}
break;
case 40: // down
if (e.metaKey) {
deltaY = -i.contentHeight;
} else if (e.altKey) {
deltaY = -i.containerHeight;
} else {
deltaY = -30;
}
break;
case 32: // space bar
if (e.shiftKey) {
deltaY = i.containerHeight;
} else {
deltaY = -i.containerHeight;
}
break;
case 33: // page up
deltaY = i.containerHeight;
break;
case 34: // page down
deltaY = -i.containerHeight;
break;
case 36: // home
deltaY = i.contentHeight;
break;
case 35: // end
deltaY = -i.contentHeight;
break;
default:
return;
}
if (i.settings.suppressScrollX && deltaX !== 0) {
return;
}
if (i.settings.suppressScrollY && deltaY !== 0) {
return;
}
element.scrollTop -= deltaY;
element.scrollLeft += deltaX;
updateGeometry(i);
if (shouldPreventDefault(deltaX, deltaY)) {
e.preventDefault();
}
});
}
/* eslint-disable */
function wheel (i) {
var element = i.element;
function shouldPreventDefault(deltaX, deltaY) {
var roundedScrollTop = Math.floor(element.scrollTop);
var isTop = element.scrollTop === 0;
var isBottom = roundedScrollTop + element.offsetHeight === element.scrollHeight;
var isLeft = element.scrollLeft === 0;
var isRight = element.scrollLeft + element.offsetWidth === element.scrollWidth;
var hitsBound;
// pick axis with primary direction
if (Math.abs(deltaY) > Math.abs(deltaX)) {
hitsBound = isTop || isBottom;
} else {
hitsBound = isLeft || isRight;
}
return hitsBound ? !i.settings.wheelPropagation : true;
}
function getDeltaFromEvent(e) {
var deltaX = e.deltaX;
var deltaY = -1 * e.deltaY;
if (typeof deltaX === 'undefined' || typeof deltaY === 'undefined') {
// OS X Safari
deltaX = (-1 * e.wheelDeltaX) / 6;
deltaY = e.wheelDeltaY / 6;
}
if (e.deltaMode && e.deltaMode === 1) {
// Firefox in deltaMode 1: Line scrolling
deltaX *= 10;
deltaY *= 10;
}
if (deltaX !== deltaX && deltaY !== deltaY /* NaN checks */) {
// IE in some mouse drivers
deltaX = 0;
deltaY = e.wheelDelta;
}
if (e.shiftKey) {
// reverse axis with shift key
return [-deltaY, -deltaX];
}
return [deltaX, deltaY];
}
function shouldBeConsumedByChild(target, deltaX, deltaY) {
// FIXME: this is a workaround for <select> issue in FF and IE #571
if (!env.isWebKit && element.querySelector('select:focus')) {
return true;
}
if (!element.contains(target)) {
return false;
}
var cursor = target;
while (cursor && cursor !== element) {
if (cursor.classList.contains(cls.element.consuming)) {
return true;
}
var style = get(cursor);
// if deltaY && vertical scrollable
if (deltaY && style.overflowY.match(/(scroll|auto)/)) {
var maxScrollTop = cursor.scrollHeight - cursor.clientHeight;
if (maxScrollTop > 0) {
if (
(cursor.scrollTop > 0 && deltaY < 0) ||
(cursor.scrollTop < maxScrollTop && deltaY > 0)
) {
return true;
}
}
}
// if deltaX && horizontal scrollable
if (deltaX && style.overflowX.match(/(scroll|auto)/)) {
var maxScrollLeft = cursor.scrollWidth - cursor.clientWidth;
if (maxScrollLeft > 0) {
if (
(cursor.scrollLeft > 0 && deltaX < 0) ||
(cursor.scrollLeft < maxScrollLeft && deltaX > 0)
) {
return true;
}
}
}
cursor = cursor.parentNode;
}
return false;
}
function mousewheelHandler(e) {
var ref = getDeltaFromEvent(e);
var deltaX = ref[0];
var deltaY = ref[1];
if (shouldBeConsumedByChild(e.target, deltaX, deltaY)) {
return;
}
var shouldPrevent = false;
if (!i.settings.useBothWheelAxes) {
// deltaX will only be used for horizontal scrolling and deltaY will
// only be used for vertical scrolling - this is the default
element.scrollTop -= deltaY * i.settings.wheelSpeed;
element.scrollLeft += deltaX * i.settings.wheelSpeed;
} else if (i.scrollbarYActive && !i.scrollbarXActive) {
// only vertical scrollbar is active and useBothWheelAxes option is
// active, so let's scroll vertical bar using both mouse wheel axes
if (deltaY) {
element.scrollTop -= deltaY * i.settings.wheelSpeed;
} else {
element.scrollTop += deltaX * i.settings.wheelSpeed;
}
shouldPrevent = true;
} else if (i.scrollbarXActive && !i.scrollbarYActive) {
// useBothWheelAxes and only horizontal bar is active, so use both
// wheel axes for horizontal bar
if (deltaX) {
element.scrollLeft += deltaX * i.settings.wheelSpeed;
} else {
element.scrollLeft -= deltaY * i.settings.wheelSpeed;
}
shouldPrevent = true;
}
updateGeometry(i);
shouldPrevent = shouldPrevent || shouldPreventDefault(deltaX, deltaY);
if (shouldPrevent && !e.ctrlKey) {
e.stopPropagation();
e.preventDefault();
}
}
if (typeof window.onwheel !== 'undefined') {
i.event.bind(element, 'wheel', mousewheelHandler);
} else if (typeof window.onmousewheel !== 'undefined') {
i.event.bind(element, 'mousewheel', mousewheelHandler);
}
}
function touch (i) {
if (!env.supportsTouch && !env.supportsIePointer) {
return;
}
var element = i.element;
var state = {
startOffset: {},
startTime: 0,
speed: {},
easingLoop: null,
};
function shouldPrevent(deltaX, deltaY) {
var scrollTop = Math.floor(element.scrollTop);
var scrollLeft = element.scrollLeft;
var magnitudeX = Math.abs(deltaX);
var magnitudeY = Math.abs(deltaY);
if (magnitudeY > magnitudeX) {
// user is perhaps trying to swipe up/down the page
if (
(deltaY < 0 && scrollTop === i.contentHeight - i.containerHeight) ||
(deltaY > 0 && scrollTop === 0)
) {
// set prevent for mobile Chrome refresh
return window.scrollY === 0 && deltaY > 0 && env.isChrome;
}
} else if (magnitudeX > magnitudeY) {
// user is perhaps trying to swipe left/right across the page
if (
(deltaX < 0 && scrollLeft === i.contentWidth - i.containerWidth) ||
(deltaX > 0 && scrollLeft === 0)
) {
return true;
}
}
return true;
}
function applyTouchMove(differenceX, differenceY) {
element.scrollTop -= differenceY;
element.scrollLeft -= differenceX;
updateGeometry(i);
}
function getTouch(e) {
if (e.targetTouches) {
return e.targetTouches[0];
}
// Maybe IE pointer
return e;
}
function shouldHandle(e) {
if (e.target === i.scrollbarX || e.target === i.scrollbarY) {
return false;
}
if (e.pointerType && e.pointerType === 'pen' && e.buttons === 0) {
return false;
}
if (e.targetTouches && e.targetTouches.length === 1) {
return true;
}
if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
return true;
}
return false;
}
function touchStart(e) {
if (!shouldHandle(e)) {
return;
}
var touch = getTouch(e);
state.startOffset.pageX = touch.pageX;
state.startOffset.pageY = touch.pageY;
state.startTime = new Date().getTime();
if (state.easingLoop !== null) {
clearInterval(state.easingLoop);
}
}
function shouldBeConsumedByChild(target, deltaX, deltaY) {
if (!element.contains(target)) {
return false;
}
var cursor = target;
while (cursor && cursor !== element) {
if (cursor.classList.contains(cls.element.consuming)) {
return true;
}
var style = get(cursor);
// if deltaY && vertical scrollable
if (deltaY && style.overflowY.match(/(scroll|auto)/)) {
var maxScrollTop = cursor.scrollHeight - cursor.clientHeight;
if (maxScrollTop > 0) {
if (
(cursor.scrollTop > 0 && deltaY < 0) ||
(cursor.scrollTop < maxScrollTop && deltaY > 0)
) {
return true;
}
}
}
// if deltaX && horizontal scrollable
if (deltaX && style.overflowX.match(/(scroll|auto)/)) {
var maxScrollLeft = cursor.scrollWidth - cursor.clientWidth;
if (maxScrollLeft > 0) {
if (
(cursor.scrollLeft > 0 && deltaX < 0) ||
(cursor.scrollLeft < maxScrollLeft && deltaX > 0)
) {
return true;
}
}
}
cursor = cursor.parentNode;
}
return false;
}
function touchMove(e) {
if (shouldHandle(e)) {
var touch = getTouch(e);
var currentOffset = { pageX: touch.pageX, pageY: touch.pageY };
var differenceX = currentOffset.pageX - state.startOffset.pageX;
var differenceY = currentOffset.pageY - state.startOffset.pageY;
if (shouldBeConsumedByChild(e.target, differenceX, differenceY)) {
return;
}
applyTouchMove(differenceX, differenceY);
state.startOffset = currentOffset;
var currentTime = new Date().getTime();
var timeGap = currentTime - state.startTime;
if (timeGap > 0) {
state.speed.x = differenceX / timeGap;
state.speed.y = differenceY / timeGap;
state.startTime = currentTime;
}
if (shouldPrevent(differenceX, differenceY)) {
// Prevent the default behavior if the event is cancelable
if (e.cancelable) {
e.preventDefault();
}
}
}
}
function touchEnd() {
if (i.settings.swipeEasing) {
clearInterval(state.easingLoop);
state.easingLoop = setInterval(function () {
if (i.isInitialized) {
clearInterval(state.easingLoop);
return;
}
if (!state.speed.x && !state.speed.y) {
clearInterval(state.easingLoop);
return;
}
if (Math.abs(state.speed.x) < 0.01 && Math.abs(state.speed.y) < 0.01) {
clearInterval(state.easingLoop);
return;
}
applyTouchMove(state.speed.x * 30, state.speed.y * 30);
state.speed.x *= 0.8;
state.speed.y *= 0.8;
}, 10);
}
}
if (env.supportsTouch) {
i.event.bind(element, 'touchstart', touchStart);
i.event.bind(element, 'touchmove', touchMove);
i.event.bind(element, 'touchend', touchEnd);
} else if (env.supportsIePointer) {
if (window.PointerEvent) {
i.event.bind(element, 'pointerdown', touchStart);
i.event.bind(element, 'pointermove', touchMove);
i.event.bind(element, 'pointerup', touchEnd);
} else if (window.MSPointerEvent) {
i.event.bind(element, 'MSPointerDown', touchStart);
i.event.bind(element, 'MSPointerMove', touchMove);
i.event.bind(element, 'MSPointerUp', touchEnd);
}
}
}
/* eslint-disable */
var defaultSettings = function () { return ({
handlers: ['click-rail', 'drag-thumb', 'keyboard', 'wheel', 'touch'],
maxScrollbarLength: null,
minScrollbarLength: null,
scrollingThreshold: 1000,
scrollXMarginOffset: 0,
scrollYMarginOffset: 0,
suppressScrollX: false,
suppressScrollY: false,
swipeEasing: true,
useBothWheelAxes: false,
wheelPropagation: true,
wheelSpeed: 1,
}); };
var handlers = {
'click-rail': clickRail,
'drag-thumb': setupScrollHandlers,
keyboard: keyboard,
wheel: wheel,
touch: touch,
};
var PerfectScrollbar = function PerfectScrollbar(element, userSettings) {
var this$1 = this;
if ( userSettings === void 0 ) userSettings = {};
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element || !element.nodeName) {
throw new Error('no element is specified to initialize PerfectScrollbar');
}
this.element = element;
element.classList.add(cls.main);
this.settings = defaultSettings();
for (var key in userSettings) {
this.settings[key] = userSettings[key];
}
this.containerWidth = null;
this.containerHeight = null;
this.contentWidth = null;
this.contentHeight = null;
var focus = function () { return element.classList.add(cls.state.focus); };
var blur = function () { return element.classList.remove(cls.state.focus); };
this.isRtl = get(element).direction === 'rtl';
if (this.isRtl === true) {
element.classList.add(cls.rtl);
}
this.isNegativeScroll = (function () {
var originalScrollLeft = element.scrollLeft;
var result = null;
element.scrollLeft = -1;
result = element.scrollLeft < 0;
element.scrollLeft = originalScrollLeft;
return result;
})();
this.negativeScrollAdjustment = this.isNegativeScroll
? element.scrollWidth - element.clientWidth
: 0;
this.event = new EventManager();
this.ownerDocument = element.ownerDocument || document;
this.scrollbarXRail = div(cls.element.rail('x'));
element.appendChild(this.scrollbarXRail);
this.scrollbarX = div(cls.element.thumb('x'));
this.scrollbarXRail.appendChild(this.scrollbarX);
this.scrollbarX.setAttribute('tabindex', 0);
this.event.bind(this.scrollbarX, 'focus', focus);
this.event.bind(this.scrollbarX, 'blur', blur);
this.scrollbarXActive = null;
this.scrollbarXWidth = null;
this.scrollbarXLeft = null;
var railXStyle = get(this.scrollbarXRail);
this.scrollbarXBottom = parseInt(railXStyle.bottom, 10);
if (isNaN(this.scrollbarXBottom)) {
this.isScrollbarXUsingBottom = false;
this.scrollbarXTop = toInt(railXStyle.top);
} else {
this.isScrollbarXUsingBottom = true;
}
this.railBorderXWidth = toInt(railXStyle.borderLeftWidth) + toInt(railXStyle.borderRightWidth);
// Set rail to display:block to calculate margins
perfect_scrollbar_esm_set(this.scrollbarXRail, { display: 'block' });
this.railXMarginWidth = toInt(railXStyle.marginLeft) + toInt(railXStyle.marginRight);
perfect_scrollbar_esm_set(this.scrollbarXRail, { display: '' });
this.railXWidth = null;
this.railXRatio = null;
this.scrollbarYRail = div(cls.element.rail('y'));
element.appendChild(this.scrollbarYRail);
this.scrollbarY = div(cls.element.thumb('y'));
this.scrollbarYRail.appendChild(this.scrollbarY);
this.scrollbarY.setAttribute('tabindex', 0);
this.event.bind(this.scrollbarY, 'focus', focus);
this.event.bind(this.scrollbarY, 'blur', blur);
this.scrollbarYActive = null;
this.scrollbarYHeight = null;
this.scrollbarYTop = null;
var railYStyle = get(this.scrollbarYRail);
this.scrollbarYRight = parseInt(railYStyle.right, 10);
if (isNaN(this.scrollbarYRight)) {
this.isScrollbarYUsingRight = false;
this.scrollbarYLeft = toInt(railYStyle.left);
} else {
this.isScrollbarYUsingRight = true;
}
this.scrollbarYOuterWidth = this.isRtl ? perfect_scrollbar_esm_outerWidth(this.scrollbarY) : null;
this.railBorderYWidth = toInt(railYStyle.borderTopWidth) + toInt(railYStyle.borderBottomWidth);
perfect_scrollbar_esm_set(this.scrollbarYRail, { display: 'block' });
this.railYMarginHeight = toInt(railYStyle.marginTop) + toInt(railYStyle.marginBottom);
perfect_scrollbar_esm_set(this.scrollbarYRail, { display: '' });
this.railYHeight = null;
this.railYRatio = null;
this.reach = {
x:
element.scrollLeft <= 0
? 'start'
: element.scrollLeft >= this.contentWidth - this.containerWidth
? 'end'
: null,
y:
element.scrollTop <= 0
? 'start'
: element.scrollTop >= this.contentHeight - this.containerHeight
? 'end'
: null,
};
this.isAlive = true;
this.settings.handlers.forEach(function (handlerName) { return handlers[handlerName](this$1); });
this.lastScrollTop = Math.floor(element.scrollTop); // for onScroll only
this.lastScrollLeft = element.scrollLeft; // for onScroll only
this.event.bind(this.element, 'scroll', function (e) { return this$1.onScroll(e); });
updateGeometry(this);
};
PerfectScrollbar.prototype.update = function update () {
if (!this.isAlive) {
return;
}
// Recalcuate negative scrollLeft adjustment
this.negativeScrollAdjustment = this.isNegativeScroll
? this.element.scrollWidth - this.element.clientWidth
: 0;
// Recalculate rail margins
perfect_scrollbar_esm_set(this.scrollbarXRail, { display: 'block' });
perfect_scrollbar_esm_set(this.scrollbarYRail, { display: 'block' });
this.railXMarginWidth =
toInt(get(this.scrollbarXRail).marginLeft) +
toInt(get(this.scrollbarXRail).marginRight);
this.railYMarginHeight =
toInt(get(this.scrollbarYRail).marginTop) +
toInt(get(this.scrollbarYRail).marginBottom);
// Hide scrollbars not to affect scrollWidth and scrollHeight
perfect_scrollbar_esm_set(this.scrollbarXRail, { display: 'none' });
perfect_scrollbar_esm_set(this.scrollbarYRail, { display: 'none' });
updateGeometry(this);
processScrollDiff(this, 'top', 0, false, true);
processScrollDiff(this, 'left', 0, false, true);
perfect_scrollbar_esm_set(this.scrollbarXRail, { display: '' });
perfect_scrollbar_esm_set(this.scrollbarYRail, { display: '' });
};
PerfectScrollbar.prototype.onScroll = function onScroll (e) {
if (!this.isAlive) {
return;
}
updateGeometry(this);
processScrollDiff(this, 'top', this.element.scrollTop - this.lastScrollTop);
processScrollDiff(this, 'left', this.element.scrollLeft - this.lastScrollLeft);
this.lastScrollTop = Math.floor(this.element.scrollTop);
this.lastScrollLeft = this.element.scrollLeft;
};
PerfectScrollbar.prototype.destroy = function destroy () {
if (!this.isAlive) {
return;
}
this.event.unbindAll();
remove(this.scrollbarX);
remove(this.scrollbarY);
remove(this.scrollbarXRail);
remove(this.scrollbarYRail);
this.removePsClasses();
// unset elements
this.element = null;
this.scrollbarX = null;
this.scrollbarY = null;
this.scrollbarXRail = null;
this.scrollbarYRail = null;
this.isAlive = false;
};
PerfectScrollbar.prototype.removePsClasses = function removePsClasses () {
this.element.className = this.element.className
.split(' ')
.filter(function (name) { return !name.match(/^ps([-_].+|)$/); })
.join(' ');
};
/* harmony default export */ const perfect_scrollbar_esm = (PerfectScrollbar);
//# sourceMappingURL=perfect-scrollbar.esm.js.map
;// ../assets/scripts/scrollbar/index.js
/* harmony default export */ const scrollbar = ((function () {
const scrollables = document.querySelectorAll('.scrollable');
if (scrollables.length > 0) {
scrollables.forEach(el => {
new perfect_scrollbar_esm(el);
});
}
})());
;// ../assets/scripts/search/index.js
/* harmony default export */ const search = ((function () {
const searchToggle = document.querySelector('.search-toggle');
const searchBox = document.querySelector('.search-box');
const searchInput = document.querySelector('.search-input');
const searchInputField = document.querySelector('.search-input input');
if (searchToggle && searchBox && searchInput && searchInputField) {
searchToggle.addEventListener('click', e => {
searchBox.classList.toggle('active');
searchInput.classList.toggle('active');
searchInputField.focus();
e.preventDefault();
});
}
})());
// EXTERNAL MODULE: ../../node_modules/skycons/skycons.js
var skycons = __webpack_require__(658);
var skycons_default = /*#__PURE__*/__webpack_require__.n(skycons);
;// ../assets/scripts/skycons/index.js
const Skycons = skycons_default()(window);
/* harmony default export */ const scripts_skycons = ((function () {
let icons;
const initSkycons = () => {
const skyconsColor = theme.getCSSVar('--skycons-color');
if (icons) {
icons.pause();
icons.remove('all');
}
icons = new Skycons({
'color': skyconsColor
});
const list = ['clear-day', 'clear-night', 'partly-cloudy-day', 'partly-cloudy-night', 'cloudy', 'rain', 'sleet', 'snow', 'wind', 'fog'];
let i = list.length;
while (i--) {
const weatherType = list[i],
elements = document.getElementsByClassName(weatherType);
let j = elements.length;
while (j--) {
icons.set(elements[j], weatherType);
}
}
icons.play();
};
// Initialize skycons
initSkycons();
// Listen for theme changes
window.addEventListener('adminator:themeChanged', initSkycons);
})());
;// ../../node_modules/jsvectormap/dist/jsvectormap.esm.js
/**
* By https://github.com/TehShrike/deepmerge
*/
var isMergeableObject = function isMergeableObject(value) {
return isNonNullObject(value) && !isSpecial(value);
};
function isNonNullObject(value) {
return !!value && typeof value === 'object';
}
function isSpecial(value) {
var stringValue = Object.prototype.toString.call(value);
return stringValue === '[object RegExp]' || stringValue === '[object Date]' || isNode(value) || isReactElement(value);
}
// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
function isReactElement(value) {
return value.$$typeof === REACT_ELEMENT_TYPE;
}
function isNode(value) {
return value instanceof Node;
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {};
}
function cloneUnlessOtherwiseSpecified(value, options) {
return options.clone !== false && options.isMergeableObject(value) ? deepmerge(emptyTarget(value), value, options) : value;
}
function defaultArrayMerge(target, source, options) {
return target.concat(source).map(function (element) {
return cloneUnlessOtherwiseSpecified(element, options);
});
}
function getMergeFunction(key, options) {
if (!options.customMerge) {
return deepmerge;
}
var customMerge = options.customMerge(key);
return typeof customMerge === 'function' ? customMerge : deepmerge;
}
function getEnumerableOwnPropertySymbols(target) {
return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function (symbol) {
return target.propertyIsEnumerable(symbol);
}) : [];
}
function getKeys(target) {
return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target));
}
function propertyIsOnObject(object, property) {
try {
return property in object;
} catch (_) {
return false;
}
}
// Protects from prototype poisoning and unexpected merging up the prototype chain.
function propertyIsUnsafe(target, key) {
return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,
&& !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,
&& Object.propertyIsEnumerable.call(target, key)); // and also unsafe if they're nonenumerable.
}
function mergeObject(target, source, options) {
var destination = {};
if (options.isMergeableObject(target)) {
getKeys(target).forEach(function (key) {
destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
});
}
getKeys(source).forEach(function (key) {
if (propertyIsUnsafe(target, key)) {
return;
}
if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {
destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
} else {
destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
}
});
return destination;
}
var deepmerge = function deepmerge(target, source, options) {
options = options || {};
options.arrayMerge = options.arrayMerge || defaultArrayMerge;
options.isMergeableObject = options.isMergeableObject || isMergeableObject;
// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
// implementations can use it. The caller may not replace it.
options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;
var sourceIsArray = Array.isArray(source);
var targetIsArray = Array.isArray(target);
var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
if (!sourceAndTargetTypesMatch) {
return cloneUnlessOtherwiseSpecified(source, options);
} else if (sourceIsArray) {
return options.arrayMerge(target, source, options);
} else {
return mergeObject(target, source, options);
}
};
/**
* --------------------------------------------------------------------------
* Public Util Api
* --------------------------------------------------------------------------
*/
var getElement = function getElement(selector) {
if (typeof selector === 'object' && typeof selector.nodeType !== 'undefined') {
return selector;
}
if (typeof selector === 'string') {
return document.querySelector(selector);
}
return null;
};
var createElement = function createElement(type, classes, content, html) {
if (html === void 0) {
html = false;
}
var el = document.createElement(type);
if (content) {
el[!html ? 'textContent' : 'innerHTML'] = content;
}
if (classes) {
el.className = classes;
}
return el;
};
var findElement = function findElement(parentElement, selector) {
return Element.prototype.querySelector.call(parentElement, selector);
};
var jsvectormap_esm_removeElement = function removeElement(target) {
target.parentNode.removeChild(target);
};
var isImageUrl = function isImageUrl(url) {
return /\.(jpg|gif|png)$/.test(url);
};
var hyphenate = function hyphenate(string) {
return string.replace(/[\w]([A-Z])/g, function (m) {
return m[0] + "-" + m[1];
}).toLowerCase();
};
var jsvectormap_esm_merge = function merge(target, source, deep) {
if (deep === void 0) {
deep = false;
}
if (deep) {
return deepmerge(target, source);
}
return Object.assign(target, source);
};
var getLineUid = function getLineUid(from, to) {
return from.toLowerCase() + ":to:" + to.toLowerCase();
};
var inherit = function inherit(target, source) {
Object.assign(target.prototype, source);
};
var eventRegistry = {};
var eventUid = 1;
var EventHandler = {
on: function on(element, event, handler, options) {
if (options === void 0) {
options = {};
}
var uid = "jvm:" + event + "::" + eventUid++;
eventRegistry[uid] = {
selector: element,
handler: handler
};
handler._uid = uid;
element.addEventListener(event, handler, options);
},
delegate: function delegate(element, event, selector, handler) {
event = event.split(' ');
event.forEach(function (eventName) {
EventHandler.on(element, eventName, function (e) {
var target = e.target;
if (target.matches(selector)) {
handler.call(target, e);
}
});
});
},
off: function off(element, event, handler) {
var eventType = event.split(':')[1];
element.removeEventListener(eventType, handler);
delete eventRegistry[handler._uid];
},
flush: function flush() {
Object.keys(eventRegistry).forEach(function (event) {
EventHandler.off(eventRegistry[event].selector, event, eventRegistry[event].handler);
});
},
getEventRegistry: function getEventRegistry() {
return eventRegistry;
}
};
function setupContainerEvents() {
var _this = this;
var map = this;
var mouseDown = false;
var oldPageX;
var oldPageY;
if (this.params.draggable) {
EventHandler.on(this.container, 'mousemove', function (e) {
if (!mouseDown) {
return false;
}
map.transX -= (oldPageX - e.pageX) / map.scale;
map.transY -= (oldPageY - e.pageY) / map.scale;
map._applyTransform();
oldPageX = e.pageX;
oldPageY = e.pageY;
});
EventHandler.on(this.container, 'mousedown', function (e) {
mouseDown = true;
oldPageX = e.pageX;
oldPageY = e.pageY;
return false;
});
EventHandler.on(document.body, 'mouseup', function () {
mouseDown = false;
});
}
if (this.params.zoomOnScroll) {
EventHandler.on(this.container, 'wheel', function (event) {
var deltaY = ((event.deltaY || -event.wheelDelta || event.detail) >> 10 || 1) * 75;
var rect = _this.container.getBoundingClientRect();
var offsetX = event.pageX - rect.left - window.scrollX;
var offsetY = event.pageY - rect.top - window.scrollY;
var zoomStep = Math.pow(1 + map.params.zoomOnScrollSpeed / 1000, -1.5 * deltaY);
if (map.tooltip) {
map._tooltip.hide();
}
map._setScale(map.scale * zoomStep, offsetX, offsetY);
event.preventDefault();
});
}
}
var jsvectormap_esm_Events = {
onLoaded: 'map:loaded',
onViewportChange: 'viewport:changed',
onRegionClick: 'region:clicked',
onMarkerClick: 'marker:clicked',
onRegionSelected: 'region:selected',
onMarkerSelected: 'marker:selected',
onRegionTooltipShow: 'region.tooltip:show',
onMarkerTooltipShow: 'marker.tooltip:show',
onDestroyed: 'map:destroyed'
};
var jsvectormap_esm_parseEvent = function parseEvent(map, selector, isTooltip) {
var element = getElement(selector);
var type = element.getAttribute('class').indexOf('jvm-region') === -1 ? 'marker' : 'region';
var isRegion = type === 'region';
var code = isRegion ? element.getAttribute('data-code') : element.getAttribute('data-index');
var event = isRegion ? jsvectormap_esm_Events.onRegionSelected : jsvectormap_esm_Events.onMarkerSelected;
// Init tooltip event
if (isTooltip) {
event = isRegion ? jsvectormap_esm_Events.onRegionTooltipShow : jsvectormap_esm_Events.onMarkerTooltipShow;
}
return {
type: type,
code: code,
event: event,
element: isRegion ? map.regions[code].element : map._markers[code].element,
tooltipText: isRegion ? map._mapData.paths[code].name || '' : map._markers[code].config.name || ''
};
};
function setupElementEvents() {
var map = this;
var container = this.container;
var pageX, pageY, mouseMoved;
EventHandler.on(container, 'mousemove', function (event) {
if (Math.abs(pageX - event.pageX) + Math.abs(pageY - event.pageY) > 2) {
mouseMoved = true;
}
});
// When the mouse is pressed
EventHandler.delegate(container, 'mousedown', '.jvm-element', function (event) {
pageX = event.pageX;
pageY = event.pageY;
mouseMoved = false;
});
// When the mouse is over the region/marker | When the mouse is out the region/marker
EventHandler.delegate(container, 'mouseover mouseout', '.jvm-element', function (event) {
var data = jsvectormap_esm_parseEvent(map, this, true);
var showTooltip = map.params.showTooltip;
if (event.type === 'mouseover') {
data.element.hover(true);
if (showTooltip) {
map._tooltip.text(data.tooltipText);
map._emit(data.event, [event, map._tooltip, data.code]);
if (!event.defaultPrevented) {
map._tooltip.show();
}
}
} else {
data.element.hover(false);
if (showTooltip) {
map._tooltip.hide();
}
}
});
// When the click is released
EventHandler.delegate(container, 'mouseup', '.jvm-element', function (event) {
var data = jsvectormap_esm_parseEvent(map, this);
if (mouseMoved) {
return;
}
if (data.type === 'region' && map.params.regionsSelectable || data.type === 'marker' && map.params.markersSelectable) {
var element = data.element;
// We're checking if regions/markers|SelectableOne option is presented
if (map.params[data.type + "sSelectableOne"]) {
data.type === 'region' ? map.clearSelectedRegions() : map.clearSelectedMarkers();
}
if (data.element.isSelected) {
element.select(false);
} else {
element.select(true);
}
map._emit(data.event, [data.code, element.isSelected, data.type === 'region' ? map.getSelectedRegions() : map.getSelectedMarkers()]);
}
});
// When region/marker is clicked
EventHandler.delegate(container, 'click', '.jvm-element', function (event) {
var _parseEvent = jsvectormap_esm_parseEvent(map, this),
type = _parseEvent.type,
code = _parseEvent.code;
map._emit(type === 'region' ? jsvectormap_esm_Events.onRegionClick : jsvectormap_esm_Events.onMarkerClick, [event, code]);
});
}
function setupZoomButtons() {
var _this = this;
var zoomInOption = this.params.zoomInButton;
var zoomOutOption = this.params.zoomOutButton;
var getZoomButton = function getZoomButton(zoomOption) {
return typeof zoomOption === 'string' ? document.querySelector(zoomOption) : zoomOption;
};
var zoomIn = zoomInOption ? getZoomButton(zoomInOption) : createElement('div', 'jvm-zoom-btn jvm-zoomin', '&#43;', true);
var zoomOut = zoomOutOption ? getZoomButton(zoomOutOption) : createElement('div', 'jvm-zoom-btn jvm-zoomout', '&#x2212', true);
if (!zoomInOption) {
this.container.appendChild(zoomIn);
}
if (!zoomOutOption) {
this.container.appendChild(zoomOut);
}
var handler = function handler(zoomin) {
if (zoomin === void 0) {
zoomin = true;
}
return function () {
return _this._setScale(zoomin ? _this.scale * _this.params.zoomStep : _this.scale / _this.params.zoomStep, _this._width / 2, _this._height / 2, false, _this.params.zoomAnimate);
};
};
EventHandler.on(zoomIn, 'click', handler());
EventHandler.on(zoomOut, 'click', handler(false));
}
function setupContainerTouchEvents() {
var map = this,
touchStartScale,
touchStartDistance,
touchX,
touchY,
centerTouchX,
centerTouchY,
lastTouchesLength;
var handleTouchEvent = function handleTouchEvent(e) {
var touches = e.touches;
var offset, scale, transXOld, transYOld;
if (e.type == 'touchstart') {
lastTouchesLength = 0;
}
if (touches.length == 1) {
if (lastTouchesLength == 1) {
var _map$_tooltip;
transXOld = map.transX;
transYOld = map.transY;
map.transX -= (touchX - touches[0].pageX) / map.scale;
map.transY -= (touchY - touches[0].pageY) / map.scale;
(_map$_tooltip = map._tooltip) == null || _map$_tooltip.hide();
map._applyTransform();
if (transXOld != map.transX || transYOld != map.transY) {
e.preventDefault();
}
}
touchX = touches[0].pageX;
touchY = touches[0].pageY;
} else if (touches.length == 2) {
if (lastTouchesLength == 2) {
var _map$_tooltip2;
scale = Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2)) / touchStartDistance;
map._setScale(touchStartScale * scale, centerTouchX, centerTouchY);
(_map$_tooltip2 = map._tooltip) == null || _map$_tooltip2.hide();
e.preventDefault();
} else {
var rect = map.container.getBoundingClientRect();
offset = {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX
};
if (touches[0].pageX > touches[1].pageX) {
centerTouchX = touches[1].pageX + (touches[0].pageX - touches[1].pageX) / 2;
} else {
centerTouchX = touches[0].pageX + (touches[1].pageX - touches[0].pageX) / 2;
}
if (touches[0].pageY > touches[1].pageY) {
centerTouchY = touches[1].pageY + (touches[0].pageY - touches[1].pageY) / 2;
} else {
centerTouchY = touches[0].pageY + (touches[1].pageY - touches[0].pageY) / 2;
}
centerTouchX -= offset.left;
centerTouchY -= offset.top;
touchStartScale = map.scale;
touchStartDistance = Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2));
}
}
lastTouchesLength = touches.length;
};
EventHandler.on(map.container, 'touchstart', handleTouchEvent);
EventHandler.on(map.container, 'touchmove', handleTouchEvent);
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _assertThisInitialized(e) {
if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return e;
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
function _createForOfIteratorHelperLoose(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (t) return (t = t.call(r)).next.bind(t);
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var o = 0;
return function () {
return o >= r.length ? {
done: !0
} : {
done: !1,
value: r[o++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _inheritsLoose(t, o) {
t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
function _setPrototypeOf(t, e) {
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
return t.__proto__ = e, t;
}, _setPrototypeOf(t, e);
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
var jsvectormap_esm_BaseComponent = /*#__PURE__*/function () {
function BaseComponent() {}
var _proto = BaseComponent.prototype;
_proto.dispose = function dispose() {
if (this._tooltip) {
jsvectormap_esm_removeElement(this._tooltip);
} else {
// @todo: move shape in base component in v2
this.shape.remove();
}
for (var _iterator = _createForOfIteratorHelperLoose(Object.getOwnPropertyNames(this)), _step; !(_step = _iterator()).done;) {
var propertyName = _step.value;
this[propertyName] = null;
}
};
return BaseComponent;
}();
var Interactable = {
getLabelText: function getLabelText(key, label) {
if (!label) {
return;
}
if (typeof label.render === 'function') {
var params = [];
// Pass additional paramater (Marker config object) in case it's a Marker.
if (this.constructor.Name === 'marker') {
params.push(this.getConfig());
}
// Becuase we need to add the key always at the end
params.push(key);
return label.render.apply(this, params);
}
return key;
},
getLabelOffsets: function getLabelOffsets(key, label) {
if (typeof label.offsets === 'function') {
return label.offsets(key);
}
// If offsets are an array of offsets e.g offsets: [ [0, 25], [10, 15] ]
if (Array.isArray(label.offsets)) {
return label.offsets[key];
}
return [0, 0];
},
setStyle: function setStyle(property, value) {
this.shape.setStyle(property, value);
},
remove: function remove() {
this.shape.remove();
if (this.label) this.label.remove();
},
hover: function hover(state) {
this._setStatus('isHovered', state);
},
select: function select(state) {
this._setStatus('isSelected', state);
},
// Private
_setStatus: function _setStatus(property, state) {
this.shape[property] = state;
this.shape.updateStyle();
this[property] = state;
if (this.label) {
this.label[property] = state;
this.label.updateStyle();
}
}
};
var Region = /*#__PURE__*/function (_BaseComponent) {
function Region(_ref) {
var _this;
var map = _ref.map,
code = _ref.code,
path = _ref.path,
style = _ref.style,
label = _ref.label,
labelStyle = _ref.labelStyle,
labelsGroup = _ref.labelsGroup;
_this = _BaseComponent.call(this) || this;
_this._map = map;
_this.shape = _this._createRegion(path, code, style);
var text = _this.getLabelText(code, label);
// If label is passed and render function returns something
if (label && text) {
var bbox = _this.shape.getBBox();
var offsets = _this.getLabelOffsets(code, label);
_this.labelX = bbox.x + bbox.width / 2 + offsets[0];
_this.labelY = bbox.y + bbox.height / 2 + offsets[1];
_this.label = _this._map.canvas.createText({
text: text,
textAnchor: 'middle',
alignmentBaseline: 'central',
dataCode: code,
x: _this.labelX,
y: _this.labelY
}, labelStyle, labelsGroup);
_this.label.addClass('jvm-region jvm-element');
}
return _this;
}
_inheritsLoose(Region, _BaseComponent);
var _proto = Region.prototype;
_proto._createRegion = function _createRegion(path, code, style) {
path = this._map.canvas.createPath({
d: path,
dataCode: code
}, style);
path.addClass('jvm-region jvm-element');
return path;
};
_proto.updateLabelPosition = function updateLabelPosition() {
if (this.label) {
this.label.set({
x: this.labelX * this._map.scale + this._map.transX * this._map.scale,
y: this.labelY * this._map.scale + this._map.transY * this._map.scale
});
}
};
return Region;
}(jsvectormap_esm_BaseComponent);
inherit(Region, Interactable);
function createRegions() {
this._regionLabelsGroup = this._regionLabelsGroup || this.canvas.createGroup('jvm-regions-labels-group');
for (var code in this._mapData.paths) {
var region = new Region({
map: this,
code: code,
path: this._mapData.paths[code].path,
style: jsvectormap_esm_merge({}, this.params.regionStyle),
labelStyle: this.params.regionLabelStyle,
labelsGroup: this._regionLabelsGroup,
label: this.params.labels && this.params.labels.regions
});
this.regions[code] = {
config: this._mapData.paths[code],
element: region
};
}
}
var LINE_CLASS = 'jvm-line';
var Line = /*#__PURE__*/function (_BaseComponent) {
function Line(options, style) {
var _this;
_this = _BaseComponent.call(this) || this;
_this._options = options;
_this._style = {
initial: style
};
_this._draw();
return _this;
}
_inheritsLoose(Line, _BaseComponent);
var _proto = Line.prototype;
_proto.setStyle = function setStyle(property, value) {
this.shape.setStyle(property, value);
};
_proto.getConfig = function getConfig() {
return this._options.config;
};
_proto._draw = function _draw() {
var _this$_options = this._options,
index = _this$_options.index,
group = _this$_options.group,
map = _this$_options.map;
var config = {
d: this._getDAttribute(),
fill: 'none',
dataIndex: index
};
this.shape = map.canvas.createPath(config, this._style, group);
this.shape.addClass(LINE_CLASS);
};
_proto._getDAttribute = function _getDAttribute() {
var _this$_options2 = this._options,
x1 = _this$_options2.x1,
y1 = _this$_options2.y1,
x2 = _this$_options2.x2,
y2 = _this$_options2.y2,
curvature = _this$_options2.curvature;
return "M" + x1 + "," + y1 + this._getQCommand(x1, y1, x2, y2, curvature) + x2 + "," + y2;
};
_proto._getQCommand = function _getQCommand(x1, y1, x2, y2, curvature) {
if (!curvature) {
return ' ';
}
var curveX = (x1 + x2) / 2 + curvature * (y2 - y1);
var curveY = (y1 + y2) / 2 - curvature * (x2 - x1);
return " Q" + curveX + "," + curveY + " ";
};
return Line;
}(jsvectormap_esm_BaseComponent);
var _excluded = ["curvature"],
_excluded2 = ["curvature"];
function createLines(lines) {
var point1 = false,
point2 = false;
var _this$params$lineStyl = this.params.lineStyle,
curvature = _this$params$lineStyl.curvature,
lineStyle = _objectWithoutPropertiesLoose(_this$params$lineStyl, _excluded);
for (var index in lines) {
var lineConfig = lines[index];
for (var _i = 0, _Object$values = Object.values(this._markers); _i < _Object$values.length; _i++) {
var markerConfig = _Object$values[_i].config;
if (markerConfig.name === lineConfig.from) {
point1 = this.getMarkerPosition(markerConfig);
}
if (markerConfig.name === lineConfig.to) {
point2 = this.getMarkerPosition(markerConfig);
}
}
if (point1 !== false && point2 !== false) {
var _ref = lineConfig.style || {},
curvatureOption = _ref.curvature,
style = _objectWithoutPropertiesLoose(_ref, _excluded2);
// Register lines with unique keys
this._lines[getLineUid(lineConfig.from, lineConfig.to)] = new Line({
index: index,
map: this,
group: this._linesGroup,
config: lineConfig,
x1: point1.x,
y1: point1.y,
x2: point2.x,
y2: point2.y,
curvature: curvatureOption == 0 ? 0 : curvatureOption || curvature
}, jsvectormap_esm_merge(lineStyle, style, true));
}
}
}
var NAME = 'marker';
var JVM_PREFIX$1 = 'jvm-';
var MARKER_CLASS = JVM_PREFIX$1 + "element " + JVM_PREFIX$1 + "marker";
var MARKER_LABEL_CLASS = JVM_PREFIX$1 + "element " + JVM_PREFIX$1 + "label";
var Marker = /*#__PURE__*/function (_BaseComponent) {
function Marker(options, style) {
var _this;
_this = _BaseComponent.call(this) || this;
_this._options = options;
_this._style = style;
_this._labelX = null;
_this._labelY = null;
_this._offsets = null;
_this._isImage = !!style.initial.image;
_this._draw();
if (_this._options.label) {
_this._drawLabel();
}
if (_this._isImage) {
_this.updateLabelPosition();
}
return _this;
}
_inheritsLoose(Marker, _BaseComponent);
var _proto = Marker.prototype;
_proto.getConfig = function getConfig() {
return this._options.config;
};
_proto.updateLabelPosition = function updateLabelPosition() {
var map = this._options.map;
if (this.label) {
this.label.set({
x: this._labelX * map.scale + this._offsets[0] + map.transX * map.scale + 5 + (this._isImage ? (this.shape.width || 0) / 2 : this.shape.node.r.baseVal.value),
y: this._labelY * map.scale + map.transY * this._options.map.scale + this._offsets[1]
});
}
};
_proto._draw = function _draw() {
var _this$_options = this._options,
index = _this$_options.index,
map = _this$_options.map,
group = _this$_options.group,
cx = _this$_options.cx,
cy = _this$_options.cy;
var shapeType = this._isImage ? 'createImage' : 'createCircle';
this.shape = map.canvas[shapeType]({
dataIndex: index,
cx: cx,
cy: cy
}, this._style, group);
this.shape.addClass(MARKER_CLASS);
};
_proto._drawLabel = function _drawLabel() {
var _this$_options2 = this._options,
index = _this$_options2.index,
map = _this$_options2.map,
label = _this$_options2.label,
labelsGroup = _this$_options2.labelsGroup,
cx = _this$_options2.cx,
cy = _this$_options2.cy,
config = _this$_options2.config,
isRecentlyCreated = _this$_options2.isRecentlyCreated;
var labelText = this.getLabelText(index, label);
this._labelX = cx / map.scale - map.transX;
this._labelY = cy / map.scale - map.transY;
this._offsets = isRecentlyCreated && config.offsets ? config.offsets : this.getLabelOffsets(index, label);
this.label = map.canvas.createText({
text: labelText,
dataIndex: index,
x: this._labelX,
y: this._labelY,
dy: '0.6ex'
}, map.params.markerLabelStyle, labelsGroup);
this.label.addClass(MARKER_LABEL_CLASS);
if (isRecentlyCreated) {
this.updateLabelPosition();
}
};
return _createClass(Marker, null, [{
key: "Name",
get: function get() {
return NAME;
}
}]);
}(jsvectormap_esm_BaseComponent);
inherit(Marker, Interactable);
function createMarkers(markers, isRecentlyCreated) {
var _this = this;
if (markers === void 0) {
markers = {};
}
if (isRecentlyCreated === void 0) {
isRecentlyCreated = false;
}
var _loop = function _loop() {
var config = markers[index];
var point = _this.getMarkerPosition(config);
var uid = config.coords.join(':');
if (!point) {
return 0; // continue
}
// We're checking if recently created marker does already exist
// If it does we don't need to create it again, so we'll continue
// Becuase we may have more than one marker submitted via `addMarkers` method.
if (isRecentlyCreated) {
if (Object.keys(_this._markers).filter(function (i) {
return _this._markers[i]._uid === uid;
}).length) {
return 0; // continue
}
index = Object.keys(_this._markers).length;
}
var marker = new Marker({
index: index,
map: _this,
label: _this.params.labels && _this.params.labels.markers,
labelsGroup: _this._markerLabelsGroup,
cx: point.x,
cy: point.y,
group: _this._markersGroup,
config: config,
isRecentlyCreated: isRecentlyCreated
}, jsvectormap_esm_merge(_this.params.markerStyle, _extends({}, config.style || {}), true));
// Check for marker duplication
// this is useful when for example: a user clicks a button for creating marker two times
// so it will remove the old one and the new one will take its place.
if (_this._markers[index]) {
_this.removeMarkers([index]);
}
_this._markers[index] = {
_uid: uid,
config: config,
element: marker
};
},
_ret;
for (var index in markers) {
_ret = _loop();
if (_ret === 0) continue;
}
}
var jsvectormap_esm_Legend = /*#__PURE__*/function () {
function Legend(options) {
if (options === void 0) {
options = {};
}
this._options = options;
this._map = this._options.map;
this._series = this._options.series;
this._body = createElement('div', 'jvm-legend');
if (this._options.cssClass) {
this._body.setAttribute('class', this._options.cssClass);
}
if (options.vertical) {
this._map.legendVertical.appendChild(this._body);
} else {
this._map.legendHorizontal.appendChild(this._body);
}
this.render();
}
var _proto = Legend.prototype;
_proto.render = function render() {
var ticks = this._series.scale.getTicks();
this._body.innderHTML = '';
if (this._options.title) {
var legendTitle = createElement('div', 'jvm-legend-title', this._options.title);
this._body.appendChild(legendTitle);
}
for (var i = 0; i < ticks.length; i++) {
var tick = createElement('div', 'jvm-legend-tick');
var sample = createElement('div', 'jvm-legend-tick-sample');
switch (this._series.config.attribute) {
case 'fill':
if (isImageUrl(ticks[i].value)) {
sample.style.background = "url(" + ticks[i].value + ")";
} else {
sample.style.background = ticks[i].value;
}
break;
case 'stroke':
sample.style.background = ticks[i].value;
break;
case 'image':
sample.style.background = "url(" + (typeof ticks[i].value === 'object' ? ticks[i].value.url : ticks[i].value) + ") no-repeat center center";
sample.style.backgroundSize = 'cover';
break;
}
tick.appendChild(sample);
var label = ticks[i].label;
if (this._options.labelRender) {
label = this._options.labelRender(label);
}
var tickText = createElement('div', 'jvm-legend-tick-text', label);
tick.appendChild(tickText);
this._body.appendChild(tick);
}
};
return Legend;
}();
var OrdinalScale = /*#__PURE__*/function () {
function OrdinalScale(scale) {
this._scale = scale;
}
var _proto = OrdinalScale.prototype;
_proto.getValue = function getValue(value) {
return this._scale[value];
};
_proto.getTicks = function getTicks() {
var ticks = [];
for (var key in this._scale) {
ticks.push({
label: key,
value: this._scale[key]
});
}
return ticks;
};
return OrdinalScale;
}();
var Series = /*#__PURE__*/function () {
function Series(config, elements, map) {
if (config === void 0) {
config = {};
}
// Private
this._map = map;
this._elements = elements; // Could be markers or regions
this._values = config.values || {};
// Protected
this.config = config;
this.config.attribute = config.attribute || 'fill';
// Set initial attributes
if (config.attributes) {
this.setAttributes(config.attributes);
}
if (typeof config.scale === 'object') {
this.scale = new OrdinalScale(config.scale);
}
if (this.config.legend) {
this.legend = new jsvectormap_esm_Legend(jsvectormap_esm_merge({
map: this._map,
series: this
}, this.config.legend));
}
this.setValues(this._values);
}
var _proto = Series.prototype;
_proto.setValues = function setValues(values) {
var attrs = {};
for (var key in values) {
if (values[key]) {
attrs[key] = this.scale.getValue(values[key]);
}
}
this.setAttributes(attrs);
};
_proto.setAttributes = function setAttributes(attrs) {
for (var code in attrs) {
if (this._elements[code]) {
this._elements[code].element.setStyle(this.config.attribute, attrs[code]);
}
}
};
_proto.clear = function clear() {
var key,
attrs = {};
for (key in this._values) {
if (this._elements[key]) {
attrs[key] = this._elements[key].element.shape.style.initial[this.config.attribute];
}
}
this.setAttributes(attrs);
this._values = {};
};
return Series;
}();
function createSeries() {
this.series = {
markers: [],
regions: []
};
for (var key in this.params.series) {
for (var i = 0; i < this.params.series[key].length; i++) {
this.series[key][i] = new Series(this.params.series[key][i], key === 'markers' ? this._markers : this.regions, this);
}
}
}
function applyTransform() {
var maxTransX, maxTransY, minTransX, minTransY;
if (this._defaultWidth * this.scale <= this._width) {
maxTransX = (this._width - this._defaultWidth * this.scale) / (2 * this.scale);
minTransX = (this._width - this._defaultWidth * this.scale) / (2 * this.scale);
} else {
maxTransX = 0;
minTransX = (this._width - this._defaultWidth * this.scale) / this.scale;
}
if (this._defaultHeight * this.scale <= this._height) {
maxTransY = (this._height - this._defaultHeight * this.scale) / (2 * this.scale);
minTransY = (this._height - this._defaultHeight * this.scale) / (2 * this.scale);
} else {
maxTransY = 0;
minTransY = (this._height - this._defaultHeight * this.scale) / this.scale;
}
if (this.transY > maxTransY) {
this.transY = maxTransY;
} else if (this.transY < minTransY) {
this.transY = minTransY;
}
if (this.transX > maxTransX) {
this.transX = maxTransX;
} else if (this.transX < minTransX) {
this.transX = minTransX;
}
this.canvas.applyTransformParams(this.scale, this.transX, this.transY);
if (this._markers) {
this._repositionMarkers();
}
if (this._lines) {
this._repositionLines();
}
this._repositionLabels();
}
function resize() {
var curBaseScale = this._baseScale;
if (this._width / this._height > this._defaultWidth / this._defaultHeight) {
this._baseScale = this._height / this._defaultHeight;
this._baseTransX = Math.abs(this._width - this._defaultWidth * this._baseScale) / (2 * this._baseScale);
} else {
this._baseScale = this._width / this._defaultWidth;
this._baseTransY = Math.abs(this._height - this._defaultHeight * this._baseScale) / (2 * this._baseScale);
}
this.scale *= this._baseScale / curBaseScale;
this.transX *= this._baseScale / curBaseScale;
this.transY *= this._baseScale / curBaseScale;
}
function setScale(scale, anchorX, anchorY, isCentered, animate) {
var _this = this;
var zoomStep,
interval,
i = 0,
count = Math.abs(Math.round((scale - this.scale) * 60 / Math.max(scale, this.scale))),
scaleStart,
scaleDiff,
transXStart,
transXDiff,
transYStart,
transYDiff,
transX,
transY;
if (scale > this.params.zoomMax * this._baseScale) {
scale = this.params.zoomMax * this._baseScale;
} else if (scale < this.params.zoomMin * this._baseScale) {
scale = this.params.zoomMin * this._baseScale;
}
if (typeof anchorX != 'undefined' && typeof anchorY != 'undefined') {
zoomStep = scale / this.scale;
if (isCentered) {
transX = anchorX + this._defaultWidth * (this._width / (this._defaultWidth * scale)) / 2;
transY = anchorY + this._defaultHeight * (this._height / (this._defaultHeight * scale)) / 2;
} else {
transX = this.transX - (zoomStep - 1) / scale * anchorX;
transY = this.transY - (zoomStep - 1) / scale * anchorY;
}
}
if (animate && count > 0) {
scaleStart = this.scale;
scaleDiff = (scale - scaleStart) / count;
transXStart = this.transX * this.scale;
transYStart = this.transY * this.scale;
transXDiff = (transX * scale - transXStart) / count;
transYDiff = (transY * scale - transYStart) / count;
interval = setInterval(function () {
i += 1;
_this.scale = scaleStart + scaleDiff * i;
_this.transX = (transXStart + transXDiff * i) / _this.scale;
_this.transY = (transYStart + transYDiff * i) / _this.scale;
_this._applyTransform();
if (i == count) {
clearInterval(interval);
_this._emit(jsvectormap_esm_Events.onViewportChange, [_this.scale, _this.transX, _this.transY]);
}
}, 10);
} else {
this.transX = transX;
this.transY = transY;
this.scale = scale;
this._applyTransform();
this._emit(jsvectormap_esm_Events.onViewportChange, [this.scale, this.transX, this.transY]);
}
}
function setFocus(config) {
var _this = this;
if (config === void 0) {
config = {};
}
var bbox,
codes = [];
if (config.region) {
codes.push(config.region);
} else if (config.regions) {
codes = config.regions;
}
if (codes.length) {
codes.forEach(function (code) {
if (_this.regions[code]) {
var itemBbox = _this.regions[code].element.shape.getBBox();
if (itemBbox) {
// Handle the first loop
if (typeof bbox == 'undefined') {
bbox = itemBbox;
} else {
// get the old bbox properties plus the current
// this kinda incrementing the old values and the new values
bbox = {
x: Math.min(bbox.x, itemBbox.x),
y: Math.min(bbox.y, itemBbox.y),
width: Math.max(bbox.x + bbox.width, itemBbox.x + itemBbox.width) - Math.min(bbox.x, itemBbox.x),
height: Math.max(bbox.y + bbox.height, itemBbox.y + itemBbox.height) - Math.min(bbox.y, itemBbox.y)
};
}
}
}
});
return this._setScale(Math.min(this._width / bbox.width, this._height / bbox.height), -(bbox.x + bbox.width / 2), -(bbox.y + bbox.height / 2), true, config.animate);
} else if (config.coords) {
var point = this.coordsToPoint(config.coords[0], config.coords[1]);
var x = this.transX - point.x / this.scale;
var y = this.transY - point.y / this.scale;
return this._setScale(config.scale * this._baseScale, x, y, true, config.animate);
}
}
function updateSize() {
this._width = this.container.offsetWidth;
this._height = this.container.offsetHeight;
this._resize();
this.canvas.setSize(this._width, this._height);
this._applyTransform();
}
/**
* ------------------------------------------------------------------------
* Object
* ------------------------------------------------------------------------
*/
var Proj = {
/* sgn(n){
if (n > 0) {
return 1;
} else if (n < 0) {
return -1;
} else {
return n;
}
}, */
mill: function mill(lat, lng, c) {
return {
x: this.radius * (lng - c) * this.radDeg,
y: -this.radius * Math.log(Math.tan((45 + 0.4 * lat) * this.radDeg)) / 0.8
};
},
/* mill_inv(x, y, c) {
return {
lat: (2.5 * Math.atan(Math.exp(0.8 * y / this.radius)) - 5 * Math.PI / 8) * this.degRad,
lng: (c * this.radDeg + x / this.radius) * this.degRad
};
}, */
merc: function merc(lat, lng, c) {
return {
x: this.radius * (lng - c) * this.radDeg,
y: -this.radius * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360))
};
},
/* merc_inv(x, y, c) {
return {
lat: (2 * Math.atan(Math.exp(y / this.radius)) - Math.PI / 2) * this.degRad,
lng: (c * this.radDeg + x / this.radius) * this.degRad
};
}, */
aea: function aea(lat, lng, c) {
var fi0 = 0,
lambda0 = c * this.radDeg,
fi1 = 29.5 * this.radDeg,
fi2 = 45.5 * this.radDeg,
fi = lat * this.radDeg,
lambda = lng * this.radDeg,
n = (Math.sin(fi1) + Math.sin(fi2)) / 2,
C = Math.cos(fi1) * Math.cos(fi1) + 2 * n * Math.sin(fi1),
theta = n * (lambda - lambda0),
ro = Math.sqrt(C - 2 * n * Math.sin(fi)) / n,
ro0 = Math.sqrt(C - 2 * n * Math.sin(fi0)) / n;
return {
x: ro * Math.sin(theta) * this.radius,
y: -(ro0 - ro * Math.cos(theta)) * this.radius
};
},
/* aea_inv(xCoord, yCoord, c) {
var x = xCoord / this.radius,
y = yCoord / this.radius,
fi0 = 0,
lambda0 = c * this.radDeg,
fi1 = 29.5 * this.radDeg,
fi2 = 45.5 * this.radDeg,
n = (Math.sin(fi1)+Math.sin(fi2)) / 2,
C = Math.cos(fi1)*Math.cos(fi1)+2*n*Math.sin(fi1),
ro0 = Math.sqrt(C-2*n*Math.sin(fi0))/n,
ro = Math.sqrt(x*x+(ro0-y)*(ro0-y)),
theta = Math.atan( x / (ro0 - y) );
return {
lat: (Math.asin((C - ro * ro * n * n) / (2 * n))) * this.degRad,
lng: (lambda0 + theta / n) * this.degRad
};
}, */
lcc: function lcc(lat, lng, c) {
var fi0 = 0,
lambda0 = c * this.radDeg,
lambda = lng * this.radDeg,
fi1 = 33 * this.radDeg,
fi2 = 45 * this.radDeg,
fi = lat * this.radDeg,
n = Math.log(Math.cos(fi1) * (1 / Math.cos(fi2))) / Math.log(Math.tan(Math.PI / 4 + fi2 / 2) * (1 / Math.tan(Math.PI / 4 + fi1 / 2))),
F = Math.cos(fi1) * Math.pow(Math.tan(Math.PI / 4 + fi1 / 2), n) / n,
ro = F * Math.pow(1 / Math.tan(Math.PI / 4 + fi / 2), n),
ro0 = F * Math.pow(1 / Math.tan(Math.PI / 4 + fi0 / 2), n);
return {
x: ro * Math.sin(n * (lambda - lambda0)) * this.radius,
y: -(ro0 - ro * Math.cos(n * (lambda - lambda0))) * this.radius
};
}
/* lcc_inv(xCoord, yCoord, c) {
var x = xCoord / this.radius,
y = yCoord / this.radius,
fi0 = 0,
lambda0 = c * this.radDeg,
fi1 = 33 * this.radDeg,
fi2 = 45 * this.radDeg,
n = Math.log( Math.cos(fi1) * (1 / Math.cos(fi2)) ) / Math.log( Math.tan( Math.PI / 4 + fi2 / 2) * (1 / Math.tan( Math.PI / 4 + fi1 / 2) ) ),
F = ( Math.cos(fi1) * Math.pow( Math.tan( Math.PI / 4 + fi1 / 2 ), n ) ) / n,
ro0 = F * Math.pow( 1 / Math.tan( Math.PI / 4 + fi0 / 2 ), n ),
ro = this.sgn(n) * Math.sqrt(x*x+(ro0-y)*(ro0-y)),
theta = Math.atan( x / (ro0 - y) );
return {
lat: (2 * Math.atan(Math.pow(F/ro, 1/n)) - Math.PI / 2) * this.degRad,
lng: (lambda0 + theta / n) * this.degRad
};
} */
};
Proj.degRad = 180 / Math.PI;
Proj.radDeg = Math.PI / 180;
Proj.radius = 6381372;
function coordsToPoint(lat, lng) {
var projection = jsvectormap_esm_Map.maps[this.params.map].projection;
var _Proj$projection$type = Proj[projection.type](lat, lng, projection.centralMeridian),
x = _Proj$projection$type.x,
y = _Proj$projection$type.y;
var inset = this.getInsetForPoint(x, y);
if (!inset) {
return false;
}
var bbox = inset.bbox;
x = (x - bbox[0].x) / (bbox[1].x - bbox[0].x) * inset.width * this.scale;
y = (y - bbox[0].y) / (bbox[1].y - bbox[0].y) * inset.height * this.scale;
return {
x: x + this.transX * this.scale + inset.left * this.scale,
y: y + this.transY * this.scale + inset.top * this.scale
};
}
function getInsetForPoint(x, y) {
var insets = jsvectormap_esm_Map.maps[this.params.map].insets;
for (var index = 0; index < insets.length; index++) {
var _insets$index$bbox = insets[index].bbox,
start = _insets$index$bbox[0],
end = _insets$index$bbox[1];
if (x > start.x && x < end.x && y > start.y && y < end.y) {
return insets[index];
}
}
}
function getMarkerPosition(_ref) {
var coords = _ref.coords;
if (jsvectormap_esm_Map.maps[this.params.map].projection) {
return this.coordsToPoint.apply(this, coords);
}
return {
x: coords[0] * this.scale + this.transX * this.scale,
y: coords[1] * this.scale + this.transY * this.scale
};
}
function repositionLines() {
var _this = this;
var curvature = this.params.lineStyle.curvature;
Object.values(this._lines).forEach(function (line) {
var startMarker = Object.values(_this._markers).find(function (_ref) {
var config = _ref.config;
return config.name === line.getConfig().from;
});
var endMarker = Object.values(_this._markers).find(function (_ref2) {
var config = _ref2.config;
return config.name === line.getConfig().to;
});
if (startMarker && endMarker) {
var _this$getMarkerPositi = _this.getMarkerPosition(startMarker.config),
x1 = _this$getMarkerPositi.x,
y1 = _this$getMarkerPositi.y;
var _this$getMarkerPositi2 = _this.getMarkerPosition(endMarker.config),
x2 = _this$getMarkerPositi2.x,
y2 = _this$getMarkerPositi2.y;
var curvatureOption = line._options.curvature == 0 ? 0 : line._options.curvature || curvature;
var midX = (x1 + x2) / 2;
var midY = (y1 + y2) / 2;
var curveX = midX + curvatureOption * (y2 - y1);
var curveY = midY - curvatureOption * (x2 - x1);
line.setStyle({
d: "M" + x1 + "," + y1 + " Q" + curveX + "," + curveY + " " + x2 + "," + y2
});
}
});
}
function repositionMarkers() {
for (var index in this._markers) {
var point = this.getMarkerPosition(this._markers[index].config);
if (point !== false) {
this._markers[index].element.setStyle({
cx: point.x,
cy: point.y
});
}
}
}
function repositionLabels() {
var labels = this.params.labels;
if (!labels) {
return;
}
// Regions labels
if (labels.regions) {
for (var key in this.regions) {
this.regions[key].element.updateLabelPosition();
}
}
// Markers labels
if (labels.markers) {
for (var _key in this._markers) {
this._markers[_key].element.updateLabelPosition();
}
}
}
var core = {
_setupContainerEvents: setupContainerEvents,
_setupElementEvents: setupElementEvents,
_setupZoomButtons: setupZoomButtons,
_setupContainerTouchEvents: setupContainerTouchEvents,
_createRegions: createRegions,
_createLines: createLines,
_createMarkers: createMarkers,
_createSeries: createSeries,
_applyTransform: applyTransform,
_resize: resize,
_setScale: setScale,
setFocus: setFocus,
updateSize: updateSize,
coordsToPoint: coordsToPoint,
getInsetForPoint: getInsetForPoint,
getMarkerPosition: getMarkerPosition,
_repositionLines: repositionLines,
_repositionMarkers: repositionMarkers,
_repositionLabels: repositionLabels
};
var jsvectormap_esm_Defaults = {
map: 'world',
backgroundColor: 'transparent',
draggable: true,
zoomButtons: true,
zoomOnScroll: true,
zoomOnScrollSpeed: 3,
zoomMax: 12,
zoomMin: 1,
zoomAnimate: true,
showTooltip: true,
zoomStep: 1.5,
bindTouchEvents: true,
// Line options
lineStyle: {
curvature: 0,
stroke: '#808080',
strokeWidth: 1,
strokeLinecap: 'round'
},
// Marker options
markersSelectable: false,
markersSelectableOne: false,
markerStyle: {
initial: {
r: 7,
fill: '#374151',
fillOpacity: 1,
stroke: '#FFF',
strokeWidth: 5,
strokeOpacity: .5
},
hover: {
fill: '#3cc0ff',
cursor: 'pointer'
},
selected: {
fill: 'blue'
},
selectedHover: {}
},
markerLabelStyle: {
initial: {
fontFamily: 'Verdana',
fontSize: 12,
fontWeight: 500,
cursor: 'default',
fill: '#374151'
},
hover: {
cursor: 'pointer'
},
selected: {},
selectedHover: {}
},
// Region options
regionsSelectable: false,
regionsSelectableOne: false,
regionStyle: {
initial: {
fill: '#dee2e8',
fillOpacity: 1,
stroke: 'none',
strokeWidth: 0
},
hover: {
fillOpacity: .7,
cursor: 'pointer'
},
selected: {
fill: '#9ca3af'
},
selectedHover: {}
},
regionLabelStyle: {
initial: {
fontFamily: 'Verdana',
fontSize: '12',
fontWeight: 'bold',
cursor: 'default',
fill: '#35373e'
},
hover: {
cursor: 'pointer'
}
}
};
var SVGElement = /*#__PURE__*/function () {
function SVGElement(name, config) {
this.node = this._createElement(name);
if (config) {
this.set(config);
}
}
// Create new SVG element `svg`, `g`, `path`, `line`, `circle`, `image`, etc.
// https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris
var _proto = SVGElement.prototype;
_proto._createElement = function _createElement(tagName) {
return document.createElementNS('http://www.w3.org/2000/svg', tagName);
};
_proto.addClass = function addClass(className) {
this.node.setAttribute('class', className);
};
_proto.getBBox = function getBBox() {
return this.node.getBBox();
}
// Apply attributes on the current node element
;
_proto.set = function set(property, value) {
if (typeof property === 'object') {
for (var attr in property) {
this.applyAttr(attr, property[attr]);
}
} else {
this.applyAttr(property, value);
}
};
_proto.get = function get(property) {
return this.style.initial[property];
};
_proto.applyAttr = function applyAttr(property, value) {
this.node.setAttribute(hyphenate(property), value);
};
_proto.remove = function remove() {
jsvectormap_esm_removeElement(this.node);
};
return SVGElement;
}();
var SVGShapeElement = /*#__PURE__*/function (_SVGElement) {
function SVGShapeElement(name, config, style) {
var _this;
if (style === void 0) {
style = {};
}
_this = _SVGElement.call(this, name, config) || this;
_this.isHovered = false;
_this.isSelected = false;
_this.style = style;
_this.style.current = {};
_this.updateStyle();
return _this;
}
_inheritsLoose(SVGShapeElement, _SVGElement);
var _proto = SVGShapeElement.prototype;
_proto.setStyle = function setStyle(property, value) {
if (typeof property === 'object') {
jsvectormap_esm_merge(this.style.current, property);
} else {
var _merge;
jsvectormap_esm_merge(this.style.current, (_merge = {}, _merge[property] = value, _merge));
}
this.updateStyle();
};
_proto.updateStyle = function updateStyle() {
var attrs = {};
jsvectormap_esm_merge(attrs, this.style.initial);
jsvectormap_esm_merge(attrs, this.style.current);
if (this.isHovered) {
jsvectormap_esm_merge(attrs, this.style.hover);
}
if (this.isSelected) {
jsvectormap_esm_merge(attrs, this.style.selected);
if (this.isHovered) {
jsvectormap_esm_merge(attrs, this.style.selectedHover);
}
}
this.set(attrs);
};
return SVGShapeElement;
}(SVGElement);
var SVGTextElement = /*#__PURE__*/function (_SVGShapeElement) {
function SVGTextElement(config, style) {
return _SVGShapeElement.call(this, 'text', config, style) || this;
}
_inheritsLoose(SVGTextElement, _SVGShapeElement);
var _proto = SVGTextElement.prototype;
_proto.applyAttr = function applyAttr(attr, value) {
attr === 'text' ? this.node.textContent = value : _SVGShapeElement.prototype.applyAttr.call(this, attr, value);
};
return SVGTextElement;
}(SVGShapeElement);
var SVGImageElement = /*#__PURE__*/function (_SVGShapeElement) {
function SVGImageElement(config, style) {
return _SVGShapeElement.call(this, 'image', config, style) || this;
}
_inheritsLoose(SVGImageElement, _SVGShapeElement);
var _proto = SVGImageElement.prototype;
_proto.applyAttr = function applyAttr(attr, value) {
var imageUrl;
if (attr === 'image') {
// This get executed when we have url in series.markers[0].scale.someScale.url
if (typeof value === 'object') {
imageUrl = value.url;
this.offset = value.offset || [0, 0];
} else {
imageUrl = value;
this.offset = [0, 0];
}
this.node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageUrl);
// Set width and height then call this `applyAttr` again
this.width = 23;
this.height = 23;
this.applyAttr('width', this.width);
this.applyAttr('height', this.height);
this.applyAttr('x', this.cx - this.width / 2 + this.offset[0]);
this.applyAttr('y', this.cy - this.height / 2 + this.offset[1]);
} else if (attr == 'cx') {
this.cx = value;
if (this.width) {
this.applyAttr('x', value - this.width / 2 + this.offset[0]);
}
} else if (attr == 'cy') {
this.cy = value;
if (this.height) {
this.applyAttr('y', value - this.height / 2 + this.offset[1]);
}
} else {
// This time Call SVGElement
_SVGShapeElement.prototype.applyAttr.apply(this, arguments);
}
};
return SVGImageElement;
}(SVGShapeElement);
var SVGCanvasElement = /*#__PURE__*/function (_SVGElement) {
function SVGCanvasElement(container) {
var _this;
_this = _SVGElement.call(this, 'svg') || this; // Create svg element for holding the whole map
_this._container = container;
// Create the defs element
_this._defsElement = new SVGElement('defs');
// Create group element which will hold the paths (regions)
_this._rootElement = new SVGElement('g', {
id: 'jvm-regions-group'
});
// Append the defs element to the this.node (SVG tag)
_this.node.appendChild(_this._defsElement.node);
// Append the group to this.node (SVG tag)
_this.node.appendChild(_this._rootElement.node);
// Append this.node (SVG tag) to the container
_this._container.appendChild(_this.node);
return _this;
}
_inheritsLoose(SVGCanvasElement, _SVGElement);
var _proto = SVGCanvasElement.prototype;
_proto.setSize = function setSize(width, height) {
this.node.setAttribute('width', width);
this.node.setAttribute('height', height);
};
_proto.applyTransformParams = function applyTransformParams(scale, transX, transY) {
this._rootElement.node.setAttribute('transform', "scale(" + scale + ") translate(" + transX + ", " + transY + ")");
}
// Create `path` element
;
_proto.createPath = function createPath(config, style, group) {
var path = new SVGShapeElement('path', config, style);
path.node.setAttribute('fill-rule', 'evenodd');
return this._add(path, group);
}
// Create `circle` element
;
_proto.createCircle = function createCircle(config, style, group) {
var circle = new SVGShapeElement('circle', config, style);
return this._add(circle, group);
}
// Create `line` element
;
_proto.createLine = function createLine(config, style, group) {
var line = new SVGShapeElement('line', config, style);
return this._add(line, group);
}
// Create `text` element
;
_proto.createText = function createText(config, style, group) {
var text = new SVGTextElement(config, style);
return this._add(text, group);
}
// Create `image` element
;
_proto.createImage = function createImage(config, style, group) {
var image = new SVGImageElement(config, style);
return this._add(image, group);
}
// Create `g` element
;
_proto.createGroup = function createGroup(id) {
var group = new SVGElement('g');
this.node.appendChild(group.node);
if (id) {
group.node.id = id;
}
group.canvas = this;
return group;
}
// Add some element to a spcific group or the root element if the group isn't given
;
_proto._add = function _add(element, group) {
group = group || this._rootElement;
group.node.appendChild(element.node);
return element;
};
return SVGCanvasElement;
}(SVGElement);
var jsvectormap_esm_Tooltip = /*#__PURE__*/function (_BaseComponent) {
function Tooltip(map) {
var _this;
_this = _BaseComponent.call(this) || this;
var tooltip = createElement('div', 'jvm-tooltip');
_this._map = map;
_this._tooltip = document.body.appendChild(tooltip);
_this._bindEventListeners();
return _this || _assertThisInitialized(_this);
}
_inheritsLoose(Tooltip, _BaseComponent);
var _proto = Tooltip.prototype;
_proto._bindEventListeners = function _bindEventListeners() {
var _this2 = this;
EventHandler.on(this._map.container, 'mousemove', function (event) {
if (!_this2._tooltip.classList.contains('active')) {
return;
}
var container = findElement(_this2._map.container, '#jvm-regions-group').getBoundingClientRect();
var space = 5; // Space between the cursor and tooltip element
// Tooltip
var _this2$_tooltip$getBo = _this2._tooltip.getBoundingClientRect(),
height = _this2$_tooltip$getBo.height,
width = _this2$_tooltip$getBo.width;
var topIsPassed = event.clientY <= container.top + height + space;
var top = event.pageY - height - space;
var left = event.pageX - width - space;
// Ensure the tooltip will never cross outside the canvas area(map)
if (topIsPassed) {
// Top:
top += height + space;
// The cursor is a bit larger from left side
left -= space * 2;
}
if (event.clientX < container.left + width + space) {
// Left:
left = event.pageX + space + 2;
if (topIsPassed) {
left += space * 2;
}
}
_this2.css({
top: top + "px",
left: left + "px"
});
});
};
_proto.getElement = function getElement() {
return this._tooltip;
};
_proto.show = function show() {
this._tooltip.classList.add('active');
};
_proto.hide = function hide() {
this._tooltip.classList.remove('active');
};
_proto.text = function text(string, html) {
if (html === void 0) {
html = false;
}
var property = html ? 'innerHTML' : 'textContent';
if (!string) {
return this._tooltip[property];
}
this._tooltip[property] = string;
};
_proto.css = function css(_css) {
for (var style in _css) {
this._tooltip.style[style] = _css[style];
}
return this;
};
return Tooltip;
}(jsvectormap_esm_BaseComponent);
var DataVisualization = /*#__PURE__*/function () {
function DataVisualization(_ref, map) {
var scale = _ref.scale,
values = _ref.values;
this._scale = scale;
this._values = values;
this._fromColor = this.hexToRgb(scale[0]);
this._toColor = this.hexToRgb(scale[1]);
this._map = map;
this.setMinMaxValues(values);
this.visualize();
}
var _proto = DataVisualization.prototype;
_proto.setMinMaxValues = function setMinMaxValues(values) {
this.min = Number.MAX_VALUE;
this.max = 0;
for (var value in values) {
value = parseFloat(values[value]);
if (value > this.max) {
this.max = value;
}
if (value < this.min) {
this.min = value;
}
}
};
_proto.visualize = function visualize() {
var attrs = {},
value;
for (var regionCode in this._values) {
value = parseFloat(this._values[regionCode]);
if (!isNaN(value)) {
attrs[regionCode] = this.getValue(value);
}
}
this.setAttributes(attrs);
};
_proto.setAttributes = function setAttributes(attrs) {
for (var code in attrs) {
if (this._map.regions[code]) {
this._map.regions[code].element.setStyle('fill', attrs[code]);
}
}
};
_proto.getValue = function getValue(value) {
if (this.min === this.max) {
return "#" + this._toColor.join('');
}
var hex,
color = '#';
for (var i = 0; i < 3; i++) {
hex = Math.round(this._fromColor[i] + (this._toColor[i] - this._fromColor[i]) * ((value - this.min) / (this.max - this.min))).toString(16);
color += (hex.length === 1 ? '0' : '') + hex;
}
return color;
};
_proto.hexToRgb = function hexToRgb(h) {
var r = 0,
g = 0,
b = 0;
if (h.length == 4) {
r = '0x' + h[1] + h[1];
g = '0x' + h[2] + h[2];
b = '0x' + h[3] + h[3];
} else if (h.length == 7) {
r = '0x' + h[1] + h[2];
g = '0x' + h[3] + h[4];
b = '0x' + h[5] + h[6];
}
return [parseInt(r), parseInt(g), parseInt(b)];
};
return DataVisualization;
}();
var JVM_PREFIX = 'jvm-';
var CONTAINER_CLASS = JVM_PREFIX + "container";
var MARKERS_GROUP_ID = JVM_PREFIX + "markers-group";
var MARKERS_LABELS_GROUP_ID = JVM_PREFIX + "markers-labels-group";
var LINES_GROUP_ID = JVM_PREFIX + "lines-group";
var SERIES_CONTAINER_CLASS = JVM_PREFIX + "series-container";
var SERIES_CONTAINER_H_CLASS = SERIES_CONTAINER_CLASS + " " + JVM_PREFIX + "series-h";
var SERIES_CONTAINER_V_CLASS = SERIES_CONTAINER_CLASS + " " + JVM_PREFIX + "series-v";
var jsvectormap_esm_Map = /*#__PURE__*/function () {
function Map(options) {
var _this = this;
if (options === void 0) {
options = {};
}
// Merge the given options with the default options
this.params = jsvectormap_esm_merge(Map.defaults, options, true);
// Throw an error if the given map name doesn't match
// the map that was set in map file
if (!Map.maps[this.params.map]) {
throw new Error("Attempt to use map which was not loaded: " + options.map);
}
this.regions = {};
this.scale = 1;
this.transX = 0;
this.transY = 0;
this._mapData = Map.maps[this.params.map];
this._markers = {};
this._lines = {};
this._defaultWidth = this._mapData.width;
this._defaultHeight = this._mapData.height;
this._height = 0;
this._width = 0;
this._baseScale = 1;
this._baseTransX = 0;
this._baseTransY = 0;
// `document` is already ready, just initialise now
if (document.readyState !== 'loading') {
this._init();
} else {
// Wait until `document` is ready
window.addEventListener('DOMContentLoaded', function () {
return _this._init();
});
}
}
var _proto = Map.prototype;
_proto._init = function _init() {
var options = this.params;
this.container = getElement(options.selector);
this.container.classList.add(CONTAINER_CLASS);
// The map canvas element
this.canvas = new SVGCanvasElement(this.container);
// Set the map's background color
this.setBackgroundColor(options.backgroundColor);
// Create regions
this._createRegions();
// Update size
this.updateSize();
// Lines group must be created before markers
// Otherwise the lines will be drawn on top of the markers.
if (options.lines) {
this._linesGroup = this.canvas.createGroup(LINES_GROUP_ID);
}
if (options.markers) {
this._markersGroup = this.canvas.createGroup(MARKERS_GROUP_ID);
this._markerLabelsGroup = this.canvas.createGroup(MARKERS_LABELS_GROUP_ID);
}
// Create markers
this._createMarkers(options.markers);
// Create lines
this._createLines(options.lines || {});
// Position labels
this._repositionLabels();
// Setup the container events
this._setupContainerEvents();
// Setup regions/markers events
this._setupElementEvents();
// Create zoom buttons if `zoomButtons` is presented
if (options.zoomButtons) {
this._setupZoomButtons();
}
// Create toolip
if (options.showTooltip) {
this._tooltip = new jsvectormap_esm_Tooltip(this);
}
// Set selected regions if any
if (options.selectedRegions) {
this._setSelected('regions', options.selectedRegions);
}
// Set selected regions if any
if (options.selectedMarkers) {
this._setSelected('_markers', options.selectedMarkers);
}
// Set focus on a spcific region
if (options.focusOn) {
this.setFocus(options.focusOn);
}
// Data visualization
if (options.visualizeData) {
this.dataVisualization = new DataVisualization(options.visualizeData, this);
}
// Bind touch events if true
if (options.bindTouchEvents) {
if ('ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch) {
this._setupContainerTouchEvents();
}
}
// Create series if any
if (options.series) {
this.container.appendChild(this.legendHorizontal = createElement('div', SERIES_CONTAINER_H_CLASS));
this.container.appendChild(this.legendVertical = createElement('div', SERIES_CONTAINER_V_CLASS));
this._createSeries();
}
// Fire loaded event
this._emit(jsvectormap_esm_Events.onLoaded, [this]);
}
// Public
;
_proto.setBackgroundColor = function setBackgroundColor(color) {
this.container.style.backgroundColor = color;
}
// Regions
;
_proto.getSelectedRegions = function getSelectedRegions() {
return this._getSelected('regions');
};
_proto.clearSelectedRegions = function clearSelectedRegions(regions) {
var _this2 = this;
if (regions === void 0) {
regions = undefined;
}
regions = this._normalizeRegions(regions) || this._getSelected('regions');
regions.forEach(function (key) {
_this2.regions[key].element.select(false);
});
};
_proto.setSelectedRegions = function setSelectedRegions(regions) {
this.clearSelectedRegions();
this._setSelected('regions', this._normalizeRegions(regions));
}
// Markers
;
_proto.getSelectedMarkers = function getSelectedMarkers() {
return this._getSelected('_markers');
};
_proto.clearSelectedMarkers = function clearSelectedMarkers() {
this._clearSelected('_markers');
};
_proto.setSelectedMarkers = function setSelectedMarkers(markers) {
this._setSelected('_markers', markers);
};
_proto.addMarkers = function addMarkers(config) {
config = Array.isArray(config) ? config : [config];
this._createMarkers(config, true);
};
_proto.removeMarkers = function removeMarkers(markers) {
var _this3 = this;
if (!markers) {
markers = Object.keys(this._markers);
}
markers.forEach(function (index) {
// Remove the element from the DOM
_this3._markers[index].element.remove();
// Remove the element from markers object
delete _this3._markers[index];
});
}
// Lines
;
_proto.addLine = function addLine(from, to, style) {
if (style === void 0) {
style = {};
}
console.warn('`addLine` method is deprecated, please use `addLines` instead.');
this._createLines([{
from: from,
to: to,
style: style
}], this._markers, true);
};
_proto.addLines = function addLines(config) {
var uids = this._getLinesAsUids();
if (!Array.isArray(config)) {
config = [config];
}
this._createLines(config.filter(function (line) {
return !(uids.indexOf(getLineUid(line.from, line.to)) > -1);
}), true);
};
_proto.removeLines = function removeLines(lines) {
var _this4 = this;
if (Array.isArray(lines)) {
lines = lines.map(function (line) {
return getLineUid(line.from, line.to);
});
} else {
lines = this._getLinesAsUids();
}
lines.forEach(function (uid) {
_this4._lines[uid].dispose();
delete _this4._lines[uid];
});
};
_proto.removeLine = function removeLine(from, to) {
console.warn('`removeLine` method is deprecated, please use `removeLines` instead.');
var uid = getLineUid(from, to);
if (this._lines.hasOwnProperty(uid)) {
this._lines[uid].element.remove();
delete this._lines[uid];
}
}
// Reset map
;
_proto.reset = function reset() {
for (var key in this.series) {
for (var i = 0; i < this.series[key].length; i++) {
this.series[key][i].clear();
}
}
if (this.legendHorizontal) {
jsvectormap_esm_removeElement(this.legendHorizontal);
this.legendHorizontal = null;
}
if (this.legendVertical) {
jsvectormap_esm_removeElement(this.legendVertical);
this.legendVertical = null;
}
this.scale = this._baseScale;
this.transX = this._baseTransX;
this.transY = this._baseTransY;
this._applyTransform();
this.clearSelectedMarkers();
this.clearSelectedRegions();
this.removeMarkers();
}
// Destroy the map
;
_proto.destroy = function destroy(destroyInstance) {
var _this5 = this;
if (destroyInstance === void 0) {
destroyInstance = true;
}
// Remove event registry
EventHandler.flush();
// Remove tooltip from DOM and memory
this._tooltip.dispose();
// Fire destroyed event
this._emit(jsvectormap_esm_Events.onDestroyed);
// Remove references
if (destroyInstance) {
Object.keys(this).forEach(function (key) {
try {
delete _this5[key];
} catch (e) {}
});
}
};
_proto.extend = function extend(name, callback) {
if (typeof this[name] === 'function') {
throw new Error("The method [" + name + "] does already exist, please use another name.");
}
Map.prototype[name] = callback;
}
// Private
;
_proto._emit = function _emit(eventName, args) {
for (var event in jsvectormap_esm_Events) {
if (jsvectormap_esm_Events[event] === eventName && typeof this.params[event] === 'function') {
this.params[event].apply(this, args);
}
}
}
// Get selected markers/regions
;
_proto._getSelected = function _getSelected(type) {
var selected = [];
for (var key in this[type]) {
if (this[type][key].element.isSelected) {
selected.push(key);
}
}
return selected;
};
_proto._setSelected = function _setSelected(type, keys) {
var _this6 = this;
keys.forEach(function (key) {
if (_this6[type][key]) {
_this6[type][key].element.select(true);
}
});
};
_proto._clearSelected = function _clearSelected(type) {
var _this7 = this;
this._getSelected(type).forEach(function (key) {
_this7[type][key].element.select(false);
});
};
_proto._getLinesAsUids = function _getLinesAsUids() {
return Object.keys(this._lines);
};
_proto._normalizeRegions = function _normalizeRegions(regions) {
return typeof regions === 'string' ? [regions] : regions;
};
return Map;
}();
jsvectormap_esm_Map.maps = {};
jsvectormap_esm_Map.defaults = jsvectormap_esm_Defaults;
Object.assign(jsvectormap_esm_Map.prototype, core);
/**
* jsVectorMap
* Copyrights (c) Mustafa Omar https://github.com/themustafaomar
* Released under the MIT License.
*/
var scripts_jsVectorMap = /*#__PURE__*/function () {
function jsVectorMap(options) {
if (options === void 0) {
options = {};
}
if (!options.selector) {
throw new Error('Selector is not given.');
}
return new jsvectormap_esm_Map(options);
}
// Public
jsVectorMap.addMap = function addMap(name, map) {
jsvectormap_esm_Map.maps[name] = map;
};
return jsVectorMap;
}();
var jsvectormap_esm_index = window.jsVectorMap = scripts_jsVectorMap;
// EXTERNAL MODULE: ../../node_modules/jsvectormap/dist/maps/world.js
var world = __webpack_require__(177);
;// ../assets/scripts/utils/storage.js
/**
* Adminator Storage Utilities
* Safe localStorage wrapper with error handling
*
* @module utils/storage
*/
/**
* Check if localStorage is available
* @returns {boolean}
*/
const isAvailable = () => {
try {
const test = '__storage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch {
return false;
}
};
/**
* In-memory fallback when localStorage is unavailable
* @type {Map<string, string>}
*/
const memoryStorage = new Map();
/**
* Storage utility with safe localStorage access
* Falls back to in-memory storage when localStorage is unavailable
* (e.g., private browsing, storage quota exceeded)
*
* @namespace
*/
const storage_Storage = {
/**
* Check if storage is available
* @returns {boolean}
*/
isAvailable,
/**
* Get an item from storage
*
* @param {string} key - Storage key
* @returns {string|null} Stored value or null
*
* @example
* const theme = Storage.get('theme');
*/
get(key) {
try {
if (isAvailable()) {
return localStorage.getItem(key);
}
return memoryStorage.get(key) ?? null;
} catch {
return memoryStorage.get(key) ?? null;
}
},
/**
* Set an item in storage
*
* @param {string} key - Storage key
* @param {string} value - Value to store
* @returns {boolean} Success status
*
* @example
* Storage.set('theme', 'dark');
*/
set(key, value) {
try {
if (isAvailable()) {
localStorage.setItem(key, value);
return true;
}
memoryStorage.set(key, value);
return true;
} catch {
// Fallback to memory storage
memoryStorage.set(key, value);
return true;
}
},
/**
* Remove an item from storage
*
* @param {string} key - Storage key
* @returns {boolean} Success status
*
* @example
* Storage.remove('theme');
*/
remove(key) {
try {
if (isAvailable()) {
localStorage.removeItem(key);
}
memoryStorage.delete(key);
return true;
} catch {
memoryStorage.delete(key);
return true;
}
},
/**
* Clear all storage
*
* @returns {boolean} Success status
*
* @example
* Storage.clear();
*/
clear() {
try {
if (isAvailable()) {
localStorage.clear();
}
memoryStorage.clear();
return true;
} catch {
memoryStorage.clear();
return true;
}
},
/**
* Get a JSON object from storage
*
* @param {string} key - Storage key
* @param {*} [defaultValue=null] - Default value if key doesn't exist or parse fails
* @returns {*} Parsed object or default value
*
* @example
* const settings = Storage.getJSON('settings', { theme: 'light' });
*/
getJSON(key) {
let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
try {
const value = this.get(key);
if (value === null) {
return defaultValue;
}
return JSON.parse(value);
} catch {
return defaultValue;
}
},
/**
* Set a JSON object in storage
*
* @param {string} key - Storage key
* @param {*} value - Value to store (will be JSON stringified)
* @returns {boolean} Success status
*
* @example
* Storage.setJSON('settings', { theme: 'dark', sidebar: 'expanded' });
*/
setJSON(key, value) {
try {
return this.set(key, JSON.stringify(value));
} catch {
return false;
}
},
/**
* Check if a key exists in storage
*
* @param {string} key - Storage key
* @returns {boolean}
*
* @example
* if (Storage.has('theme')) {
* // Use stored theme
* }
*/
has(key) {
return this.get(key) !== null;
},
/**
* Get all keys in storage
*
* @returns {string[]} Array of keys
*
* @example
* const keys = Storage.keys();
*/
keys() {
try {
if (isAvailable()) {
return Object.keys(localStorage);
}
return Array.from(memoryStorage.keys());
} catch {
return Array.from(memoryStorage.keys());
}
},
/**
* Get storage size in bytes (approximate)
*
* @returns {number} Size in bytes
*
* @example
* console.log(`Storage used: ${Storage.size()} bytes`);
*/
size() {
try {
let total = 0;
const keys = this.keys();
for (const key of keys) {
const value = this.get(key);
if (value) {
total += key.length + value.length;
}
}
return total * 2; // UTF-16 uses 2 bytes per character
} catch {
return 0;
}
}
};
/* harmony default export */ const storage = ((/* unused pure expression or super */ null && (storage_Storage)));
;// ../assets/scripts/utils/sanitize.js
/**
* Adminator Sanitization Utilities
* HTML and input sanitization for security
*
* @module utils/sanitize
*/
/**
* HTML entities map for encoding
* @type {Object<string, string>}
*/
const HTML_ENTITIES = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
/**
* Sanitization utilities namespace
* @namespace
*/
const Sanitize = {
/**
* Escape HTML entities to prevent XSS
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for HTML insertion
*
* @example
* const safe = Sanitize.html('<script>alert("xss")</script>');
* // Returns: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;
*/
html(str) {
if (typeof str !== 'string') {
return String(str ?? '');
}
return str.replace(/[&<>"'`=/]/g, char => HTML_ENTITIES[char]);
},
/**
* Escape string for use in HTML attributes
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for attribute values
*
* @example
* element.setAttribute('data-name', Sanitize.attr(userInput));
*/
attr(str) {
return this.html(str);
},
/**
* Sanitize URL to prevent javascript: and data: URLs
*
* @param {string} url - URL to sanitize
* @param {string[]} [allowedProtocols=['http:', 'https:', 'mailto:', 'tel:']] - Allowed protocols
* @returns {string} Sanitized URL or empty string if invalid
*
* @example
* const safeUrl = Sanitize.url(userProvidedUrl);
* if (safeUrl) {
* window.location.href = safeUrl;
* }
*/
url(url) {
let allowedProtocols = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['http:', 'https:', 'mailto:', 'tel:'];
if (typeof url !== 'string') {
return '';
}
// Trim and normalize
const trimmed = url.trim().toLowerCase();
// Block dangerous protocols
const dangerousProtocols = ['javascript:', 'data:', 'vbscript:'];
for (const protocol of dangerousProtocols) {
if (trimmed.startsWith(protocol)) {
return '';
}
}
// Check for allowed protocols
try {
const parsed = new URL(url, window.location.origin);
if (!allowedProtocols.includes(parsed.protocol)) {
// Allow relative URLs
if (!url.startsWith('/') && !url.startsWith('./') && !url.startsWith('../')) {
return '';
}
}
return url;
} catch {
// If URL parsing fails, check if it's a relative URL
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../') || url.startsWith('#')) {
return url;
}
return '';
}
},
/**
* Strip HTML tags from a string
*
* @param {string} str - String with HTML
* @returns {string} String with HTML tags removed
*
* @example
* const text = Sanitize.stripTags('<p>Hello <b>World</b></p>');
* // Returns: 'Hello World'
*/
stripTags(str) {
if (typeof str !== 'string') {
return String(str ?? '');
}
// Create a temporary element to leverage browser's HTML parser
const div = document.createElement('div');
div.innerHTML = str;
return div.textContent || div.innerText || '';
},
/**
* Sanitize a string for use in CSS
*
* @param {string} str - String to sanitize
* @returns {string} CSS-safe string
*
* @example
* element.style.setProperty('--custom', Sanitize.css(userInput));
*/
css(str) {
if (typeof str !== 'string') {
return '';
}
// Remove potentially dangerous CSS values
return str.replace(/expression\s*\(/gi, '').replace(/url\s*\(/gi, '').replace(/javascript:/gi, '').replace(/[<>"']/g, '');
},
/**
* Sanitize a string for use in a filename
*
* @param {string} str - String to sanitize
* @returns {string} Filename-safe string
*
* @example
* const filename = Sanitize.filename(userInput) + '.txt';
*/
filename(str) {
if (typeof str !== 'string') {
return '';
}
return str.replace(/[/\\?%*:|"<>]/g, '-') // Replace dangerous characters
.replace(/\.\./g, '-') // Prevent directory traversal
.replace(/^\./, '_') // Don't start with dot
.slice(0, 255); // Limit length
},
/**
* Create safe innerHTML by escaping interpolated values
*
* @param {TemplateStringsArray} strings - Template literal strings
* @param {...*} values - Values to interpolate
* @returns {string} HTML string with escaped values
*
* @example
* element.innerHTML = Sanitize.template`<div>${userInput}</div>`;
*/
template(strings) {
for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
values[_key - 1] = arguments[_key];
}
return strings.reduce((result, str, i) => {
const value = values[i - 1];
const escaped = value !== undefined ? this.html(String(value)) : '';
return result + escaped + str;
});
},
/**
* Validate and sanitize an email address
*
* @param {string} email - Email to validate
* @returns {string} Sanitized email or empty string if invalid
*
* @example
* const email = Sanitize.email(userInput);
* if (email) {
* sendEmail(email);
* }
*/
email(email) {
if (typeof email !== 'string') {
return '';
}
const trimmed = email.trim().toLowerCase();
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(trimmed)) {
return '';
}
// Additional checks
if (trimmed.length > 254) {
return '';
}
return trimmed;
},
/**
* Sanitize a number input
*
* @param {*} value - Value to sanitize
* @param {Object} [options={}] - Options
* @param {number} [options.min=-Infinity] - Minimum value
* @param {number} [options.max=Infinity] - Maximum value
* @param {number} [options.default=0] - Default if invalid
* @returns {number} Sanitized number
*
* @example
* const age = Sanitize.number(userInput, { min: 0, max: 120, default: 18 });
*/
number(value) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
min = -Infinity,
max = Infinity,
default: defaultValue = 0
} = options;
const num = parseFloat(value);
if (isNaN(num) || !isFinite(num)) {
return defaultValue;
}
return Math.max(min, Math.min(max, num));
},
/**
* Sanitize an integer input
*
* @param {*} value - Value to sanitize
* @param {Object} [options={}] - Options
* @param {number} [options.min=-Infinity] - Minimum value
* @param {number} [options.max=Infinity] - Maximum value
* @param {number} [options.default=0] - Default if invalid
* @returns {number} Sanitized integer
*
* @example
* const count = Sanitize.integer(userInput, { min: 1, max: 100 });
*/
integer(value) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return Math.floor(this.number(value, options));
}
};
/* harmony default export */ const sanitize = ((/* unused pure expression or super */ null && (Sanitize)));
;// ../assets/scripts/utils/index.js
/**
* Adminator Utilities Index
* Re-exports all utility modules for convenient importing
*
* @module utils
*/
// Re-export all utilities
// Legacy initialization code
/* harmony default export */ const utils = ((function () {
// ------------------------------------------------------
// @Window Resize
// ------------------------------------------------------
/**
* NOTE: Register resize event for Masonry layout
*/
const EVENT = document.createEvent('UIEvents');
window.EVENT = EVENT;
EVENT.initUIEvent('resize', true, false, window, 0);
window.addEventListener('load', () => {
/**
* Trigger window resize event after page load
* for recalculation of masonry layout.
*/
window.dispatchEvent(EVENT);
});
// ------------------------------------------------------
// @External Links
// ------------------------------------------------------
// Open external links in new window
const externalLinks = document.querySelectorAll('a[href^="http"], a[href^="//"]');
externalLinks.forEach(link => {
const href = link.getAttribute('href');
if (href && !href.includes(window.location.host)) {
link.setAttribute('rel', 'noopener noreferrer');
link.setAttribute('target', '_blank');
}
});
// ------------------------------------------------------
// @Resize Trigger
// ------------------------------------------------------
// Trigger resize on any element click
document.addEventListener('click', () => {
window.dispatchEvent(window.EVENT);
});
})());
;// ../assets/scripts/vectorMaps/index.js
/* harmony default export */ const vectorMaps = ((function () {
// Store map instance for cleanup
let mapInstance = null;
// Main initialization function
const vectorMapInit = () => {
const worldMapContainer = document.getElementById('world-map-marker');
if (!worldMapContainer) return;
// Remove existing map
const existingMap = document.getElementById('vmap');
if (existingMap) {
existingMap.remove();
}
// Destroy existing map instance
if (mapInstance) {
try {
mapInstance.destroy();
} catch {
// Map instance cleanup
}
mapInstance = null;
}
// Get current theme colors - using template colors directly
const isDark = theme.current() === 'dark';
const colors = {
backgroundColor: isDark ? '#313644' : '#f9fafb',
regionColor: isDark ? '#565a5c' : '#e6eaf0',
borderColor: isDark ? '#72777a' : '#d3d9e3',
hoverColor: isDark ? '#7774e7' : '#0f9aee',
selectedColor: isDark ? '#37c936' : '#7774e7',
markerFill: isDark ? '#0f9aee' : '#7774e7',
markerStroke: isDark ? '#37c936' : '#0f9aee',
scaleStart: isDark ? '#b9c2d0' : '#e6eaf0',
scaleEnd: isDark ? '#0f9aee' : '#007bff',
textColor: isDark ? '#99abb4' : '#72777a'
};
// Create new map container
const mapContainer = document.createElement('div');
mapContainer.id = 'vmap';
mapContainer.style.height = '490px';
mapContainer.style.position = 'relative';
mapContainer.style.overflow = 'hidden';
mapContainer.style.backgroundColor = colors.backgroundColor;
mapContainer.style.borderRadius = '8px';
mapContainer.style.border = `1px solid ${colors.borderColor}`;
worldMapContainer.appendChild(mapContainer);
// Initialize JSVectorMap
try {
mapInstance = jsvectormap_esm_index({
selector: '#vmap',
map: 'world',
// Styling options
backgroundColor: 'transparent',
// Region styling
regionStyle: {
initial: {
fill: colors.regionColor,
stroke: colors.borderColor,
'stroke-width': 1,
'stroke-opacity': 0.4
},
hover: {
fill: colors.hoverColor,
cursor: 'pointer'
},
selected: {
fill: colors.selectedColor
}
},
// Marker styling
markerStyle: {
initial: {
r: 7,
fill: colors.markerFill,
stroke: colors.markerStroke,
'stroke-width': 2,
'stroke-opacity': 0.4
},
hover: {
r: 10,
fill: colors.hoverColor,
'stroke-opacity': 0.8,
cursor: 'pointer'
}
},
// Markers data
markers: [{
name: 'INDIA : 350',
coords: [21.00, 78.00]
}, {
name: 'Australia : 250',
coords: [-33.00, 151.00]
}, {
name: 'USA : 250',
coords: [36.77, -119.41]
}, {
name: 'UK : 250',
coords: [55.37, -3.41]
}, {
name: 'UAE : 250',
coords: [25.20, 55.27]
}],
// Simplified approach - remove series for now to test base colors
// series: {
// regions: [
// {
// attribute: 'fill',
// scale: [colors.scaleStart, colors.scaleEnd],
// normalizeFunction: 'polynomial',
// values: {
// 'US': 50,
// 'SA': 30,
// 'AU': 70,
// 'IN': 40,
// 'GB': 60,
// 'LV': 80,
// },
// },
// ],
// },
// Interaction options
zoomOnScroll: false,
zoomButtons: false,
// Event handlers
onMarkerTooltipShow(event, tooltip, index) {
// Safe access to marker data
const marker = this.markers && this.markers[index];
const markerName = marker ? marker.name : `Marker ${index + 1}`;
tooltip.text(markerName);
},
onRegionTooltipShow(event, tooltip, code) {
// Safe access to region data
const regionName = this.mapData && this.mapData.paths && this.mapData.paths[code] ? this.mapData.paths[code].name || code : code;
const value = this.series && this.series.regions && this.series.regions[0] && this.series.regions[0].values ? this.series.regions[0].values[code] : null;
tooltip.text(`${regionName}${value ? `: ${value}` : ''}`);
},
onLoaded() {
// Map loaded successfully
}
});
// Store instance for theme updates
worldMapContainer.mapInstance = mapInstance;
} catch {
// Error initializing JSVectorMap
// Fallback: show a simple message
mapContainer.innerHTML = `
<div style="
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: ${colors.backgroundColor};
border: 1px solid ${colors.borderColor};
border-radius: 8px;
color: ${colors.textColor};
font-size: 14px;
">
<div style="text-align: center;">
<div style="font-size: 24px; margin-bottom: 8px;">🗺️</div>
<div>World Map</div>
<div style="font-size: 12px; margin-top: 4px;">Interactive map will load here</div>
</div>
</div>
`;
}
};
// Theme update function
const updateMapTheme = () => {
if (mapInstance) {
const isDark = theme.current() === 'dark';
const colors = {
backgroundColor: isDark ? '#313644' : '#f9fafb',
regionColor: isDark ? '#565a5c' : '#e6eaf0',
borderColor: isDark ? '#72777a' : '#d3d9e3',
hoverColor: isDark ? '#7774e7' : '#0f9aee',
selectedColor: isDark ? '#37c936' : '#7774e7',
markerFill: isDark ? '#0f9aee' : '#7774e7',
markerStroke: isDark ? '#37c936' : '#0f9aee',
scaleStart: isDark ? '#b9c2d0' : '#e6eaf0',
scaleEnd: isDark ? '#0f9aee' : '#007bff',
textColor: isDark ? '#99abb4' : '#72777a'
};
try {
// Update region styles - commented out series for now
// mapInstance.updateSeries('regions', {
// attribute: 'fill',
// scale: [colors.scaleStart, colors.scaleEnd],
// values: {
// 'US': 50,
// 'SA': 30,
// 'AU': 70,
// 'IN': 40,
// 'GB': 60,
// 'LV': 80,
// },
// });
// Update container background
const container = document.getElementById('vmap');
if (container) {
container.style.backgroundColor = colors.backgroundColor;
}
} catch {
// Theme update failed, reinitializing map
vectorMapInit();
}
} else {
vectorMapInit();
}
};
// Initialize map
vectorMapInit();
// Reinitialize on window resize
window.addEventListener('resize', events.debounce(vectorMapInit, 300));
// Listen for theme changes
window.addEventListener('adminator:themeChanged', events.debounce(updateMapTheme, 150));
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (mapInstance) {
try {
mapInstance.destroy();
} catch {
// Map cleanup on unload
}
mapInstance = null;
}
});
// Return public API
return {
init: vectorMapInit,
updateTheme: updateMapTheme,
getInstance: () => mapInstance
};
})());
;// ../assets/scripts/chat/index.js
/* harmony default export */ const chat = ((function () {
const chatSidebarToggle = document.getElementById('chat-sidebar-toggle');
const chatSidebar = document.getElementById('chat-sidebar');
if (chatSidebarToggle && chatSidebar) {
chatSidebarToggle.addEventListener('click', e => {
chatSidebar.classList.toggle('open');
e.preventDefault();
});
}
})());
;// ../assets/scripts/email/index.js
/* harmony default export */ const email = ((function () {
// Email side toggle functionality
const emailSideToggle = document.querySelector('.email-side-toggle');
const emailApp = document.querySelector('.email-app');
if (emailSideToggle && emailApp) {
emailSideToggle.addEventListener('click', e => {
emailApp.classList.toggle('side-active');
e.preventDefault();
});
}
// Email list item and back to mailbox functionality
const emailListItems = document.querySelectorAll('.email-list-item, .back-to-mailbox');
const emailContent = document.querySelector('.email-content');
if (emailListItems.length > 0 && emailContent) {
emailListItems.forEach(item => {
item.addEventListener('click', e => {
emailContent.classList.toggle('open');
e.preventDefault();
});
});
}
})());
// EXTERNAL MODULE: ../../node_modules/load-google-maps-api/index.js
var load_google_maps_api = __webpack_require__(95);
var load_google_maps_api_default = /*#__PURE__*/__webpack_require__.n(load_google_maps_api);
;// ../assets/scripts/googleMaps/index.js
/* harmony default export */ const googleMaps = ((function () {
let map, marker;
const initGoogleMap = () => {
const googleMapElement = document.getElementById('google-map');
if (googleMapElement) {
load_google_maps_api_default()({
key: 'AIzaSyDW8td30_gj6sGXjiMU0ALeMu1SDEwUnEA'
}).then(() => {
const latitude = 26.8206;
const longitude = 30.8025;
const mapZoom = 5;
const {
google
} = window;
const mapOptions = {
center: new google.maps.LatLng(latitude, longitude),
zoom: mapZoom,
mapTypeId: google.maps.MapTypeId.ROADMAP,
styles: [{
'featureType': 'landscape',
'stylers': [{
'hue': theme.getCSSVar('--gmap-landscape-hue')
}, {
'saturation': 43.400000000000006
}, {
'lightness': 37.599999999999994
}, {
'gamma': 1
}]
}, {
'featureType': 'road.highway',
'stylers': [{
'hue': theme.getCSSVar('--gmap-highway-hue')
}, {
'saturation': -61.8
}, {
'lightness': 45.599999999999994
}, {
'gamma': 1
}]
}, {
'featureType': 'road.arterial',
'stylers': [{
'hue': theme.getCSSVar('--gmap-road-hue')
}, {
'saturation': -100
}, {
'lightness': 51.19999999999999
}, {
'gamma': 1
}]
}, {
'featureType': 'road.local',
'stylers': [{
'hue': theme.getCSSVar('--gmap-road-hue')
}, {
'saturation': -100
}, {
'lightness': 52
}, {
'gamma': 1
}]
}, {
'featureType': 'water',
'stylers': [{
'hue': theme.getCSSVar('--gmap-water-hue')
}, {
'saturation': -13.200000000000003
}, {
'lightness': 2.4000000000000057
}, {
'gamma': 1
}]
}, {
'featureType': 'poi',
'stylers': [{
'hue': theme.getCSSVar('--gmap-poi-hue')
}, {
'saturation': -1.0989010989011234
}, {
'lightness': 11.200000000000017
}, {
'gamma': 1
}]
}]
};
map = new google.maps.Map(document.getElementById('google-map'), mapOptions);
if (marker) {
marker.setMap(null);
}
marker = new google.maps.Marker({
map,
position: new google.maps.LatLng(latitude, longitude),
visible: true
});
});
}
};
// Initialize Google Maps
initGoogleMap();
// Listen for theme changes
window.addEventListener('adminator:themeChanged', initGoogleMap);
})());
;// ../assets/scripts/ui/index.js
/**
* UI Page Bootstrap Components
* Vanilla JavaScript implementations for Bootstrap components
*/
/* harmony default export */ const ui = ((function () {
// Modal functionality
class VanillaModal {
constructor(element) {
this.element = element;
this.modal = null;
this.backdrop = null;
this.isOpen = false;
this.init();
}
init() {
this.modal = document.querySelector(this.element.getAttribute('data-bs-target'));
if (this.modal) {
this.element.addEventListener('click', e => {
e.preventDefault();
this.show();
});
// Close button functionality
const closeButtons = this.modal.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => this.hide());
});
// Close on backdrop click
this.modal.addEventListener('click', e => {
if (e.target === this.modal) {
this.hide();
}
});
}
}
show() {
if (this.isOpen) return;
// Create backdrop
this.backdrop = document.createElement('div');
this.backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(this.backdrop);
// Show modal
this.modal.style.display = 'block';
this.modal.classList.add('show');
document.body.classList.add('modal-open');
this.isOpen = true;
// Focus the modal
this.modal.setAttribute('tabindex', '-1');
this.modal.focus();
// Escape key handler
this.escapeHandler = e => {
if (e.key === 'Escape') {
this.hide();
}
};
document.addEventListener('keydown', this.escapeHandler);
}
hide() {
if (!this.isOpen) return;
// Hide modal
this.modal.classList.remove('show');
this.modal.style.display = 'none';
document.body.classList.remove('modal-open');
// Remove backdrop
if (this.backdrop) {
this.backdrop.remove();
this.backdrop = null;
}
this.isOpen = false;
// Remove escape handler
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
this.escapeHandler = null;
}
}
}
// Dropdown functionality
class VanillaDropdown {
constructor(element) {
this.element = element;
this.menu = null;
this.isOpen = false;
this.init();
}
init() {
this.menu = this.element.parentNode.querySelector('.dropdown-menu');
if (this.menu) {
this.element.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
this.toggle();
});
// Close on outside click
document.addEventListener('click', e => {
if (!this.element.parentNode.contains(e.target)) {
this.hide();
}
});
// Close on escape
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && this.isOpen) {
this.hide();
}
});
}
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other dropdowns
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
menu.classList.remove('show');
});
this.menu.classList.add('show');
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
this.menu.classList.remove('show');
this.element.setAttribute('aria-expanded', 'false');
this.isOpen = false;
}
}
// Popover functionality
class VanillaPopover {
constructor(element) {
this.element = element;
this.popover = null;
this.isOpen = false;
this.init();
}
init() {
this.element.addEventListener('click', e => {
e.preventDefault();
this.toggle();
});
// Close on outside click
document.addEventListener('click', e => {
if (!this.element.contains(e.target) && (!this.popover || !this.popover.contains(e.target))) {
this.hide();
}
});
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other popovers
document.querySelectorAll('.popover').forEach(popover => {
popover.remove();
});
const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title');
const content = this.element.getAttribute('data-bs-content');
this.popover = document.createElement('div');
this.popover.className = 'popover bs-popover-top show';
this.popover.style.position = 'absolute';
this.popover.style.zIndex = '1070';
this.popover.style.maxWidth = '276px';
this.popover.style.backgroundColor = '#fff';
this.popover.style.border = '1px solid rgba(0,0,0,.2)';
this.popover.style.borderRadius = '6px';
this.popover.style.boxShadow = '0 5px 10px rgba(0,0,0,.2)';
let popoverContent = '';
if (title) {
popoverContent += `<div class="popover-header" style="padding: 8px 14px; margin-bottom: 0; font-size: 14px; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-radius: 5px 5px 0 0; font-weight: 600;">${title}</div>`;
}
popoverContent += `<div class="popover-body" style="padding: 9px 14px; word-wrap: break-word;">${content}</div>`;
this.popover.innerHTML = popoverContent;
document.body.appendChild(this.popover);
// Position popover
const rect = this.element.getBoundingClientRect();
this.popover.style.left = `${rect.left + rect.width / 2 - this.popover.offsetWidth / 2}px`;
this.popover.style.top = `${rect.top - this.popover.offsetHeight - 10}px`;
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
if (this.popover) {
this.popover.remove();
this.popover = null;
}
this.isOpen = false;
}
}
// Tooltip functionality
class VanillaTooltip {
constructor(element) {
this.element = element;
this.tooltip = null;
this.init();
}
init() {
this.element.addEventListener('mouseenter', () => this.show());
this.element.addEventListener('mouseleave', () => this.hide());
this.element.addEventListener('focus', () => this.show());
this.element.addEventListener('blur', () => this.hide());
}
show() {
if (this.tooltip) return;
const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title');
const placement = this.element.getAttribute('data-bs-placement') || 'top';
if (!title) return;
this.tooltip = document.createElement('div');
this.tooltip.className = `tooltip bs-tooltip-${placement} show`;
this.tooltip.style.position = 'absolute';
this.tooltip.style.zIndex = '1070';
this.tooltip.style.maxWidth = '200px';
this.tooltip.style.padding = '4px 8px';
this.tooltip.style.fontSize = '12px';
this.tooltip.style.backgroundColor = '#000';
this.tooltip.style.color = '#fff';
this.tooltip.style.borderRadius = '4px';
this.tooltip.style.pointerEvents = 'none';
this.tooltip.style.whiteSpace = 'nowrap';
this.tooltip.innerHTML = `<div class="tooltip-inner">${title}</div>`;
document.body.appendChild(this.tooltip);
// Position tooltip
const rect = this.element.getBoundingClientRect();
switch (placement) {
case 'top':
this.tooltip.style.left = `${rect.left + rect.width / 2 - this.tooltip.offsetWidth / 2}px`;
this.tooltip.style.top = `${rect.top - this.tooltip.offsetHeight - 5}px`;
break;
case 'bottom':
this.tooltip.style.left = `${rect.left + rect.width / 2 - this.tooltip.offsetWidth / 2}px`;
this.tooltip.style.top = `${rect.bottom + 5}px`;
break;
case 'left':
this.tooltip.style.left = `${rect.left - this.tooltip.offsetWidth - 5}px`;
this.tooltip.style.top = `${rect.top + rect.height / 2 - this.tooltip.offsetHeight / 2}px`;
break;
case 'right':
this.tooltip.style.left = `${rect.right + 5}px`;
this.tooltip.style.top = `${rect.top + rect.height / 2 - this.tooltip.offsetHeight / 2}px`;
break;
}
}
hide() {
if (this.tooltip) {
this.tooltip.remove();
this.tooltip = null;
}
}
}
// Accordion functionality
class VanillaAccordion {
constructor(element) {
this.element = element;
this.accordion = element.closest('.accordion');
this.target = document.querySelector(element.getAttribute('data-bs-target'));
this.isOpen = !element.classList.contains('collapsed');
this.init();
}
init() {
this.element.addEventListener('click', e => {
e.preventDefault();
this.toggle();
});
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other accordion items in the same parent
const parentAccordion = this.accordion;
if (parentAccordion) {
const otherItems = parentAccordion.querySelectorAll('.accordion-collapse.show');
otherItems.forEach(item => {
if (item !== this.target) {
item.classList.remove('show');
const button = parentAccordion.querySelector(`[data-bs-target="#${item.id}"]`);
if (button) {
button.classList.add('collapsed');
button.setAttribute('aria-expanded', 'false');
}
}
});
}
// Show this item
this.target.classList.add('show');
this.element.classList.remove('collapsed');
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
this.target.classList.remove('show');
this.element.classList.add('collapsed');
this.element.setAttribute('aria-expanded', 'false');
this.isOpen = false;
}
}
// Initialize all components
const initComponents = () => {
// Initialize modals
document.querySelectorAll('[data-bs-toggle="modal"]').forEach(element => {
new VanillaModal(element);
});
// Initialize dropdowns
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(element => {
new VanillaDropdown(element);
});
// Initialize popovers
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(element => {
new VanillaPopover(element);
});
// Initialize tooltips
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
new VanillaTooltip(element);
});
// Initialize accordions
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(element => {
new VanillaAccordion(element);
});
};
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initComponents);
} else {
initComponents();
}
// Public API
return {
init: initComponents,
Modal: VanillaModal,
Dropdown: VanillaDropdown,
Popover: VanillaPopover,
Tooltip: VanillaTooltip,
Accordion: VanillaAccordion
};
})());
;// ../assets/scripts/app.js
/**
* Modern Adminator Application
* Main application entry point with enhanced mobile support
*
* @module app
* @version 2.9.0
*/
// Note: Bootstrap 5 CSS is still available via SCSS imports
// Bootstrap JS components removed to eliminate jQuery dependency
// Import styles
// Import other modules that don't need immediate modernization
class AdminatorApp {
constructor() {
this.components = new Map();
this.isInitialized = false;
this.themeManager = theme;
this.cleanupFunctions = [];
// Initialize when DOM is ready
DOM.ready(() => {
this.init();
});
}
/**
* Initialize the application
*/
init() {
if (this.isInitialized) return;
logger.time('Adminator Init');
try {
// Initialize core components
this.initSidebar();
this.initCharts();
this.initDataTables();
this.initDatePickers();
this.initTheme();
this.initMobileEnhancements();
// Setup global event listeners using event delegation
this.setupGlobalEvents();
this.isInitialized = true;
logger.timeEnd('Adminator Init');
logger.info('Application initialized successfully');
// Dispatch custom event for other scripts
events.emit(window, 'adminator:ready', {
app: this
});
} catch (error) {
logger.error('Error initializing Adminator App', error);
}
}
/**
* Initialize Sidebar component
*/
initSidebar() {
if (DOM.exists('.sidebar')) {
const sidebar = new components_Sidebar();
this.components.set('sidebar', sidebar);
}
}
/**
* Initialize Chart components
*/
initCharts() {
// Check if we have any chart elements
const hasCharts = DOM.exists('#sparklinedash') || DOM.exists('.sparkline') || DOM.exists('.sparkbar') || DOM.exists('.sparktri') || DOM.exists('.sparkdisc') || DOM.exists('.sparkbull') || DOM.exists('.sparkbox') || DOM.exists('.easy-pie-chart') || DOM.exists('#line-chart') || DOM.exists('#area-chart') || DOM.exists('#scatter-chart') || DOM.exists('#bar-chart');
if (hasCharts) {
const charts = new components_Chart();
this.components.set('charts', charts);
}
}
/**
* Initialize DataTables (modern approach)
*/
initDataTables() {
const dataTableElement = DOM.select('#dataTable');
if (dataTableElement) {
// For now, use a lightweight approach
// In future iterations, we can replace with a modern table library
this.initBasicDataTable(dataTableElement);
}
}
/**
* Basic DataTable implementation (placeholder for full modernization)
*/
initBasicDataTable(table) {
// Add basic sorting functionality
const headers = DOM.selectAll('th', table);
headers.forEach(header => {
if (header.textContent.trim()) {
header.style.cursor = 'pointer';
header.style.userSelect = 'none';
DOM.on(header, 'click', () => {
// Basic sort functionality can be added here
// For now, we'll keep the existing DataTables library
});
}
});
}
/**
* Initialize Date Pickers (modern approach with Day.js)
*/
initDatePickers() {
const startDatePickers = DOM.selectAll('.start-date');
const endDatePickers = DOM.selectAll('.end-date');
[...startDatePickers, ...endDatePickers].forEach(picker => {
// Convert to HTML5 date input for better UX
if (picker.type !== 'date') {
picker.type = 'date';
picker.classList.add('form-control');
// Clear the placeholder since HTML5 date inputs don't need it
picker.removeAttribute('placeholder');
// Set default value to today if no value is set
if (!picker.value) {
picker.value = date.form.toInputValue(date.now());
}
// Make sure the input is clickable and focusable
picker.style.pointerEvents = 'auto';
picker.style.cursor = 'pointer';
// Ensure proper styling for HTML5 date input
picker.style.minHeight = '38px';
picker.style.lineHeight = '1.5';
// Date picker converted to HTML5 with Day.js support
}
});
// Add enhanced interaction - handle both field and icon clicks
[...startDatePickers, ...endDatePickers].forEach(picker => {
// Handle direct field clicks
DOM.on(picker, 'click', event => {
event.target.focus();
// For mobile browsers, trigger the date picker
if (event.target.showPicker && typeof event.target.showPicker === 'function') {
try {
event.target.showPicker();
} catch {
// Fallback if showPicker is not supported
}
}
});
// Handle calendar icon clicks (find the icon in the input group)
const inputGroup = picker.closest('.input-group');
if (inputGroup) {
const calendarIcon = inputGroup.querySelector('.input-group-text i.ti-calendar');
if (calendarIcon) {
DOM.on(calendarIcon, 'click', event => {
event.preventDefault();
event.stopPropagation();
picker.focus();
if (picker.showPicker && typeof picker.showPicker === 'function') {
try {
picker.showPicker();
} catch {
// Date picker opened via icon click
}
}
});
}
}
});
}
/**
* Initialize theme system with toggle
*/
initTheme() {
// Initializing theme system
// Initialize theme system first
this.themeManager.init();
// Inject theme toggle if missing - with retry mechanism
setTimeout(() => {
const navRight = DOM.select('.nav-right');
// Check for nav-right and theme-toggle existence
if (navRight && !DOM.exists('#theme-toggle')) {
const li = document.createElement('li');
li.className = 'theme-toggle d-flex ai-c';
const currentTheme = this.themeManager.current();
const isDark = currentTheme === 'dark';
li.innerHTML = `
<div class="form-check form-switch d-flex ai-c" style="margin: 0; padding: 0;" role="group" aria-label="Theme switcher">
<label class="form-check-label me-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-right: 8px;">
<i class="ti-sun" aria-hidden="true" style="margin-right: 4px;"></i><span class="theme-label">Light</span>
</label>
<input class="form-check-input" type="checkbox" id="theme-toggle"
role="switch"
aria-checked="${isDark}"
aria-label="Toggle dark mode"
${isDark ? 'checked' : ''}
style="margin: 0;">
<label class="form-check-label ms-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-left: 8px;">
<span class="theme-label">Dark</span><i class="ti-moon" aria-hidden="true" style="margin-left: 4px;"></i>
</label>
</div>
`;
// Insert before user dropdown (last item) - safer approach
const lastItem = navRight.querySelector('li:last-child');
if (lastItem && lastItem.parentNode === navRight) {
navRight.insertBefore(li, lastItem);
// Theme toggle inserted before last item
} else {
navRight.appendChild(li);
// Theme toggle appended to nav-right (safer approach)
}
// Add toggle functionality
const toggle = DOM.select('#theme-toggle');
if (toggle) {
DOM.on(toggle, 'change', () => {
const newTheme = toggle.checked ? 'dark' : 'light';
toggle.setAttribute('aria-checked', toggle.checked ? 'true' : 'false');
this.themeManager.apply(newTheme);
});
// Listen for theme changes from other sources
window.addEventListener('adminator:themeChanged', event => {
const isDark = event.detail.theme === 'dark';
toggle.checked = isDark;
toggle.setAttribute('aria-checked', isDark ? 'true' : 'false');
// Update charts when theme changes
const charts = this.components.get('charts');
if (charts) charts.redrawCharts();
});
}
} else {
// No nav-right found or theme-toggle already exists
}
}, 100); // Wait 100ms for DOM to be fully ready
}
/**
* Initialize mobile-specific enhancements
*/
initMobileEnhancements() {
// Initializing mobile enhancements
this.enhanceMobileDropdowns();
this.enhanceMobileSearch();
// Prevent horizontal scroll on mobile
if (this.isMobile()) {
document.body.style.overflowX = 'hidden';
}
}
/**
* Setup global event listeners using event delegation for performance
*/
setupGlobalEvents() {
// Use event delegation for dropdown clicks (single listener instead of many)
const dropdownCleanup = events.delegate(document, 'click', '.nav-right .dropdown-toggle', (e, toggle) => this.handleDropdownClick(e, toggle));
this.cleanupFunctions.push(dropdownCleanup);
// Global click handler for closing dropdowns/search
const globalClickCleanup = events.on(document, 'click', event => {
this.handleGlobalClick(event);
});
this.cleanupFunctions.push(globalClickCleanup);
// Window resize handler with debouncing using Events utility
const debouncedResize = events.debounce(() => this.handleResize(), 250);
const resizeCleanup = events.on(window, 'resize', debouncedResize);
this.cleanupFunctions.push(resizeCleanup);
// Escape key handler using delegation
const escapeCleanup = events.on(document, 'keydown', e => {
if (e.key === 'Escape') {
this.closeAllOverlays();
}
});
this.cleanupFunctions.push(escapeCleanup);
logger.debug('Global event listeners set up with delegation');
}
/**
* Handle dropdown toggle clicks
* @param {Event} e - Click event
* @param {Element} toggle - The clicked toggle element
*/
handleDropdownClick(e, toggle) {
if (!this.isMobile()) return;
e.preventDefault();
e.stopPropagation();
const dropdown = toggle.closest('.dropdown');
const menu = dropdown === null || dropdown === void 0 ? void 0 : dropdown.querySelector('.dropdown-menu');
if (!dropdown || !menu) return;
// Close search if open
this.closeSearch();
// Close other dropdowns
DOM.selectAll('.nav-right .dropdown').forEach(d => {
if (d !== dropdown) {
var _d$querySelector;
d.classList.remove('show');
(_d$querySelector = d.querySelector('.dropdown-menu')) === null || _d$querySelector === void 0 || _d$querySelector.classList.remove('show');
}
});
// Toggle current dropdown
const isOpen = dropdown.classList.contains('show');
dropdown.classList.toggle('show', !isOpen);
menu.classList.toggle('show', !isOpen);
document.body.style.overflow = isOpen ? '' : 'hidden';
document.body.classList.toggle('mobile-menu-open', !isOpen);
}
/**
* Close all overlays (dropdowns, search)
*/
closeAllOverlays() {
// Close dropdowns
DOM.selectAll('.nav-right .dropdown').forEach(dropdown => {
var _dropdown$querySelect;
dropdown.classList.remove('show');
(_dropdown$querySelect = dropdown.querySelector('.dropdown-menu')) === null || _dropdown$querySelect === void 0 || _dropdown$querySelect.classList.remove('show');
});
// Close search
this.closeSearch();
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
/**
* Close the search overlay
*/
closeSearch() {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Reset icon
const searchIcon = searchBox.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
const field = searchInput.querySelector('input');
if (field) {
field.value = '';
field.blur();
}
}
}
/**
* Handle window resize events
*/
handleResize() {
// Window resized, updating mobile features
// Close all mobile-specific overlays when switching to desktop
if (!this.isMobile()) {
document.body.style.overflow = '';
document.body.style.overflowX = '';
// Close dropdowns
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
// Close search
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
}
} else {
// Re-enable mobile overflow protection
document.body.style.overflowX = 'hidden';
}
// Re-apply mobile enhancements
this.enhanceMobileDropdowns();
this.enhanceMobileSearch();
}
/**
* Handle global click events
*/
handleGlobalClick(event) {
// Close mobile dropdowns when clicking outside
if (!event.target.closest('.dropdown')) {
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
document.body.style.overflow = '';
}
// Close search when clicking outside
if (!event.target.closest('.search-box') && !event.target.closest('.search-input')) {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
}
}
/**
* Check if we're on a mobile device
*/
isMobile() {
return window.innerWidth <= 768;
}
/**
* Enhanced mobile dropdown handling with improved email layout
*/
enhanceMobileDropdowns() {
if (!this.isMobile()) return;
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('.dropdown-toggle');
const menu = dropdown.querySelector('.dropdown-menu');
if (toggle && menu) {
// Remove existing listeners to prevent duplicates
const newToggle = toggle.cloneNode(true);
toggle.replaceWith(newToggle);
// Add click functionality for mobile dropdowns
DOM.on(newToggle, 'click', e => {
e.preventDefault();
e.stopPropagation();
// Close search if open
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
}
// Close other dropdowns first
dropdowns.forEach(otherDropdown => {
if (otherDropdown !== dropdown) {
otherDropdown.classList.remove('show');
const otherMenu = otherDropdown.querySelector('.dropdown-menu');
if (otherMenu) otherMenu.classList.remove('show');
}
});
// Toggle current dropdown
const isOpen = dropdown.classList.contains('show');
if (isOpen) {
dropdown.classList.remove('show');
menu.classList.remove('show');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
} else {
dropdown.classList.add('show');
menu.classList.add('show');
document.body.style.overflow = 'hidden';
document.body.classList.add('mobile-menu-open');
}
});
// Enhanced mobile close button functionality
DOM.on(menu, 'click', e => {
// Check if clicked on the close area (::before pseudo-element area)
const rect = menu.getBoundingClientRect();
const clickY = e.clientY - rect.top;
// If clicked in top 50px (close button area)
if (clickY <= 50) {
dropdown.classList.remove('show');
menu.classList.remove('show');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
e.preventDefault();
e.stopPropagation();
}
});
}
});
// Close dropdowns on escape key
DOM.on(document, 'keydown', e => {
if (e.key === 'Escape') {
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
});
}
/**
* Enhanced mobile search handling - Full-width search bar
*/
enhanceMobileSearch() {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
const searchToggle = searchBox.querySelector('a');
const searchField = searchInput.querySelector('input');
if (searchToggle && searchField) {
// Setting up full-width search functionality
// Remove existing listeners to prevent duplication
const newSearchToggle = searchToggle.cloneNode(true);
searchToggle.replaceWith(newSearchToggle);
DOM.on(newSearchToggle, 'click', e => {
e.preventDefault();
e.stopPropagation();
// Full-width search toggle clicked
// Close any open dropdowns first
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
// Toggle search state
const isActive = searchInput.classList.contains('active');
const searchIcon = newSearchToggle.querySelector('i');
if (isActive) {
// Close search
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Change icon back to search
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
if (searchField) {
searchField.value = '';
searchField.blur();
}
// Full-width search closed
} else {
// Open search
searchInput.classList.add('active');
document.body.classList.add('search-open');
// Change icon to close
if (searchIcon) {
searchIcon.className = 'ti-close';
}
// Focus the input after a short delay
setTimeout(() => {
if (searchField) {
searchField.focus();
// Search field focused
}
}, 100);
// Full-width search opened
}
});
// Close search on escape
DOM.on(document, 'keydown', e => {
if (e.key === 'Escape' && searchInput.classList.contains('active')) {
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Reset icon
const searchIcon = newSearchToggle.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
if (searchField) {
searchField.value = '';
searchField.blur();
}
// Full-width search closed via escape
}
});
// Handle search input
DOM.on(searchField, 'keypress', e => {
if (e.key === 'Enter') {
e.preventDefault();
const query = searchField.value.trim();
if (query) {
// Search query submitted
// Implement your search logic here
// For demo, close search after "searching"
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
const searchIcon = newSearchToggle.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
searchField.value = '';
searchField.blur();
}
}
});
// Full-width search functionality initialized
}
}
}
/**
* Get a component by name
*/
getComponent(name) {
return this.components.get(name);
}
/**
* Check if app is ready
*/
isReady() {
return this.isInitialized;
}
/**
* Destroy the application and clean up all resources
*/
destroy() {
logger.info('Destroying Adminator App');
// Clean up all event listeners
this.cleanupFunctions.forEach(cleanup => {
if (typeof cleanup === 'function') {
cleanup();
}
});
this.cleanupFunctions = [];
// Destroy all components
this.components.forEach((component, name) => {
if (typeof component.destroy === 'function') {
component.destroy();
logger.debug(`Component destroyed: ${name}`);
}
});
this.components.clear();
// Cleanup performance observers
utils_performance.cleanup();
this.isInitialized = false;
logger.info('Adminator App destroyed');
}
/**
* Refresh/reinitialize the application
*/
refresh() {
logger.info('Refreshing Adminator App');
if (this.isInitialized) {
this.destroy();
}
setTimeout(() => {
this.init();
}, 100);
}
}
// Initialize the application
const app = new AdminatorApp();
// Make app globally available for debugging
window.AdminatorApp = app;
// Export for module usage
/* harmony default export */ const scripts_app = ((/* unused pure expression or super */ null && (app)));
;// ../assets/scripts/datatable/index.js
// DataTable implementation
/* harmony default export */ const datatable = ((function () {
// Vanilla JS DataTable implementation
class VanillaDataTable {
constructor(element) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
this.element = element;
this.options = {
sortable: true,
searchable: true,
pagination: true,
pageSize: 10,
...options
};
this.originalData = [];
this.filteredData = [];
this.currentPage = 1;
this.sortColumn = null;
this.sortDirection = 'asc';
this.init();
}
init() {
this.extractData();
this.createControls();
this.applyStyles();
this.bindEvents();
this.render();
}
extractData() {
const rows = this.element.querySelectorAll('tbody tr');
this.originalData = Array.from(rows).map(row => {
const cells = row.querySelectorAll('td');
return Array.from(cells).map(cell => cell.textContent.trim());
});
this.filteredData = [...this.originalData];
}
createControls() {
const wrapper = document.createElement('div');
wrapper.className = 'datatable-wrapper';
// Create search input
if (this.options.searchable) {
const searchWrapper = document.createElement('div');
searchWrapper.className = 'datatable-search';
searchWrapper.innerHTML = `
<label>
Search:
<input type="text" class="form-control" placeholder="Search...">
</label>
`;
wrapper.appendChild(searchWrapper);
}
// Create pagination info
if (this.options.pagination) {
const infoWrapper = document.createElement('div');
infoWrapper.className = 'datatable-info';
wrapper.appendChild(infoWrapper);
}
// Wrap the table
this.element.parentNode.insertBefore(wrapper, this.element);
wrapper.appendChild(this.element);
// Create pagination controls
if (this.options.pagination) {
const paginationWrapper = document.createElement('div');
paginationWrapper.className = 'datatable-pagination';
wrapper.appendChild(paginationWrapper);
}
this.wrapper = wrapper;
}
applyStyles() {
// Apply Bootstrap-like styles
this.element.classList.add('table', 'table-striped', 'table-bordered');
// Add custom styles
const style = document.createElement('style');
style.textContent = `
.datatable-wrapper {
margin: 20px 0;
}
.datatable-search {
margin-bottom: 15px;
}
.datatable-search input {
width: 250px;
display: inline-block;
margin-left: 5px;
}
.datatable-info {
margin-top: 15px;
color: var(--c-text-muted, #6c757d);
font-size: 14px;
}
.datatable-pagination {
margin-top: 15px;
display: flex;
justify-content: center;
}
.datatable-pagination button {
background: var(--c-bkg-card, #fff);
border: 1px solid var(--c-border, #dee2e6);
color: var(--c-text-base, #333);
padding: 6px 12px;
margin: 0 2px;
cursor: pointer;
border-radius: 4px;
}
.datatable-pagination button:hover {
background: var(--c-primary, #007bff);
color: white;
}
.datatable-pagination button.active {
background: var(--c-primary, #007bff);
color: white;
}
.datatable-pagination button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.datatable-sort {
cursor: pointer;
user-select: none;
position: relative;
}
.datatable-sort:hover {
background: var(--c-bkg-card, #f8f9fa);
}
.datatable-sort::after {
content: '↕';
position: absolute;
right: 8px;
opacity: 0.5;
}
.datatable-sort.asc::after {
content: '↑';
opacity: 1;
}
.datatable-sort.desc::after {
content: '↓';
opacity: 1;
}
`;
document.head.appendChild(style);
}
bindEvents() {
// Search functionality
if (this.options.searchable) {
const searchInput = this.wrapper.querySelector('.datatable-search input');
searchInput.addEventListener('input', e => {
this.search(e.target.value);
});
}
// Sorting functionality
if (this.options.sortable) {
const headers = this.element.querySelectorAll('thead th');
headers.forEach((header, index) => {
header.classList.add('datatable-sort');
header.addEventListener('click', () => {
this.sort(index);
});
});
}
}
search(query) {
if (!query) {
this.filteredData = [...this.originalData];
} else {
this.filteredData = this.originalData.filter(row => row.some(cell => cell.toLowerCase().includes(query.toLowerCase())));
}
this.currentPage = 1;
this.render();
}
sort(columnIndex) {
if (this.sortColumn === columnIndex) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortColumn = columnIndex;
this.sortDirection = 'asc';
}
this.filteredData.sort((a, b) => {
const aVal = a[columnIndex];
const bVal = b[columnIndex];
// Try to parse as numbers
const aNum = parseFloat(aVal);
const bNum = parseFloat(bVal);
let comparison = 0;
if (!isNaN(aNum) && !isNaN(bNum)) {
comparison = aNum - bNum;
} else {
comparison = aVal.localeCompare(bVal);
}
return this.sortDirection === 'asc' ? comparison : -comparison;
});
this.updateSortHeaders();
this.render();
}
updateSortHeaders() {
const headers = this.element.querySelectorAll('thead th');
headers.forEach((header, index) => {
header.classList.remove('asc', 'desc');
if (index === this.sortColumn) {
header.classList.add(this.sortDirection);
}
});
}
render() {
const tbody = this.element.querySelector('tbody');
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
const pageData = this.filteredData.slice(startIndex, endIndex);
// Clear tbody
tbody.innerHTML = '';
// Add rows
pageData.forEach(rowData => {
const row = document.createElement('tr');
rowData.forEach(cellData => {
const cell = document.createElement('td');
cell.textContent = cellData;
row.appendChild(cell);
});
tbody.appendChild(row);
});
// Update pagination
if (this.options.pagination) {
this.updatePagination();
}
// Update info
this.updateInfo();
}
updatePagination() {
const totalPages = Math.ceil(this.filteredData.length / this.options.pageSize);
const paginationWrapper = this.wrapper.querySelector('.datatable-pagination');
paginationWrapper.innerHTML = '';
if (totalPages <= 1) return;
// Previous button
const prevBtn = document.createElement('button');
prevBtn.textContent = 'Previous';
prevBtn.disabled = this.currentPage === 1;
prevBtn.addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.render();
}
});
paginationWrapper.appendChild(prevBtn);
// Page numbers
for (let i = 1; i <= totalPages; i++) {
const pageBtn = document.createElement('button');
pageBtn.textContent = i;
pageBtn.classList.toggle('active', i === this.currentPage);
pageBtn.addEventListener('click', () => {
this.currentPage = i;
this.render();
});
paginationWrapper.appendChild(pageBtn);
}
// Next button
const nextBtn = document.createElement('button');
nextBtn.textContent = 'Next';
nextBtn.disabled = this.currentPage === totalPages;
nextBtn.addEventListener('click', () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.render();
}
});
paginationWrapper.appendChild(nextBtn);
}
updateInfo() {
const infoWrapper = this.wrapper.querySelector('.datatable-info');
if (!infoWrapper) return;
const startIndex = (this.currentPage - 1) * this.options.pageSize + 1;
const endIndex = Math.min(startIndex + this.options.pageSize - 1, this.filteredData.length);
const total = this.filteredData.length;
infoWrapper.textContent = `Showing ${startIndex} to ${endIndex} of ${total} entries`;
}
destroy() {
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
}
}
}
// Initialize DataTable
const initializeDataTable = () => {
const tableElement = document.getElementById('dataTable');
if (tableElement) {
// Clean up existing instance
if (tableElement.dataTableInstance) {
tableElement.dataTableInstance.destroy();
}
// Create new instance
const dataTable = new VanillaDataTable(tableElement, {
sortable: true,
searchable: true,
pagination: true,
pageSize: 10
});
// Store instance for cleanup
tableElement.dataTableInstance = dataTable;
}
};
// Initialize on load
initializeDataTable();
// Reinitialize on theme change
window.addEventListener('adminator:themeChanged', () => {
setTimeout(initializeDataTable, 100);
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
const tableElement = document.getElementById('dataTable');
if (tableElement && tableElement.dataTableInstance) {
tableElement.dataTableInstance.destroy();
}
});
// Return public API
return {
init: initializeDataTable,
VanillaDataTable
};
})());
;// ../assets/scripts/index.js
/**
* Adminator Admin Template
* Modern Entry Point - Phase 2 Modernization
*/
// Import the modern application
// Legacy imports that haven't been modernized yet
// These will be gradually replaced in future iterations
// import './datepicker'; // REMOVED: Replaced with modern day.js implementation in app.js
// Note: The following have been modernized and are now handled by app.js:
// - sidebar (now Sidebar component)
// - charts (now ChartComponent using Chart.js instead of jQuery Sparkline)
// - Basic DOM utilities (now DOM utils)
})();
/******/ })()
;