index.js 89 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141
  1. import { m as mergeProps, g as guid, i as isArraysEqual, T as Theme, a as mapHash, B as BaseComponent, V as ViewContextType, C as ContentContainer, b as buildViewClassNames, c as greatestDurationDenominator, d as createDuration, e as BASE_OPTION_DEFAULTS, f as arrayToHash, h as filterHash, j as buildEventSourceRefiners, p as parseEventSource, k as formatWithOrdinals, u as unpromisify, l as buildRangeApiWithTimeZone, n as identity, r as requestJson, s as subtractDurations, o as intersectRanges, q as startOfDay, t as addDays, v as hashValuesToArray, w as buildEventApis, D as DelayedRunner, x as createFormatter, y as diffWholeDays, z as memoize, A as memoizeObjArg, E as isPropsEqual, F as Emitter, G as getInitialDate, H as rangeContainsMarker, I as createEmptyEventStore, J as reduceCurrentDate, K as reduceEventStore, L as rezoneEventStoreDates, M as mergeRawOptions, N as BASE_OPTION_REFINERS, O as CALENDAR_LISTENER_REFINERS, P as CALENDAR_OPTION_REFINERS, Q as COMPLEX_OPTION_COMPARATORS, R as VIEW_OPTION_REFINERS, S as DateEnv, U as DateProfileGenerator, W as createEventUi, X as parseBusinessHours, Y as setRef, Z as Interaction, _ as getElSeg, $ as elementClosest, a0 as EventImpl, a1 as listenBySelector, a2 as listenToHoverBySelector, a3 as PureComponent, a4 as buildViewContext, a5 as getUniqueDomId, a6 as parseInteractionSettings, a7 as interactionSettingsStore, a8 as getNow, a9 as CalendarImpl, aa as flushSync, ab as CalendarRoot, ac as RenderId, ad as ensureElHasStyles, ae as applyStyleProp, af as sliceEventStore } from './internal-common.js';
  2. export { ag as JsonRequestError } from './internal-common.js';
  3. import { createElement, createRef, Fragment, render } from 'preact';
  4. import 'preact/compat';
  5. const globalLocales = [];
  6. const MINIMAL_RAW_EN_LOCALE = {
  7. code: 'en',
  8. week: {
  9. dow: 0,
  10. doy: 4, // 4 days need to be within the year to be considered the first week
  11. },
  12. direction: 'ltr',
  13. buttonText: {
  14. prev: 'prev',
  15. next: 'next',
  16. prevYear: 'prev year',
  17. nextYear: 'next year',
  18. year: 'year',
  19. today: 'today',
  20. month: 'month',
  21. week: 'week',
  22. day: 'day',
  23. list: 'list',
  24. },
  25. weekText: 'W',
  26. weekTextLong: 'Week',
  27. closeHint: 'Close',
  28. timeHint: 'Time',
  29. eventHint: 'Event',
  30. allDayText: 'all-day',
  31. moreLinkText: 'more',
  32. noEventsText: 'No events to display',
  33. };
  34. const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), {
  35. // Includes things we don't want other locales to inherit,
  36. // things that derive from other translatable strings.
  37. buttonHints: {
  38. prev: 'Previous $0',
  39. next: 'Next $0',
  40. today(buttonText, unit) {
  41. return (unit === 'day')
  42. ? 'Today'
  43. : `This ${buttonText}`;
  44. },
  45. }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) {
  46. return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`;
  47. } });
  48. function organizeRawLocales(explicitRawLocales) {
  49. let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en';
  50. let allRawLocales = globalLocales.concat(explicitRawLocales);
  51. let rawLocaleMap = {
  52. en: RAW_EN_LOCALE,
  53. };
  54. for (let rawLocale of allRawLocales) {
  55. rawLocaleMap[rawLocale.code] = rawLocale;
  56. }
  57. return {
  58. map: rawLocaleMap,
  59. defaultCode,
  60. };
  61. }
  62. function buildLocale(inputSingular, available) {
  63. if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) {
  64. return parseLocale(inputSingular.code, [inputSingular.code], inputSingular);
  65. }
  66. return queryLocale(inputSingular, available);
  67. }
  68. function queryLocale(codeArg, available) {
  69. let codes = [].concat(codeArg || []); // will convert to array
  70. let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE;
  71. return parseLocale(codeArg, codes, raw);
  72. }
  73. function queryRawLocale(codes, available) {
  74. for (let i = 0; i < codes.length; i += 1) {
  75. let parts = codes[i].toLocaleLowerCase().split('-');
  76. for (let j = parts.length; j > 0; j -= 1) {
  77. let simpleId = parts.slice(0, j).join('-');
  78. if (available[simpleId]) {
  79. return available[simpleId];
  80. }
  81. }
  82. }
  83. return null;
  84. }
  85. function parseLocale(codeArg, codes, raw) {
  86. let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']);
  87. delete merged.code; // don't want this part of the options
  88. let { week } = merged;
  89. delete merged.week;
  90. return {
  91. codeArg,
  92. codes,
  93. week,
  94. simpleNumberFormat: new Intl.NumberFormat(codeArg),
  95. options: merged,
  96. };
  97. }
  98. // TODO: easier way to add new hooks? need to update a million things
  99. function createPlugin(input) {
  100. return {
  101. id: guid(),
  102. name: input.name,
  103. premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
  104. deps: input.deps || [],
  105. reducers: input.reducers || [],
  106. isLoadingFuncs: input.isLoadingFuncs || [],
  107. contextInit: [].concat(input.contextInit || []),
  108. eventRefiners: input.eventRefiners || {},
  109. eventDefMemberAdders: input.eventDefMemberAdders || [],
  110. eventSourceRefiners: input.eventSourceRefiners || {},
  111. isDraggableTransformers: input.isDraggableTransformers || [],
  112. eventDragMutationMassagers: input.eventDragMutationMassagers || [],
  113. eventDefMutationAppliers: input.eventDefMutationAppliers || [],
  114. dateSelectionTransformers: input.dateSelectionTransformers || [],
  115. datePointTransforms: input.datePointTransforms || [],
  116. dateSpanTransforms: input.dateSpanTransforms || [],
  117. views: input.views || {},
  118. viewPropsTransformers: input.viewPropsTransformers || [],
  119. isPropsValid: input.isPropsValid || null,
  120. externalDefTransforms: input.externalDefTransforms || [],
  121. viewContainerAppends: input.viewContainerAppends || [],
  122. eventDropTransformers: input.eventDropTransformers || [],
  123. componentInteractions: input.componentInteractions || [],
  124. calendarInteractions: input.calendarInteractions || [],
  125. themeClasses: input.themeClasses || {},
  126. eventSourceDefs: input.eventSourceDefs || [],
  127. cmdFormatter: input.cmdFormatter,
  128. recurringTypes: input.recurringTypes || [],
  129. namedTimeZonedImpl: input.namedTimeZonedImpl,
  130. initialView: input.initialView || '',
  131. elementDraggingImpl: input.elementDraggingImpl,
  132. optionChangeHandlers: input.optionChangeHandlers || {},
  133. scrollGridImpl: input.scrollGridImpl || null,
  134. listenerRefiners: input.listenerRefiners || {},
  135. optionRefiners: input.optionRefiners || {},
  136. propSetHandlers: input.propSetHandlers || {},
  137. };
  138. }
  139. function buildPluginHooks(pluginDefs, globalDefs) {
  140. let currentPluginIds = {};
  141. let hooks = {
  142. premiumReleaseDate: undefined,
  143. reducers: [],
  144. isLoadingFuncs: [],
  145. contextInit: [],
  146. eventRefiners: {},
  147. eventDefMemberAdders: [],
  148. eventSourceRefiners: {},
  149. isDraggableTransformers: [],
  150. eventDragMutationMassagers: [],
  151. eventDefMutationAppliers: [],
  152. dateSelectionTransformers: [],
  153. datePointTransforms: [],
  154. dateSpanTransforms: [],
  155. views: {},
  156. viewPropsTransformers: [],
  157. isPropsValid: null,
  158. externalDefTransforms: [],
  159. viewContainerAppends: [],
  160. eventDropTransformers: [],
  161. componentInteractions: [],
  162. calendarInteractions: [],
  163. themeClasses: {},
  164. eventSourceDefs: [],
  165. cmdFormatter: null,
  166. recurringTypes: [],
  167. namedTimeZonedImpl: null,
  168. initialView: '',
  169. elementDraggingImpl: null,
  170. optionChangeHandlers: {},
  171. scrollGridImpl: null,
  172. listenerRefiners: {},
  173. optionRefiners: {},
  174. propSetHandlers: {},
  175. };
  176. function addDefs(defs) {
  177. for (let def of defs) {
  178. const pluginName = def.name;
  179. const currentId = currentPluginIds[pluginName];
  180. if (currentId === undefined) {
  181. currentPluginIds[pluginName] = def.id;
  182. addDefs(def.deps);
  183. hooks = combineHooks(hooks, def);
  184. }
  185. else if (currentId !== def.id) {
  186. // different ID than the one already added
  187. console.warn(`Duplicate plugin '${pluginName}'`);
  188. }
  189. }
  190. }
  191. if (pluginDefs) {
  192. addDefs(pluginDefs);
  193. }
  194. addDefs(globalDefs);
  195. return hooks;
  196. }
  197. function buildBuildPluginHooks() {
  198. let currentOverrideDefs = [];
  199. let currentGlobalDefs = [];
  200. let currentHooks;
  201. return (overrideDefs, globalDefs) => {
  202. if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
  203. currentHooks = buildPluginHooks(overrideDefs, globalDefs);
  204. }
  205. currentOverrideDefs = overrideDefs;
  206. currentGlobalDefs = globalDefs;
  207. return currentHooks;
  208. };
  209. }
  210. function combineHooks(hooks0, hooks1) {
  211. return {
  212. premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
  213. reducers: hooks0.reducers.concat(hooks1.reducers),
  214. isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
  215. contextInit: hooks0.contextInit.concat(hooks1.contextInit),
  216. eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners),
  217. eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
  218. eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners),
  219. isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
  220. eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
  221. eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
  222. dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
  223. datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
  224. dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
  225. views: Object.assign(Object.assign({}, hooks0.views), hooks1.views),
  226. viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
  227. isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
  228. externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
  229. viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
  230. eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
  231. calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
  232. componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
  233. themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses),
  234. eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
  235. cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
  236. recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
  237. namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl,
  238. initialView: hooks0.initialView || hooks1.initialView,
  239. elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl,
  240. optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers),
  241. scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl,
  242. listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners),
  243. optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners),
  244. propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers),
  245. };
  246. }
  247. function compareOptionalDates(date0, date1) {
  248. if (date0 === undefined) {
  249. return date1;
  250. }
  251. if (date1 === undefined) {
  252. return date0;
  253. }
  254. return new Date(Math.max(date0.valueOf(), date1.valueOf()));
  255. }
  256. class StandardTheme extends Theme {
  257. }
  258. StandardTheme.prototype.classes = {
  259. root: 'fc-theme-standard',
  260. tableCellShaded: 'fc-cell-shaded',
  261. buttonGroup: 'fc-button-group',
  262. button: 'fc-button fc-button-primary',
  263. buttonActive: 'fc-button-active',
  264. };
  265. StandardTheme.prototype.baseIconClass = 'fc-icon';
  266. StandardTheme.prototype.iconClasses = {
  267. close: 'fc-icon-x',
  268. prev: 'fc-icon-chevron-left',
  269. next: 'fc-icon-chevron-right',
  270. prevYear: 'fc-icon-chevrons-left',
  271. nextYear: 'fc-icon-chevrons-right',
  272. };
  273. StandardTheme.prototype.rtlIconClasses = {
  274. prev: 'fc-icon-chevron-right',
  275. next: 'fc-icon-chevron-left',
  276. prevYear: 'fc-icon-chevrons-right',
  277. nextYear: 'fc-icon-chevrons-left',
  278. };
  279. StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly
  280. StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon';
  281. StandardTheme.prototype.iconOverridePrefix = 'fc-icon-';
  282. function compileViewDefs(defaultConfigs, overrideConfigs) {
  283. let hash = {};
  284. let viewType;
  285. for (viewType in defaultConfigs) {
  286. ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
  287. }
  288. for (viewType in overrideConfigs) {
  289. ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
  290. }
  291. return hash;
  292. }
  293. function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
  294. if (hash[viewType]) {
  295. return hash[viewType];
  296. }
  297. let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
  298. if (viewDef) {
  299. hash[viewType] = viewDef;
  300. }
  301. return viewDef;
  302. }
  303. function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
  304. let defaultConfig = defaultConfigs[viewType];
  305. let overrideConfig = overrideConfigs[viewType];
  306. let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
  307. ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
  308. let theComponent = queryProp('component');
  309. let superType = queryProp('superType');
  310. let superDef = null;
  311. if (superType) {
  312. if (superType === viewType) {
  313. throw new Error('Can\'t have a custom view type that references itself');
  314. }
  315. superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
  316. }
  317. if (!theComponent && superDef) {
  318. theComponent = superDef.component;
  319. }
  320. if (!theComponent) {
  321. return null; // don't throw a warning, might be settings for a single-unit view
  322. }
  323. return {
  324. type: viewType,
  325. component: theComponent,
  326. defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})),
  327. overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})),
  328. };
  329. }
  330. function parseViewConfigs(inputs) {
  331. return mapHash(inputs, parseViewConfig);
  332. }
  333. function parseViewConfig(input) {
  334. let rawOptions = typeof input === 'function' ?
  335. { component: input } :
  336. input;
  337. let { component } = rawOptions;
  338. if (rawOptions.content) {
  339. // TODO: remove content/classNames/didMount/etc from options?
  340. component = createViewHookComponent(rawOptions);
  341. }
  342. else if (component && !(component.prototype instanceof BaseComponent)) {
  343. // WHY?: people were using `component` property for `content`
  344. // TODO: converge on one setting name
  345. component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component }));
  346. }
  347. return {
  348. superType: rawOptions.type,
  349. component: component,
  350. rawOptions, // includes type and component too :(
  351. };
  352. }
  353. function createViewHookComponent(options) {
  354. return (viewProps) => (createElement(ViewContextType.Consumer, null, (context) => (createElement(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 }))));
  355. }
  356. function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
  357. let defaultConfigs = parseViewConfigs(defaultInputs);
  358. let overrideConfigs = parseViewConfigs(optionOverrides.views);
  359. let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
  360. return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults));
  361. }
  362. function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
  363. let durationInput = viewDef.overrides.duration ||
  364. viewDef.defaults.duration ||
  365. dynamicOptionOverrides.duration ||
  366. optionOverrides.duration;
  367. let duration = null;
  368. let durationUnit = '';
  369. let singleUnit = '';
  370. let singleUnitOverrides = {};
  371. if (durationInput) {
  372. duration = createDurationCached(durationInput);
  373. if (duration) { // valid?
  374. let denom = greatestDurationDenominator(duration);
  375. durationUnit = denom.unit;
  376. if (denom.value === 1) {
  377. singleUnit = durationUnit;
  378. singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
  379. }
  380. }
  381. }
  382. let queryButtonText = (optionsSubset) => {
  383. let buttonTextMap = optionsSubset.buttonText || {};
  384. let buttonTextKey = viewDef.defaults.buttonTextKey;
  385. if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) {
  386. return buttonTextMap[buttonTextKey];
  387. }
  388. if (buttonTextMap[viewDef.type] != null) {
  389. return buttonTextMap[viewDef.type];
  390. }
  391. if (buttonTextMap[singleUnit] != null) {
  392. return buttonTextMap[singleUnit];
  393. }
  394. return null;
  395. };
  396. let queryButtonTitle = (optionsSubset) => {
  397. let buttonHints = optionsSubset.buttonHints || {};
  398. let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text
  399. if (buttonKey != null && buttonHints[buttonKey] != null) {
  400. return buttonHints[buttonKey];
  401. }
  402. if (buttonHints[viewDef.type] != null) {
  403. return buttonHints[viewDef.type];
  404. }
  405. if (buttonHints[singleUnit] != null) {
  406. return buttonHints[singleUnit];
  407. }
  408. return null;
  409. };
  410. return {
  411. type: viewDef.type,
  412. component: viewDef.component,
  413. duration,
  414. durationUnit,
  415. singleUnit,
  416. optionDefaults: viewDef.defaults,
  417. optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides),
  418. buttonTextOverride: queryButtonText(dynamicOptionOverrides) ||
  419. queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence
  420. viewDef.overrides.buttonText,
  421. buttonTextDefault: queryButtonText(localeDefaults) ||
  422. viewDef.defaults.buttonText ||
  423. queryButtonText(BASE_OPTION_DEFAULTS) ||
  424. viewDef.type,
  425. // not DRY
  426. buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) ||
  427. queryButtonTitle(optionOverrides) ||
  428. viewDef.overrides.buttonHint,
  429. buttonTitleDefault: queryButtonTitle(localeDefaults) ||
  430. viewDef.defaults.buttonHint ||
  431. queryButtonTitle(BASE_OPTION_DEFAULTS),
  432. // will eventually fall back to buttonText
  433. };
  434. }
  435. // hack to get memoization working
  436. let durationInputMap = {};
  437. function createDurationCached(durationInput) {
  438. let json = JSON.stringify(durationInput);
  439. let res = durationInputMap[json];
  440. if (res === undefined) {
  441. res = createDuration(durationInput);
  442. durationInputMap[json] = res;
  443. }
  444. return res;
  445. }
  446. function reduceViewType(viewType, action) {
  447. switch (action.type) {
  448. case 'CHANGE_VIEW_TYPE':
  449. viewType = action.viewType;
  450. }
  451. return viewType;
  452. }
  453. function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
  454. switch (action.type) {
  455. case 'SET_OPTION':
  456. return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue });
  457. default:
  458. return dynamicOptionOverrides;
  459. }
  460. }
  461. function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) {
  462. let dp;
  463. switch (action.type) {
  464. case 'CHANGE_VIEW_TYPE':
  465. return dateProfileGenerator.build(action.dateMarker || currentDate);
  466. case 'CHANGE_DATE':
  467. return dateProfileGenerator.build(action.dateMarker);
  468. case 'PREV':
  469. dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate);
  470. if (dp.isValid) {
  471. return dp;
  472. }
  473. break;
  474. case 'NEXT':
  475. dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate);
  476. if (dp.isValid) {
  477. return dp;
  478. }
  479. break;
  480. }
  481. return currentDateProfile;
  482. }
  483. function initEventSources(calendarOptions, dateProfile, context) {
  484. let activeRange = dateProfile ? dateProfile.activeRange : null;
  485. return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context);
  486. }
  487. function reduceEventSources(eventSources, action, dateProfile, context) {
  488. let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
  489. switch (action.type) {
  490. case 'ADD_EVENT_SOURCES': // already parsed
  491. return addSources(eventSources, action.sources, activeRange, context);
  492. case 'REMOVE_EVENT_SOURCE':
  493. return removeSource(eventSources, action.sourceId);
  494. case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
  495. case 'NEXT':
  496. case 'CHANGE_DATE':
  497. case 'CHANGE_VIEW_TYPE':
  498. if (dateProfile) {
  499. return fetchDirtySources(eventSources, activeRange, context);
  500. }
  501. return eventSources;
  502. case 'FETCH_EVENT_SOURCES':
  503. return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type?
  504. arrayToHash(action.sourceIds) :
  505. excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context);
  506. case 'RECEIVE_EVENTS':
  507. case 'RECEIVE_EVENT_ERROR':
  508. return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange);
  509. case 'REMOVE_ALL_EVENT_SOURCES':
  510. return {};
  511. default:
  512. return eventSources;
  513. }
  514. }
  515. function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) {
  516. let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
  517. return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context);
  518. }
  519. function computeEventSourcesLoading(eventSources) {
  520. for (let sourceId in eventSources) {
  521. if (eventSources[sourceId].isFetching) {
  522. return true;
  523. }
  524. }
  525. return false;
  526. }
  527. function addSources(eventSourceHash, sources, fetchRange, context) {
  528. let hash = {};
  529. for (let source of sources) {
  530. hash[source.sourceId] = source;
  531. }
  532. if (fetchRange) {
  533. hash = fetchDirtySources(hash, fetchRange, context);
  534. }
  535. return Object.assign(Object.assign({}, eventSourceHash), hash);
  536. }
  537. function removeSource(eventSourceHash, sourceId) {
  538. return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId);
  539. }
  540. function fetchDirtySources(sourceHash, fetchRange, context) {
  541. return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context);
  542. }
  543. function isSourceDirty(eventSource, fetchRange, context) {
  544. if (!doesSourceNeedRange(eventSource, context)) {
  545. return !eventSource.latestFetchId;
  546. }
  547. return !context.options.lazyFetching ||
  548. !eventSource.fetchRange ||
  549. eventSource.isFetching || // always cancel outdated in-progress fetches
  550. fetchRange.start < eventSource.fetchRange.start ||
  551. fetchRange.end > eventSource.fetchRange.end;
  552. }
  553. function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) {
  554. let nextSources = {};
  555. for (let sourceId in prevSources) {
  556. let source = prevSources[sourceId];
  557. if (sourceIdHash[sourceId]) {
  558. nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context);
  559. }
  560. else {
  561. nextSources[sourceId] = source;
  562. }
  563. }
  564. return nextSources;
  565. }
  566. function fetchSource(eventSource, fetchRange, isRefetch, context) {
  567. let { options, calendarApi } = context;
  568. let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId];
  569. let fetchId = guid();
  570. sourceDef.fetch({
  571. eventSource,
  572. range: fetchRange,
  573. isRefetch,
  574. context,
  575. }, (res) => {
  576. let { rawEvents } = res;
  577. if (options.eventSourceSuccess) {
  578. rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents;
  579. }
  580. if (eventSource.success) {
  581. rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents;
  582. }
  583. context.dispatch({
  584. type: 'RECEIVE_EVENTS',
  585. sourceId: eventSource.sourceId,
  586. fetchId,
  587. fetchRange,
  588. rawEvents,
  589. });
  590. }, (error) => {
  591. let errorHandled = false;
  592. if (options.eventSourceFailure) {
  593. options.eventSourceFailure.call(calendarApi, error);
  594. errorHandled = true;
  595. }
  596. if (eventSource.failure) {
  597. eventSource.failure(error);
  598. errorHandled = true;
  599. }
  600. if (!errorHandled) {
  601. console.warn(error.message, error);
  602. }
  603. context.dispatch({
  604. type: 'RECEIVE_EVENT_ERROR',
  605. sourceId: eventSource.sourceId,
  606. fetchId,
  607. fetchRange,
  608. error,
  609. });
  610. });
  611. return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId });
  612. }
  613. function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) {
  614. let eventSource = sourceHash[sourceId];
  615. if (eventSource && // not already removed
  616. fetchId === eventSource.latestFetchId) {
  617. return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) });
  618. }
  619. return sourceHash;
  620. }
  621. function excludeStaticSources(eventSources, context) {
  622. return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context));
  623. }
  624. function parseInitialSources(rawOptions, context) {
  625. let refiners = buildEventSourceRefiners(context);
  626. let rawSources = [].concat(rawOptions.eventSources || []);
  627. let sources = []; // parsed
  628. if (rawOptions.initialEvents) {
  629. rawSources.unshift(rawOptions.initialEvents);
  630. }
  631. if (rawOptions.events) {
  632. rawSources.unshift(rawOptions.events);
  633. }
  634. for (let rawSource of rawSources) {
  635. let source = parseEventSource(rawSource, context, refiners);
  636. if (source) {
  637. sources.push(source);
  638. }
  639. }
  640. return sources;
  641. }
  642. function doesSourceNeedRange(eventSource, context) {
  643. let defs = context.pluginHooks.eventSourceDefs;
  644. return !defs[eventSource.sourceDefId].ignoreRange;
  645. }
  646. function reduceDateSelection(currentSelection, action) {
  647. switch (action.type) {
  648. case 'UNSELECT_DATES':
  649. return null;
  650. case 'SELECT_DATES':
  651. return action.selection;
  652. default:
  653. return currentSelection;
  654. }
  655. }
  656. function reduceSelectedEvent(currentInstanceId, action) {
  657. switch (action.type) {
  658. case 'UNSELECT_EVENT':
  659. return '';
  660. case 'SELECT_EVENT':
  661. return action.eventInstanceId;
  662. default:
  663. return currentInstanceId;
  664. }
  665. }
  666. function reduceEventDrag(currentDrag, action) {
  667. let newDrag;
  668. switch (action.type) {
  669. case 'UNSET_EVENT_DRAG':
  670. return null;
  671. case 'SET_EVENT_DRAG':
  672. newDrag = action.state;
  673. return {
  674. affectedEvents: newDrag.affectedEvents,
  675. mutatedEvents: newDrag.mutatedEvents,
  676. isEvent: newDrag.isEvent,
  677. };
  678. default:
  679. return currentDrag;
  680. }
  681. }
  682. function reduceEventResize(currentResize, action) {
  683. let newResize;
  684. switch (action.type) {
  685. case 'UNSET_EVENT_RESIZE':
  686. return null;
  687. case 'SET_EVENT_RESIZE':
  688. newResize = action.state;
  689. return {
  690. affectedEvents: newResize.affectedEvents,
  691. mutatedEvents: newResize.mutatedEvents,
  692. isEvent: newResize.isEvent,
  693. };
  694. default:
  695. return currentResize;
  696. }
  697. }
  698. function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
  699. let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
  700. let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
  701. return { header, footer };
  702. }
  703. function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
  704. let sectionWidgets = {};
  705. let viewsWithButtons = [];
  706. let hasTitle = false;
  707. for (let sectionName in sectionStrHash) {
  708. let sectionStr = sectionStrHash[sectionName];
  709. let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi);
  710. sectionWidgets[sectionName] = sectionRes.widgets;
  711. viewsWithButtons.push(...sectionRes.viewsWithButtons);
  712. hasTitle = hasTitle || sectionRes.hasTitle;
  713. }
  714. return { sectionWidgets, viewsWithButtons, hasTitle };
  715. }
  716. /*
  717. BAD: querying icons and text here. should be done at render time
  718. */
  719. function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined
  720. calendarOptionOverrides, // overrides only!, unrefined :(
  721. theme, viewSpecs, calendarApi) {
  722. let isRtl = calendarOptions.direction === 'rtl';
  723. let calendarCustomButtons = calendarOptions.customButtons || {};
  724. let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {};
  725. let calendarButtonText = calendarOptions.buttonText || {};
  726. let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {};
  727. let calendarButtonHints = calendarOptions.buttonHints || {};
  728. let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
  729. let viewsWithButtons = [];
  730. let hasTitle = false;
  731. let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => {
  732. if (buttonName === 'title') {
  733. hasTitle = true;
  734. return { buttonName };
  735. }
  736. let customButtonProps;
  737. let viewSpec;
  738. let buttonClick;
  739. let buttonIcon; // only one of these will be set
  740. let buttonText; // "
  741. let buttonHint;
  742. // ^ for the title="" attribute, for accessibility
  743. if ((customButtonProps = calendarCustomButtons[buttonName])) {
  744. buttonClick = (ev) => {
  745. if (customButtonProps.click) {
  746. customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context?
  747. }
  748. };
  749. (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) ||
  750. (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
  751. (buttonText = customButtonProps.text);
  752. buttonHint = customButtonProps.hint || customButtonProps.text;
  753. }
  754. else if ((viewSpec = viewSpecs[buttonName])) {
  755. viewsWithButtons.push(buttonName);
  756. buttonClick = () => {
  757. calendarApi.changeView(buttonName);
  758. };
  759. (buttonText = viewSpec.buttonTextOverride) ||
  760. (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
  761. (buttonText = viewSpec.buttonTextDefault);
  762. let textFallback = viewSpec.buttonTextOverride ||
  763. viewSpec.buttonTextDefault;
  764. buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride ||
  765. viewSpec.buttonTitleDefault ||
  766. calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName
  767. textFallback);
  768. }
  769. else if (calendarApi[buttonName]) { // a calendarApi method
  770. buttonClick = () => {
  771. calendarApi[buttonName]();
  772. };
  773. (buttonText = calendarButtonTextOverrides[buttonName]) ||
  774. (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
  775. (buttonText = calendarButtonText[buttonName]); // everything else is considered default
  776. if (buttonName === 'prevYear' || buttonName === 'nextYear') {
  777. let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next';
  778. buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] ||
  779. calendarButtonHints[prevOrNext], [
  780. calendarButtonText.year || 'year',
  781. 'year',
  782. ], calendarButtonText[buttonName]);
  783. }
  784. else {
  785. buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] ||
  786. calendarButtonHints[buttonName], [
  787. calendarButtonText[navUnit] || navUnit,
  788. navUnit,
  789. ], calendarButtonText[buttonName]);
  790. }
  791. }
  792. return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint };
  793. })));
  794. return { widgets, viewsWithButtons, hasTitle };
  795. }
  796. // always represents the current view. otherwise, it'd need to change value every time date changes
  797. class ViewImpl {
  798. constructor(type, getCurrentData, dateEnv) {
  799. this.type = type;
  800. this.getCurrentData = getCurrentData;
  801. this.dateEnv = dateEnv;
  802. }
  803. get calendar() {
  804. return this.getCurrentData().calendarApi;
  805. }
  806. get title() {
  807. return this.getCurrentData().viewTitle;
  808. }
  809. get activeStart() {
  810. return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
  811. }
  812. get activeEnd() {
  813. return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
  814. }
  815. get currentStart() {
  816. return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
  817. }
  818. get currentEnd() {
  819. return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
  820. }
  821. getOption(name) {
  822. return this.getCurrentData().options[name]; // are the view-specific options
  823. }
  824. }
  825. let eventSourceDef$2 = {
  826. ignoreRange: true,
  827. parseMeta(refined) {
  828. if (Array.isArray(refined.events)) {
  829. return refined.events;
  830. }
  831. return null;
  832. },
  833. fetch(arg, successCallback) {
  834. successCallback({
  835. rawEvents: arg.eventSource.meta,
  836. });
  837. },
  838. };
  839. const arrayEventSourcePlugin = createPlugin({
  840. name: 'array-event-source',
  841. eventSourceDefs: [eventSourceDef$2],
  842. });
  843. let eventSourceDef$1 = {
  844. parseMeta(refined) {
  845. if (typeof refined.events === 'function') {
  846. return refined.events;
  847. }
  848. return null;
  849. },
  850. fetch(arg, successCallback, errorCallback) {
  851. const { dateEnv } = arg.context;
  852. const func = arg.eventSource.meta;
  853. unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback);
  854. },
  855. };
  856. const funcEventSourcePlugin = createPlugin({
  857. name: 'func-event-source',
  858. eventSourceDefs: [eventSourceDef$1],
  859. });
  860. const JSON_FEED_EVENT_SOURCE_REFINERS = {
  861. method: String,
  862. extraParams: identity,
  863. startParam: String,
  864. endParam: String,
  865. timeZoneParam: String,
  866. };
  867. let eventSourceDef = {
  868. parseMeta(refined) {
  869. if (refined.url && (refined.format === 'json' || !refined.format)) {
  870. return {
  871. url: refined.url,
  872. format: 'json',
  873. method: (refined.method || 'GET').toUpperCase(),
  874. extraParams: refined.extraParams,
  875. startParam: refined.startParam,
  876. endParam: refined.endParam,
  877. timeZoneParam: refined.timeZoneParam,
  878. };
  879. }
  880. return null;
  881. },
  882. fetch(arg, successCallback, errorCallback) {
  883. const { meta } = arg.eventSource;
  884. const requestParams = buildRequestParams(meta, arg.range, arg.context);
  885. requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => {
  886. successCallback({ rawEvents, response });
  887. }, errorCallback);
  888. },
  889. };
  890. const jsonFeedEventSourcePlugin = createPlugin({
  891. name: 'json-event-source',
  892. eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS,
  893. eventSourceDefs: [eventSourceDef],
  894. });
  895. function buildRequestParams(meta, range, context) {
  896. let { dateEnv, options } = context;
  897. let startParam;
  898. let endParam;
  899. let timeZoneParam;
  900. let customRequestParams;
  901. let params = {};
  902. startParam = meta.startParam;
  903. if (startParam == null) {
  904. startParam = options.startParam;
  905. }
  906. endParam = meta.endParam;
  907. if (endParam == null) {
  908. endParam = options.endParam;
  909. }
  910. timeZoneParam = meta.timeZoneParam;
  911. if (timeZoneParam == null) {
  912. timeZoneParam = options.timeZoneParam;
  913. }
  914. // retrieve any outbound GET/POST data from the options
  915. if (typeof meta.extraParams === 'function') {
  916. // supplied as a function that returns a key/value object
  917. customRequestParams = meta.extraParams();
  918. }
  919. else {
  920. // probably supplied as a straight key/value object
  921. customRequestParams = meta.extraParams || {};
  922. }
  923. Object.assign(params, customRequestParams);
  924. params[startParam] = dateEnv.formatIso(range.start);
  925. params[endParam] = dateEnv.formatIso(range.end);
  926. if (dateEnv.timeZone !== 'local') {
  927. params[timeZoneParam] = dateEnv.timeZone;
  928. }
  929. return params;
  930. }
  931. const SIMPLE_RECURRING_REFINERS = {
  932. daysOfWeek: identity,
  933. startTime: createDuration,
  934. endTime: createDuration,
  935. duration: createDuration,
  936. startRecur: identity,
  937. endRecur: identity,
  938. };
  939. let recurring = {
  940. parse(refined, dateEnv) {
  941. if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) {
  942. let recurringData = {
  943. daysOfWeek: refined.daysOfWeek || null,
  944. startTime: refined.startTime || null,
  945. endTime: refined.endTime || null,
  946. startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null,
  947. endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null,
  948. };
  949. let duration;
  950. if (refined.duration) {
  951. duration = refined.duration;
  952. }
  953. if (!duration && refined.startTime && refined.endTime) {
  954. duration = subtractDurations(refined.endTime, refined.startTime);
  955. }
  956. return {
  957. allDayGuess: Boolean(!refined.startTime && !refined.endTime),
  958. duration,
  959. typeData: recurringData, // doesn't need endTime anymore but oh well
  960. };
  961. }
  962. return null;
  963. },
  964. expand(typeData, framingRange, dateEnv) {
  965. let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur });
  966. if (clippedFramingRange) {
  967. return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv);
  968. }
  969. return [];
  970. },
  971. };
  972. const simpleRecurringEventsPlugin = createPlugin({
  973. name: 'simple-recurring-event',
  974. recurringTypes: [recurring],
  975. eventRefiners: SIMPLE_RECURRING_REFINERS,
  976. });
  977. function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) {
  978. let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null;
  979. let dayMarker = startOfDay(framingRange.start);
  980. let endMarker = framingRange.end;
  981. let instanceStarts = [];
  982. while (dayMarker < endMarker) {
  983. let instanceStart;
  984. // if everyday, or this particular day-of-week
  985. if (!dowHash || dowHash[dayMarker.getUTCDay()]) {
  986. if (startTime) {
  987. instanceStart = dateEnv.add(dayMarker, startTime);
  988. }
  989. else {
  990. instanceStart = dayMarker;
  991. }
  992. instanceStarts.push(instanceStart);
  993. }
  994. dayMarker = addDays(dayMarker, 1);
  995. }
  996. return instanceStarts;
  997. }
  998. const changeHandlerPlugin = createPlugin({
  999. name: 'change-handler',
  1000. optionChangeHandlers: {
  1001. events(events, context) {
  1002. handleEventSources([events], context);
  1003. },
  1004. eventSources: handleEventSources,
  1005. },
  1006. });
  1007. /*
  1008. BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out
  1009. */
  1010. function handleEventSources(inputs, context) {
  1011. let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources);
  1012. if (unfoundSources.length === 1 &&
  1013. inputs.length === 1 &&
  1014. Array.isArray(unfoundSources[0]._raw) &&
  1015. Array.isArray(inputs[0])) {
  1016. context.dispatch({
  1017. type: 'RESET_RAW_EVENTS',
  1018. sourceId: unfoundSources[0].sourceId,
  1019. rawEvents: inputs[0],
  1020. });
  1021. return;
  1022. }
  1023. let newInputs = [];
  1024. for (let input of inputs) {
  1025. let inputFound = false;
  1026. for (let i = 0; i < unfoundSources.length; i += 1) {
  1027. if (unfoundSources[i]._raw === input) {
  1028. unfoundSources.splice(i, 1); // delete
  1029. inputFound = true;
  1030. break;
  1031. }
  1032. }
  1033. if (!inputFound) {
  1034. newInputs.push(input);
  1035. }
  1036. }
  1037. for (let unfoundSource of unfoundSources) {
  1038. context.dispatch({
  1039. type: 'REMOVE_EVENT_SOURCE',
  1040. sourceId: unfoundSource.sourceId,
  1041. });
  1042. }
  1043. for (let newInput of newInputs) {
  1044. context.calendarApi.addEventSource(newInput);
  1045. }
  1046. }
  1047. function handleDateProfile(dateProfile, context) {
  1048. context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi }));
  1049. }
  1050. function handleEventStore(eventStore, context) {
  1051. let { emitter } = context;
  1052. if (emitter.hasHandlers('eventsSet')) {
  1053. emitter.trigger('eventsSet', buildEventApis(eventStore, context));
  1054. }
  1055. }
  1056. /*
  1057. this array is exposed on the root namespace so that UMD plugins can add to it.
  1058. see the rollup-bundles script.
  1059. */
  1060. const globalPlugins = [
  1061. arrayEventSourcePlugin,
  1062. funcEventSourcePlugin,
  1063. jsonFeedEventSourcePlugin,
  1064. simpleRecurringEventsPlugin,
  1065. changeHandlerPlugin,
  1066. createPlugin({
  1067. name: 'misc',
  1068. isLoadingFuncs: [
  1069. (state) => computeEventSourcesLoading(state.eventSources),
  1070. ],
  1071. propSetHandlers: {
  1072. dateProfile: handleDateProfile,
  1073. eventStore: handleEventStore,
  1074. },
  1075. }),
  1076. ];
  1077. class TaskRunner {
  1078. constructor(runTaskOption, drainedOption) {
  1079. this.runTaskOption = runTaskOption;
  1080. this.drainedOption = drainedOption;
  1081. this.queue = [];
  1082. this.delayedRunner = new DelayedRunner(this.drain.bind(this));
  1083. }
  1084. request(task, delay) {
  1085. this.queue.push(task);
  1086. this.delayedRunner.request(delay);
  1087. }
  1088. pause(scope) {
  1089. this.delayedRunner.pause(scope);
  1090. }
  1091. resume(scope, force) {
  1092. this.delayedRunner.resume(scope, force);
  1093. }
  1094. drain() {
  1095. let { queue } = this;
  1096. while (queue.length) {
  1097. let completedTasks = [];
  1098. let task;
  1099. while ((task = queue.shift())) {
  1100. this.runTask(task);
  1101. completedTasks.push(task);
  1102. }
  1103. this.drained(completedTasks);
  1104. } // keep going, in case new tasks were added in the drained handler
  1105. }
  1106. runTask(task) {
  1107. if (this.runTaskOption) {
  1108. this.runTaskOption(task);
  1109. }
  1110. }
  1111. drained(completedTasks) {
  1112. if (this.drainedOption) {
  1113. this.drainedOption(completedTasks);
  1114. }
  1115. }
  1116. }
  1117. // Computes what the title at the top of the calendarApi should be for this view
  1118. function buildTitle(dateProfile, viewOptions, dateEnv) {
  1119. let range;
  1120. // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
  1121. if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
  1122. range = dateProfile.currentRange;
  1123. }
  1124. else { // for day units or smaller, use the actual day range
  1125. range = dateProfile.activeRange;
  1126. }
  1127. return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), {
  1128. isEndExclusive: dateProfile.isRangeAllDay,
  1129. defaultSeparator: viewOptions.titleRangeSeparator,
  1130. });
  1131. }
  1132. // Generates the format string that should be used to generate the title for the current date range.
  1133. // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
  1134. function buildTitleFormat(dateProfile) {
  1135. let { currentRangeUnit } = dateProfile;
  1136. if (currentRangeUnit === 'year') {
  1137. return { year: 'numeric' };
  1138. }
  1139. if (currentRangeUnit === 'month') {
  1140. return { year: 'numeric', month: 'long' }; // like "September 2014"
  1141. }
  1142. let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
  1143. if (days !== null && days > 1) {
  1144. // multi-day range. shorter, like "Sep 9 - 10 2014"
  1145. return { year: 'numeric', month: 'short', day: 'numeric' };
  1146. }
  1147. // one day. longer, like "September 9 2014"
  1148. return { year: 'numeric', month: 'long', day: 'numeric' };
  1149. }
  1150. // in future refactor, do the redux-style function(state=initial) for initial-state
  1151. // also, whatever is happening in constructor, have it happen in action queue too
  1152. class CalendarDataManager {
  1153. constructor(props) {
  1154. this.computeCurrentViewData = memoize(this._computeCurrentViewData);
  1155. this.organizeRawLocales = memoize(organizeRawLocales);
  1156. this.buildLocale = memoize(buildLocale);
  1157. this.buildPluginHooks = buildBuildPluginHooks();
  1158. this.buildDateEnv = memoize(buildDateEnv$1);
  1159. this.buildTheme = memoize(buildTheme);
  1160. this.parseToolbars = memoize(parseToolbars);
  1161. this.buildViewSpecs = memoize(buildViewSpecs);
  1162. this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
  1163. this.buildViewApi = memoize(buildViewApi);
  1164. this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
  1165. this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual);
  1166. this.buildEventUiBases = memoize(buildEventUiBases);
  1167. this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
  1168. this.buildTitle = memoize(buildTitle);
  1169. this.emitter = new Emitter();
  1170. this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this));
  1171. this.currentCalendarOptionsInput = {};
  1172. this.currentCalendarOptionsRefined = {};
  1173. this.currentViewOptionsInput = {};
  1174. this.currentViewOptionsRefined = {};
  1175. this.currentCalendarOptionsRefiners = {};
  1176. this.optionsForRefining = [];
  1177. this.optionsForHandling = [];
  1178. this.getCurrentData = () => this.data;
  1179. this.dispatch = (action) => {
  1180. this.actionRunner.request(action); // protects against recursive calls to _handleAction
  1181. };
  1182. this.props = props;
  1183. this.actionRunner.pause();
  1184. let dynamicOptionOverrides = {};
  1185. let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
  1186. let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView;
  1187. let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
  1188. // wire things up
  1189. // TODO: not DRY
  1190. props.calendarApi.currentDataManager = this;
  1191. this.emitter.setThisContext(props.calendarApi);
  1192. this.emitter.setOptions(currentViewData.options);
  1193. let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv);
  1194. let dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
  1195. if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) {
  1196. currentDate = dateProfile.currentRange.start;
  1197. }
  1198. let calendarContext = {
  1199. dateEnv: optionsData.dateEnv,
  1200. options: optionsData.calendarOptions,
  1201. pluginHooks: optionsData.pluginHooks,
  1202. calendarApi: props.calendarApi,
  1203. dispatch: this.dispatch,
  1204. emitter: this.emitter,
  1205. getCurrentData: this.getCurrentData,
  1206. };
  1207. // needs to be after setThisContext
  1208. for (let callback of optionsData.pluginHooks.contextInit) {
  1209. callback(calendarContext);
  1210. }
  1211. // NOT DRY
  1212. let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext);
  1213. let initialState = {
  1214. dynamicOptionOverrides,
  1215. currentViewType,
  1216. currentDate,
  1217. dateProfile,
  1218. businessHours: this.parseContextBusinessHours(calendarContext),
  1219. eventSources,
  1220. eventUiBases: {},
  1221. eventStore: createEmptyEventStore(),
  1222. renderableEventStore: createEmptyEventStore(),
  1223. dateSelection: null,
  1224. eventSelection: '',
  1225. eventDrag: null,
  1226. eventResize: null,
  1227. selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig,
  1228. };
  1229. let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState);
  1230. for (let reducer of optionsData.pluginHooks.reducers) {
  1231. Object.assign(initialState, reducer(null, null, contextAndState));
  1232. }
  1233. if (computeIsLoading(initialState, calendarContext)) {
  1234. this.emitter.trigger('loading', true); // NOT DRY
  1235. }
  1236. this.state = initialState;
  1237. this.updateData();
  1238. this.actionRunner.resume();
  1239. }
  1240. resetOptions(optionOverrides, changedOptionNames) {
  1241. let { props } = this;
  1242. if (changedOptionNames === undefined) {
  1243. props.optionOverrides = optionOverrides;
  1244. }
  1245. else {
  1246. props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides);
  1247. this.optionsForRefining.push(...changedOptionNames);
  1248. }
  1249. if (changedOptionNames === undefined || changedOptionNames.length) {
  1250. this.actionRunner.request({
  1251. type: 'NOTHING',
  1252. });
  1253. }
  1254. }
  1255. _handleAction(action) {
  1256. let { props, state, emitter } = this;
  1257. let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action);
  1258. let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
  1259. let currentViewType = reduceViewType(state.currentViewType, action);
  1260. let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
  1261. // wire things up
  1262. // TODO: not DRY
  1263. props.calendarApi.currentDataManager = this;
  1264. emitter.setThisContext(props.calendarApi);
  1265. emitter.setOptions(currentViewData.options);
  1266. let calendarContext = {
  1267. dateEnv: optionsData.dateEnv,
  1268. options: optionsData.calendarOptions,
  1269. pluginHooks: optionsData.pluginHooks,
  1270. calendarApi: props.calendarApi,
  1271. dispatch: this.dispatch,
  1272. emitter,
  1273. getCurrentData: this.getCurrentData,
  1274. };
  1275. let { currentDate, dateProfile } = state;
  1276. if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack
  1277. dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
  1278. }
  1279. currentDate = reduceCurrentDate(currentDate, action);
  1280. dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator);
  1281. if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator
  1282. action.type === 'NEXT' || // "
  1283. !rangeContainsMarker(dateProfile.currentRange, currentDate)) {
  1284. currentDate = dateProfile.currentRange.start;
  1285. }
  1286. let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext);
  1287. let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext);
  1288. let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading
  1289. let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ?
  1290. (state.renderableEventStore || eventStore) : // try from previous state
  1291. eventStore;
  1292. let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj
  1293. let eventUiBySource = this.buildEventUiBySource(eventSources);
  1294. let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
  1295. let newState = {
  1296. dynamicOptionOverrides,
  1297. currentViewType,
  1298. currentDate,
  1299. dateProfile,
  1300. eventSources,
  1301. eventStore,
  1302. renderableEventStore,
  1303. selectionConfig,
  1304. eventUiBases,
  1305. businessHours: this.parseContextBusinessHours(calendarContext),
  1306. dateSelection: reduceDateSelection(state.dateSelection, action),
  1307. eventSelection: reduceSelectedEvent(state.eventSelection, action),
  1308. eventDrag: reduceEventDrag(state.eventDrag, action),
  1309. eventResize: reduceEventResize(state.eventResize, action),
  1310. };
  1311. let contextAndState = Object.assign(Object.assign({}, calendarContext), newState);
  1312. for (let reducer of optionsData.pluginHooks.reducers) {
  1313. Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value
  1314. }
  1315. let wasLoading = computeIsLoading(state, calendarContext);
  1316. let isLoading = computeIsLoading(newState, calendarContext);
  1317. // TODO: use propSetHandlers in plugin system
  1318. if (!wasLoading && isLoading) {
  1319. emitter.trigger('loading', true);
  1320. }
  1321. else if (wasLoading && !isLoading) {
  1322. emitter.trigger('loading', false);
  1323. }
  1324. this.state = newState;
  1325. if (props.onAction) {
  1326. props.onAction(action);
  1327. }
  1328. }
  1329. updateData() {
  1330. let { props, state } = this;
  1331. let oldData = this.data;
  1332. let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi);
  1333. let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides);
  1334. let data = this.data = Object.assign(Object.assign(Object.assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state);
  1335. let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
  1336. let oldCalendarOptions = oldData && oldData.calendarOptions;
  1337. let newCalendarOptions = optionsData.calendarOptions;
  1338. if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) {
  1339. if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
  1340. // hack
  1341. state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data);
  1342. state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv);
  1343. state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv);
  1344. }
  1345. for (let optionName in changeHandlers) {
  1346. if (this.optionsForHandling.indexOf(optionName) !== -1 ||
  1347. oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
  1348. changeHandlers[optionName](newCalendarOptions[optionName], data);
  1349. }
  1350. }
  1351. }
  1352. this.optionsForHandling = [];
  1353. if (props.onData) {
  1354. props.onData(data);
  1355. }
  1356. }
  1357. computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
  1358. // TODO: blacklist options that are handled by optionChangeHandlers
  1359. if (!this.optionsForRefining.length &&
  1360. optionOverrides === this.stableOptionOverrides &&
  1361. dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
  1362. return this.stableCalendarOptionsData;
  1363. }
  1364. let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
  1365. warnUnknownOptions(extra);
  1366. let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator);
  1367. let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults);
  1368. let theme = this.buildTheme(refinedOptions, pluginHooks);
  1369. let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi);
  1370. return this.stableCalendarOptionsData = {
  1371. calendarOptions: refinedOptions,
  1372. pluginHooks,
  1373. dateEnv,
  1374. viewSpecs,
  1375. theme,
  1376. toolbarConfig,
  1377. localeDefaults,
  1378. availableRawLocales: availableLocaleData.map,
  1379. };
  1380. }
  1381. // always called from behind a memoizer
  1382. processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
  1383. let { locales, locale } = mergeRawOptions([
  1384. BASE_OPTION_DEFAULTS,
  1385. optionOverrides,
  1386. dynamicOptionOverrides,
  1387. ]);
  1388. let availableLocaleData = this.organizeRawLocales(locales);
  1389. let availableRawLocales = availableLocaleData.map;
  1390. let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
  1391. let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
  1392. 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);
  1393. let extra = {};
  1394. let raw = mergeRawOptions([
  1395. BASE_OPTION_DEFAULTS,
  1396. localeDefaults,
  1397. optionOverrides,
  1398. dynamicOptionOverrides,
  1399. ]);
  1400. let refined = {};
  1401. let currentRaw = this.currentCalendarOptionsInput;
  1402. let currentRefined = this.currentCalendarOptionsRefined;
  1403. let anyChanges = false;
  1404. for (let optionName in raw) {
  1405. if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
  1406. (optionName in currentRaw) &&
  1407. COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) {
  1408. refined[optionName] = currentRefined[optionName];
  1409. }
  1410. else if (refiners[optionName]) {
  1411. refined[optionName] = refiners[optionName](raw[optionName]);
  1412. anyChanges = true;
  1413. }
  1414. else {
  1415. extra[optionName] = currentRaw[optionName];
  1416. }
  1417. }
  1418. if (anyChanges) {
  1419. this.currentCalendarOptionsInput = raw;
  1420. this.currentCalendarOptionsRefined = refined;
  1421. this.stableOptionOverrides = optionOverrides;
  1422. this.stableDynamicOptionOverrides = dynamicOptionOverrides;
  1423. }
  1424. this.optionsForHandling.push(...this.optionsForRefining);
  1425. this.optionsForRefining = [];
  1426. return {
  1427. rawOptions: this.currentCalendarOptionsInput,
  1428. refinedOptions: this.currentCalendarOptionsRefined,
  1429. pluginHooks,
  1430. availableLocaleData,
  1431. localeDefaults,
  1432. extra,
  1433. };
  1434. }
  1435. _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
  1436. let viewSpec = optionsData.viewSpecs[viewType];
  1437. if (!viewSpec) {
  1438. throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
  1439. }
  1440. let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
  1441. warnUnknownOptions(extra);
  1442. let dateProfileGenerator = this.buildDateProfileGenerator({
  1443. dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
  1444. duration: viewSpec.duration,
  1445. durationUnit: viewSpec.durationUnit,
  1446. usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
  1447. dateEnv: optionsData.dateEnv,
  1448. calendarApi: this.props.calendarApi,
  1449. slotMinTime: refinedOptions.slotMinTime,
  1450. slotMaxTime: refinedOptions.slotMaxTime,
  1451. showNonCurrentDates: refinedOptions.showNonCurrentDates,
  1452. dayCount: refinedOptions.dayCount,
  1453. dateAlignment: refinedOptions.dateAlignment,
  1454. dateIncrement: refinedOptions.dateIncrement,
  1455. hiddenDays: refinedOptions.hiddenDays,
  1456. weekends: refinedOptions.weekends,
  1457. nowInput: refinedOptions.now,
  1458. validRangeInput: refinedOptions.validRange,
  1459. visibleRangeInput: refinedOptions.visibleRange,
  1460. fixedWeekCount: refinedOptions.fixedWeekCount,
  1461. });
  1462. let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
  1463. return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
  1464. }
  1465. processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
  1466. let raw = mergeRawOptions([
  1467. BASE_OPTION_DEFAULTS,
  1468. viewSpec.optionDefaults,
  1469. localeDefaults,
  1470. optionOverrides,
  1471. viewSpec.optionOverrides,
  1472. dynamicOptionOverrides,
  1473. ]);
  1474. 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);
  1475. let refined = {};
  1476. let currentRaw = this.currentViewOptionsInput;
  1477. let currentRefined = this.currentViewOptionsRefined;
  1478. let anyChanges = false;
  1479. let extra = {};
  1480. for (let optionName in raw) {
  1481. if (raw[optionName] === currentRaw[optionName] ||
  1482. (COMPLEX_OPTION_COMPARATORS[optionName] &&
  1483. COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) {
  1484. refined[optionName] = currentRefined[optionName];
  1485. }
  1486. else {
  1487. if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
  1488. (COMPLEX_OPTION_COMPARATORS[optionName] &&
  1489. COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
  1490. if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
  1491. refined[optionName] = this.currentCalendarOptionsRefined[optionName];
  1492. }
  1493. }
  1494. else if (refiners[optionName]) {
  1495. refined[optionName] = refiners[optionName](raw[optionName]);
  1496. }
  1497. else {
  1498. extra[optionName] = raw[optionName];
  1499. }
  1500. anyChanges = true;
  1501. }
  1502. }
  1503. if (anyChanges) {
  1504. this.currentViewOptionsInput = raw;
  1505. this.currentViewOptionsRefined = refined;
  1506. }
  1507. return {
  1508. rawOptions: this.currentViewOptionsInput,
  1509. refinedOptions: this.currentViewOptionsRefined,
  1510. extra,
  1511. };
  1512. }
  1513. }
  1514. function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) {
  1515. let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
  1516. return new DateEnv({
  1517. calendarSystem: 'gregory',
  1518. timeZone,
  1519. namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl,
  1520. locale,
  1521. weekNumberCalculation,
  1522. firstDay,
  1523. weekText,
  1524. cmdFormatter: pluginHooks.cmdFormatter,
  1525. defaultSeparator,
  1526. });
  1527. }
  1528. function buildTheme(options, pluginHooks) {
  1529. let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme;
  1530. return new ThemeClass(options);
  1531. }
  1532. function buildDateProfileGenerator(props) {
  1533. let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
  1534. return new DateProfileGeneratorClass(props);
  1535. }
  1536. function buildViewApi(type, getCurrentData, dateEnv) {
  1537. return new ViewImpl(type, getCurrentData, dateEnv);
  1538. }
  1539. function buildEventUiBySource(eventSources) {
  1540. return mapHash(eventSources, (eventSource) => eventSource.ui);
  1541. }
  1542. function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
  1543. let eventUiBases = { '': eventUiSingleBase };
  1544. for (let defId in eventDefs) {
  1545. let def = eventDefs[defId];
  1546. if (def.sourceId && eventUiBySource[def.sourceId]) {
  1547. eventUiBases[defId] = eventUiBySource[def.sourceId];
  1548. }
  1549. }
  1550. return eventUiBases;
  1551. }
  1552. function buildViewUiProps(calendarContext) {
  1553. let { options } = calendarContext;
  1554. return {
  1555. eventUiSingleBase: createEventUi({
  1556. display: options.eventDisplay,
  1557. editable: options.editable,
  1558. startEditable: options.eventStartEditable,
  1559. durationEditable: options.eventDurationEditable,
  1560. constraint: options.eventConstraint,
  1561. overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
  1562. allow: options.eventAllow,
  1563. backgroundColor: options.eventBackgroundColor,
  1564. borderColor: options.eventBorderColor,
  1565. textColor: options.eventTextColor,
  1566. color: options.eventColor,
  1567. // classNames: options.eventClassNames // render hook will handle this
  1568. }, calendarContext),
  1569. selectionConfig: createEventUi({
  1570. constraint: options.selectConstraint,
  1571. overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
  1572. allow: options.selectAllow,
  1573. }, calendarContext),
  1574. };
  1575. }
  1576. function computeIsLoading(state, context) {
  1577. for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
  1578. if (isLoadingFunc(state)) {
  1579. return true;
  1580. }
  1581. }
  1582. return false;
  1583. }
  1584. function parseContextBusinessHours(calendarContext) {
  1585. return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
  1586. }
  1587. function warnUnknownOptions(options, viewName) {
  1588. for (let optionName in options) {
  1589. console.warn(`Unknown option '${optionName}'` +
  1590. (viewName ? ` for view '${viewName}'` : ''));
  1591. }
  1592. }
  1593. class ToolbarSection extends BaseComponent {
  1594. render() {
  1595. let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
  1596. return createElement('div', { className: 'fc-toolbar-chunk' }, ...children);
  1597. }
  1598. renderWidgetGroup(widgetGroup) {
  1599. let { props } = this;
  1600. let { theme } = this.context;
  1601. let children = [];
  1602. let isOnlyButtons = true;
  1603. for (let widget of widgetGroup) {
  1604. let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget;
  1605. if (buttonName === 'title') {
  1606. isOnlyButtons = false;
  1607. children.push(createElement("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title));
  1608. }
  1609. else {
  1610. let isPressed = buttonName === props.activeButton;
  1611. let isDisabled = (!props.isTodayEnabled && buttonName === 'today') ||
  1612. (!props.isPrevEnabled && buttonName === 'prev') ||
  1613. (!props.isNextEnabled && buttonName === 'next');
  1614. let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')];
  1615. if (isPressed) {
  1616. buttonClasses.push(theme.getClass('buttonActive'));
  1617. }
  1618. children.push(createElement("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? createElement("span", { className: buttonIcon, role: "img" }) : '')));
  1619. }
  1620. }
  1621. if (children.length > 1) {
  1622. let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || '';
  1623. return createElement('div', { className: groupClassName }, ...children);
  1624. }
  1625. return children[0];
  1626. }
  1627. }
  1628. class Toolbar extends BaseComponent {
  1629. render() {
  1630. let { model, extraClassName } = this.props;
  1631. let forceLtr = false;
  1632. let startContent;
  1633. let endContent;
  1634. let sectionWidgets = model.sectionWidgets;
  1635. let centerContent = sectionWidgets.center;
  1636. if (sectionWidgets.left) {
  1637. forceLtr = true;
  1638. startContent = sectionWidgets.left;
  1639. }
  1640. else {
  1641. startContent = sectionWidgets.start;
  1642. }
  1643. if (sectionWidgets.right) {
  1644. forceLtr = true;
  1645. endContent = sectionWidgets.right;
  1646. }
  1647. else {
  1648. endContent = sectionWidgets.end;
  1649. }
  1650. let classNames = [
  1651. extraClassName || '',
  1652. 'fc-toolbar',
  1653. forceLtr ? 'fc-toolbar-ltr' : '',
  1654. ];
  1655. return (createElement("div", { className: classNames.join(' ') },
  1656. this.renderSection('start', startContent || []),
  1657. this.renderSection('center', centerContent || []),
  1658. this.renderSection('end', endContent || [])));
  1659. }
  1660. renderSection(key, widgetGroups) {
  1661. let { props } = this;
  1662. return (createElement(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 }));
  1663. }
  1664. }
  1665. class ViewHarness extends BaseComponent {
  1666. constructor() {
  1667. super(...arguments);
  1668. this.state = {
  1669. availableWidth: null,
  1670. };
  1671. this.handleEl = (el) => {
  1672. this.el = el;
  1673. setRef(this.props.elRef, el);
  1674. this.updateAvailableWidth();
  1675. };
  1676. this.handleResize = () => {
  1677. this.updateAvailableWidth();
  1678. };
  1679. }
  1680. render() {
  1681. let { props, state } = this;
  1682. let { aspectRatio } = props;
  1683. let classNames = [
  1684. 'fc-view-harness',
  1685. (aspectRatio || props.liquid || props.height)
  1686. ? 'fc-view-harness-active' // harness controls the height
  1687. : 'fc-view-harness-passive', // let the view do the height
  1688. ];
  1689. let height = '';
  1690. let paddingBottom = '';
  1691. if (aspectRatio) {
  1692. if (state.availableWidth !== null) {
  1693. height = state.availableWidth / aspectRatio;
  1694. }
  1695. else {
  1696. // while waiting to know availableWidth, we can't set height to *zero*
  1697. // because will cause lots of unnecessary scrollbars within scrollgrid.
  1698. // BETTER: don't start rendering ANYTHING yet until we know container width
  1699. // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
  1700. paddingBottom = `${(1 / aspectRatio) * 100}%`;
  1701. }
  1702. }
  1703. else {
  1704. height = props.height || '';
  1705. }
  1706. return (createElement("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children));
  1707. }
  1708. componentDidMount() {
  1709. this.context.addResizeHandler(this.handleResize);
  1710. }
  1711. componentWillUnmount() {
  1712. this.context.removeResizeHandler(this.handleResize);
  1713. }
  1714. updateAvailableWidth() {
  1715. if (this.el && // needed. but why?
  1716. this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
  1717. ) {
  1718. this.setState({ availableWidth: this.el.offsetWidth });
  1719. }
  1720. }
  1721. }
  1722. /*
  1723. Detects when the user clicks on an event within a DateComponent
  1724. */
  1725. class EventClicking extends Interaction {
  1726. constructor(settings) {
  1727. super(settings);
  1728. this.handleSegClick = (ev, segEl) => {
  1729. let { component } = this;
  1730. let { context } = component;
  1731. let seg = getElSeg(segEl);
  1732. if (seg && // might be the <div> surrounding the more link
  1733. component.isValidSegDownEl(ev.target)) {
  1734. // our way to simulate a link click for elements that can't be <a> tags
  1735. // grab before trigger fired in case trigger trashes DOM thru rerendering
  1736. let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
  1737. let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
  1738. context.emitter.trigger('eventClick', {
  1739. el: segEl,
  1740. event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance),
  1741. jsEvent: ev,
  1742. view: context.viewApi,
  1743. });
  1744. if (url && !ev.defaultPrevented) {
  1745. window.location.href = url;
  1746. }
  1747. }
  1748. };
  1749. this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events
  1750. this.handleSegClick);
  1751. }
  1752. }
  1753. /*
  1754. Triggers events and adds/removes core classNames when the user's pointer
  1755. enters/leaves event-elements of a component.
  1756. */
  1757. class EventHovering extends Interaction {
  1758. constructor(settings) {
  1759. super(settings);
  1760. // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
  1761. this.handleEventElRemove = (el) => {
  1762. if (el === this.currentSegEl) {
  1763. this.handleSegLeave(null, this.currentSegEl);
  1764. }
  1765. };
  1766. this.handleSegEnter = (ev, segEl) => {
  1767. if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
  1768. this.currentSegEl = segEl;
  1769. this.triggerEvent('eventMouseEnter', ev, segEl);
  1770. }
  1771. };
  1772. this.handleSegLeave = (ev, segEl) => {
  1773. if (this.currentSegEl) {
  1774. this.currentSegEl = null;
  1775. this.triggerEvent('eventMouseLeave', ev, segEl);
  1776. }
  1777. };
  1778. this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events
  1779. this.handleSegEnter, this.handleSegLeave);
  1780. }
  1781. destroy() {
  1782. this.removeHoverListeners();
  1783. }
  1784. triggerEvent(publicEvName, ev, segEl) {
  1785. let { component } = this;
  1786. let { context } = component;
  1787. let seg = getElSeg(segEl);
  1788. if (!ev || component.isValidSegDownEl(ev.target)) {
  1789. context.emitter.trigger(publicEvName, {
  1790. el: segEl,
  1791. event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance),
  1792. jsEvent: ev,
  1793. view: context.viewApi,
  1794. });
  1795. }
  1796. }
  1797. }
  1798. class CalendarContent extends PureComponent {
  1799. constructor() {
  1800. super(...arguments);
  1801. this.buildViewContext = memoize(buildViewContext);
  1802. this.buildViewPropTransformers = memoize(buildViewPropTransformers);
  1803. this.buildToolbarProps = memoize(buildToolbarProps);
  1804. this.headerRef = createRef();
  1805. this.footerRef = createRef();
  1806. this.interactionsStore = {};
  1807. // eslint-disable-next-line
  1808. this.state = {
  1809. viewLabelId: getUniqueDomId(),
  1810. };
  1811. // Component Registration
  1812. // -----------------------------------------------------------------------------------------------------------------
  1813. this.registerInteractiveComponent = (component, settingsInput) => {
  1814. let settings = parseInteractionSettings(component, settingsInput);
  1815. let DEFAULT_INTERACTIONS = [
  1816. EventClicking,
  1817. EventHovering,
  1818. ];
  1819. let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions);
  1820. let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
  1821. this.interactionsStore[component.uid] = interactions;
  1822. interactionSettingsStore[component.uid] = settings;
  1823. };
  1824. this.unregisterInteractiveComponent = (component) => {
  1825. let listeners = this.interactionsStore[component.uid];
  1826. if (listeners) {
  1827. for (let listener of listeners) {
  1828. listener.destroy();
  1829. }
  1830. delete this.interactionsStore[component.uid];
  1831. }
  1832. delete interactionSettingsStore[component.uid];
  1833. };
  1834. // Resizing
  1835. // -----------------------------------------------------------------------------------------------------------------
  1836. this.resizeRunner = new DelayedRunner(() => {
  1837. this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ?
  1838. this.props.emitter.trigger('windowResize', { view: this.props.viewApi });
  1839. });
  1840. this.handleWindowResize = (ev) => {
  1841. let { options } = this.props;
  1842. if (options.handleWindowResize &&
  1843. ev.target === window // avoid jqui events
  1844. ) {
  1845. this.resizeRunner.request(options.windowResizeDelay);
  1846. }
  1847. };
  1848. }
  1849. /*
  1850. renders INSIDE of an outer div
  1851. */
  1852. render() {
  1853. let { props } = this;
  1854. let { toolbarConfig, options } = props;
  1855. let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer????
  1856. props.viewTitle);
  1857. let viewVGrow = false;
  1858. let viewHeight = '';
  1859. let viewAspectRatio;
  1860. if (props.isHeightAuto || props.forPrint) {
  1861. viewHeight = '';
  1862. }
  1863. else if (options.height != null) {
  1864. viewVGrow = true;
  1865. }
  1866. else if (options.contentHeight != null) {
  1867. viewHeight = options.contentHeight;
  1868. }
  1869. else {
  1870. viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
  1871. }
  1872. let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
  1873. let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
  1874. ? this.state.viewLabelId
  1875. : undefined;
  1876. return (createElement(ViewContextType.Provider, { value: viewContext },
  1877. toolbarConfig.header && (createElement(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))),
  1878. createElement(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId },
  1879. this.renderView(props),
  1880. this.buildAppendContent()),
  1881. toolbarConfig.footer && (createElement(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps)))));
  1882. }
  1883. componentDidMount() {
  1884. let { props } = this;
  1885. this.calendarInteractions = props.pluginHooks.calendarInteractions
  1886. .map((CalendarInteractionClass) => new CalendarInteractionClass(props));
  1887. window.addEventListener('resize', this.handleWindowResize);
  1888. let { propSetHandlers } = props.pluginHooks;
  1889. for (let propName in propSetHandlers) {
  1890. propSetHandlers[propName](props[propName], props);
  1891. }
  1892. }
  1893. componentDidUpdate(prevProps) {
  1894. let { props } = this;
  1895. let { propSetHandlers } = props.pluginHooks;
  1896. for (let propName in propSetHandlers) {
  1897. if (props[propName] !== prevProps[propName]) {
  1898. propSetHandlers[propName](props[propName], props);
  1899. }
  1900. }
  1901. }
  1902. componentWillUnmount() {
  1903. window.removeEventListener('resize', this.handleWindowResize);
  1904. this.resizeRunner.clear();
  1905. for (let interaction of this.calendarInteractions) {
  1906. interaction.destroy();
  1907. }
  1908. this.props.emitter.trigger('_unmount');
  1909. }
  1910. buildAppendContent() {
  1911. let { props } = this;
  1912. let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props));
  1913. return createElement(Fragment, {}, ...children);
  1914. }
  1915. renderView(props) {
  1916. let { pluginHooks } = props;
  1917. let { viewSpec } = props;
  1918. let viewProps = {
  1919. dateProfile: props.dateProfile,
  1920. businessHours: props.businessHours,
  1921. eventStore: props.renderableEventStore,
  1922. eventUiBases: props.eventUiBases,
  1923. dateSelection: props.dateSelection,
  1924. eventSelection: props.eventSelection,
  1925. eventDrag: props.eventDrag,
  1926. eventResize: props.eventResize,
  1927. isHeightAuto: props.isHeightAuto,
  1928. forPrint: props.forPrint,
  1929. };
  1930. let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
  1931. for (let transformer of transformers) {
  1932. Object.assign(viewProps, transformer.transform(viewProps, props));
  1933. }
  1934. let ViewComponent = viewSpec.component;
  1935. return (createElement(ViewComponent, Object.assign({}, viewProps)));
  1936. }
  1937. }
  1938. function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) {
  1939. // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
  1940. let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason
  1941. let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false);
  1942. let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false);
  1943. return {
  1944. title,
  1945. activeButton: viewSpec.type,
  1946. navUnit: viewSpec.singleUnit,
  1947. isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
  1948. isPrevEnabled: prevInfo.isValid,
  1949. isNextEnabled: nextInfo.isValid,
  1950. };
  1951. }
  1952. // Plugin
  1953. // -----------------------------------------------------------------------------------------------------------------
  1954. function buildViewPropTransformers(theClasses) {
  1955. return theClasses.map((TheClass) => new TheClass());
  1956. }
  1957. class Calendar extends CalendarImpl {
  1958. constructor(el, optionOverrides = {}) {
  1959. super();
  1960. this.isRendering = false;
  1961. this.isRendered = false;
  1962. this.currentClassNames = [];
  1963. this.customContentRenderId = 0;
  1964. this.handleAction = (action) => {
  1965. // actions we know we want to render immediately
  1966. switch (action.type) {
  1967. case 'SET_EVENT_DRAG':
  1968. case 'SET_EVENT_RESIZE':
  1969. this.renderRunner.tryDrain();
  1970. }
  1971. };
  1972. this.handleData = (data) => {
  1973. this.currentData = data;
  1974. this.renderRunner.request(data.calendarOptions.rerenderDelay);
  1975. };
  1976. this.handleRenderRequest = () => {
  1977. if (this.isRendering) {
  1978. this.isRendered = true;
  1979. let { currentData } = this;
  1980. flushSync(() => {
  1981. render(createElement(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => {
  1982. this.setClassNames(classNames);
  1983. this.setHeight(height);
  1984. return (createElement(RenderId.Provider, { value: this.customContentRenderId },
  1985. createElement(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData))));
  1986. }), this.el);
  1987. });
  1988. }
  1989. else if (this.isRendered) {
  1990. this.isRendered = false;
  1991. render(null, this.el);
  1992. this.setClassNames([]);
  1993. this.setHeight('');
  1994. }
  1995. };
  1996. ensureElHasStyles(el);
  1997. this.el = el;
  1998. this.renderRunner = new DelayedRunner(this.handleRenderRequest);
  1999. new CalendarDataManager({
  2000. optionOverrides,
  2001. calendarApi: this,
  2002. onAction: this.handleAction,
  2003. onData: this.handleData,
  2004. });
  2005. }
  2006. render() {
  2007. let wasRendering = this.isRendering;
  2008. if (!wasRendering) {
  2009. this.isRendering = true;
  2010. }
  2011. else {
  2012. this.customContentRenderId += 1;
  2013. }
  2014. this.renderRunner.request();
  2015. if (wasRendering) {
  2016. this.updateSize();
  2017. }
  2018. }
  2019. destroy() {
  2020. if (this.isRendering) {
  2021. this.isRendering = false;
  2022. this.renderRunner.request();
  2023. }
  2024. }
  2025. updateSize() {
  2026. flushSync(() => {
  2027. super.updateSize();
  2028. });
  2029. }
  2030. batchRendering(func) {
  2031. this.renderRunner.pause('batchRendering');
  2032. func();
  2033. this.renderRunner.resume('batchRendering');
  2034. }
  2035. pauseRendering() {
  2036. this.renderRunner.pause('pauseRendering');
  2037. }
  2038. resumeRendering() {
  2039. this.renderRunner.resume('pauseRendering', true);
  2040. }
  2041. resetOptions(optionOverrides, changedOptionNames) {
  2042. this.currentDataManager.resetOptions(optionOverrides, changedOptionNames);
  2043. }
  2044. setClassNames(classNames) {
  2045. if (!isArraysEqual(classNames, this.currentClassNames)) {
  2046. let { classList } = this.el;
  2047. for (let className of this.currentClassNames) {
  2048. classList.remove(className);
  2049. }
  2050. for (let className of classNames) {
  2051. classList.add(className);
  2052. }
  2053. this.currentClassNames = classNames;
  2054. }
  2055. }
  2056. setHeight(height) {
  2057. applyStyleProp(this.el, 'height', height);
  2058. }
  2059. }
  2060. function formatDate(dateInput, options = {}) {
  2061. let dateEnv = buildDateEnv(options);
  2062. let formatter = createFormatter(options);
  2063. let dateMeta = dateEnv.createMarkerMeta(dateInput);
  2064. if (!dateMeta) { // TODO: warning?
  2065. return '';
  2066. }
  2067. return dateEnv.format(dateMeta.marker, formatter, {
  2068. forcedTzo: dateMeta.forcedTzo,
  2069. });
  2070. }
  2071. function formatRange(startInput, endInput, options) {
  2072. let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object
  2073. let formatter = createFormatter(options);
  2074. let startMeta = dateEnv.createMarkerMeta(startInput);
  2075. let endMeta = dateEnv.createMarkerMeta(endInput);
  2076. if (!startMeta || !endMeta) { // TODO: warning?
  2077. return '';
  2078. }
  2079. return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, {
  2080. forcedStartTzo: startMeta.forcedTzo,
  2081. forcedEndTzo: endMeta.forcedTzo,
  2082. isEndExclusive: options.isEndExclusive,
  2083. defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator,
  2084. });
  2085. }
  2086. // TODO: more DRY and optimized
  2087. function buildDateEnv(settings) {
  2088. let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere
  2089. return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale }));
  2090. }
  2091. // HELPERS
  2092. /*
  2093. if nextDayThreshold is specified, slicing is done in an all-day fashion.
  2094. you can get nextDayThreshold from context.nextDayThreshold
  2095. */
  2096. function sliceEvents(props, allDay) {
  2097. return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg;
  2098. }
  2099. const version = '6.1.14';
  2100. export { Calendar, createPlugin, formatDate, formatRange, globalLocales, globalPlugins, sliceEvents, version };