index.cjs 90 KB

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