12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141 |
- import { m as mergeProps, g as guid, i as isArraysEqual, T as Theme, a as mapHash, B as BaseComponent, V as ViewContextType, C as ContentContainer, b as buildViewClassNames, c as greatestDurationDenominator, d as createDuration, e as BASE_OPTION_DEFAULTS, f as arrayToHash, h as filterHash, j as buildEventSourceRefiners, p as parseEventSource, k as formatWithOrdinals, u as unpromisify, l as buildRangeApiWithTimeZone, n as identity, r as requestJson, s as subtractDurations, o as intersectRanges, q as startOfDay, t as addDays, v as hashValuesToArray, w as buildEventApis, D as DelayedRunner, x as createFormatter, y as diffWholeDays, z as memoize, A as memoizeObjArg, E as isPropsEqual, F as Emitter, G as getInitialDate, H as rangeContainsMarker, I as createEmptyEventStore, J as reduceCurrentDate, K as reduceEventStore, L as rezoneEventStoreDates, M as mergeRawOptions, N as BASE_OPTION_REFINERS, O as CALENDAR_LISTENER_REFINERS, P as CALENDAR_OPTION_REFINERS, Q as COMPLEX_OPTION_COMPARATORS, R as VIEW_OPTION_REFINERS, S as DateEnv, U as DateProfileGenerator, W as createEventUi, X as parseBusinessHours, Y as setRef, Z as Interaction, _ as getElSeg, $ as elementClosest, a0 as EventImpl, a1 as listenBySelector, a2 as listenToHoverBySelector, a3 as PureComponent, a4 as buildViewContext, a5 as getUniqueDomId, a6 as parseInteractionSettings, a7 as interactionSettingsStore, a8 as getNow, a9 as CalendarImpl, aa as flushSync, ab as CalendarRoot, ac as RenderId, ad as ensureElHasStyles, ae as applyStyleProp, af as sliceEventStore } from './internal-common.js';
- export { ag as JsonRequestError } from './internal-common.js';
- import { createElement, createRef, Fragment, render } from 'preact';
- import 'preact/compat';
- const globalLocales = [];
- const MINIMAL_RAW_EN_LOCALE = {
- code: 'en',
- week: {
- dow: 0,
- doy: 4, // 4 days need to be within the year to be considered the first week
- },
- direction: 'ltr',
- buttonText: {
- prev: 'prev',
- next: 'next',
- prevYear: 'prev year',
- nextYear: 'next year',
- year: 'year',
- today: 'today',
- month: 'month',
- week: 'week',
- day: 'day',
- list: 'list',
- },
- weekText: 'W',
- weekTextLong: 'Week',
- closeHint: 'Close',
- timeHint: 'Time',
- eventHint: 'Event',
- allDayText: 'all-day',
- moreLinkText: 'more',
- noEventsText: 'No events to display',
- };
- const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), {
- // Includes things we don't want other locales to inherit,
- // things that derive from other translatable strings.
- buttonHints: {
- prev: 'Previous $0',
- next: 'Next $0',
- today(buttonText, unit) {
- return (unit === 'day')
- ? 'Today'
- : `This ${buttonText}`;
- },
- }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) {
- return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`;
- } });
- function organizeRawLocales(explicitRawLocales) {
- let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en';
- let allRawLocales = globalLocales.concat(explicitRawLocales);
- let rawLocaleMap = {
- en: RAW_EN_LOCALE,
- };
- for (let rawLocale of allRawLocales) {
- rawLocaleMap[rawLocale.code] = rawLocale;
- }
- return {
- map: rawLocaleMap,
- defaultCode,
- };
- }
- function buildLocale(inputSingular, available) {
- if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) {
- return parseLocale(inputSingular.code, [inputSingular.code], inputSingular);
- }
- return queryLocale(inputSingular, available);
- }
- function queryLocale(codeArg, available) {
- let codes = [].concat(codeArg || []); // will convert to array
- let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE;
- return parseLocale(codeArg, codes, raw);
- }
- function queryRawLocale(codes, available) {
- for (let i = 0; i < codes.length; i += 1) {
- let parts = codes[i].toLocaleLowerCase().split('-');
- for (let j = parts.length; j > 0; j -= 1) {
- let simpleId = parts.slice(0, j).join('-');
- if (available[simpleId]) {
- return available[simpleId];
- }
- }
- }
- return null;
- }
- function parseLocale(codeArg, codes, raw) {
- let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']);
- delete merged.code; // don't want this part of the options
- let { week } = merged;
- delete merged.week;
- return {
- codeArg,
- codes,
- week,
- simpleNumberFormat: new Intl.NumberFormat(codeArg),
- options: merged,
- };
- }
- // TODO: easier way to add new hooks? need to update a million things
- function createPlugin(input) {
- return {
- id: guid(),
- name: input.name,
- premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
- deps: input.deps || [],
- reducers: input.reducers || [],
- isLoadingFuncs: input.isLoadingFuncs || [],
- contextInit: [].concat(input.contextInit || []),
- eventRefiners: input.eventRefiners || {},
- eventDefMemberAdders: input.eventDefMemberAdders || [],
- eventSourceRefiners: input.eventSourceRefiners || {},
- isDraggableTransformers: input.isDraggableTransformers || [],
- eventDragMutationMassagers: input.eventDragMutationMassagers || [],
- eventDefMutationAppliers: input.eventDefMutationAppliers || [],
- dateSelectionTransformers: input.dateSelectionTransformers || [],
- datePointTransforms: input.datePointTransforms || [],
- dateSpanTransforms: input.dateSpanTransforms || [],
- views: input.views || {},
- viewPropsTransformers: input.viewPropsTransformers || [],
- isPropsValid: input.isPropsValid || null,
- externalDefTransforms: input.externalDefTransforms || [],
- viewContainerAppends: input.viewContainerAppends || [],
- eventDropTransformers: input.eventDropTransformers || [],
- componentInteractions: input.componentInteractions || [],
- calendarInteractions: input.calendarInteractions || [],
- themeClasses: input.themeClasses || {},
- eventSourceDefs: input.eventSourceDefs || [],
- cmdFormatter: input.cmdFormatter,
- recurringTypes: input.recurringTypes || [],
- namedTimeZonedImpl: input.namedTimeZonedImpl,
- initialView: input.initialView || '',
- elementDraggingImpl: input.elementDraggingImpl,
- optionChangeHandlers: input.optionChangeHandlers || {},
- scrollGridImpl: input.scrollGridImpl || null,
- listenerRefiners: input.listenerRefiners || {},
- optionRefiners: input.optionRefiners || {},
- propSetHandlers: input.propSetHandlers || {},
- };
- }
- function buildPluginHooks(pluginDefs, globalDefs) {
- let currentPluginIds = {};
- let hooks = {
- premiumReleaseDate: undefined,
- reducers: [],
- isLoadingFuncs: [],
- contextInit: [],
- eventRefiners: {},
- eventDefMemberAdders: [],
- eventSourceRefiners: {},
- isDraggableTransformers: [],
- eventDragMutationMassagers: [],
- eventDefMutationAppliers: [],
- dateSelectionTransformers: [],
- datePointTransforms: [],
- dateSpanTransforms: [],
- views: {},
- viewPropsTransformers: [],
- isPropsValid: null,
- externalDefTransforms: [],
- viewContainerAppends: [],
- eventDropTransformers: [],
- componentInteractions: [],
- calendarInteractions: [],
- themeClasses: {},
- eventSourceDefs: [],
- cmdFormatter: null,
- recurringTypes: [],
- namedTimeZonedImpl: null,
- initialView: '',
- elementDraggingImpl: null,
- optionChangeHandlers: {},
- scrollGridImpl: null,
- listenerRefiners: {},
- optionRefiners: {},
- propSetHandlers: {},
- };
- function addDefs(defs) {
- for (let def of defs) {
- const pluginName = def.name;
- const currentId = currentPluginIds[pluginName];
- if (currentId === undefined) {
- currentPluginIds[pluginName] = def.id;
- addDefs(def.deps);
- hooks = combineHooks(hooks, def);
- }
- else if (currentId !== def.id) {
- // different ID than the one already added
- console.warn(`Duplicate plugin '${pluginName}'`);
- }
- }
- }
- if (pluginDefs) {
- addDefs(pluginDefs);
- }
- addDefs(globalDefs);
- return hooks;
- }
- function buildBuildPluginHooks() {
- let currentOverrideDefs = [];
- let currentGlobalDefs = [];
- let currentHooks;
- return (overrideDefs, globalDefs) => {
- if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
- currentHooks = buildPluginHooks(overrideDefs, globalDefs);
- }
- currentOverrideDefs = overrideDefs;
- currentGlobalDefs = globalDefs;
- return currentHooks;
- };
- }
- function combineHooks(hooks0, hooks1) {
- return {
- premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
- reducers: hooks0.reducers.concat(hooks1.reducers),
- isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
- contextInit: hooks0.contextInit.concat(hooks1.contextInit),
- eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners),
- eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
- eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners),
- isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
- eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
- eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
- dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
- datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
- dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
- views: Object.assign(Object.assign({}, hooks0.views), hooks1.views),
- viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
- isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
- externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
- viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
- eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
- calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
- componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
- themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses),
- eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
- cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
- recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
- namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl,
- initialView: hooks0.initialView || hooks1.initialView,
- elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl,
- optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers),
- scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl,
- listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners),
- optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners),
- propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers),
- };
- }
- function compareOptionalDates(date0, date1) {
- if (date0 === undefined) {
- return date1;
- }
- if (date1 === undefined) {
- return date0;
- }
- return new Date(Math.max(date0.valueOf(), date1.valueOf()));
- }
- class StandardTheme extends Theme {
- }
- StandardTheme.prototype.classes = {
- root: 'fc-theme-standard',
- tableCellShaded: 'fc-cell-shaded',
- buttonGroup: 'fc-button-group',
- button: 'fc-button fc-button-primary',
- buttonActive: 'fc-button-active',
- };
- StandardTheme.prototype.baseIconClass = 'fc-icon';
- StandardTheme.prototype.iconClasses = {
- close: 'fc-icon-x',
- prev: 'fc-icon-chevron-left',
- next: 'fc-icon-chevron-right',
- prevYear: 'fc-icon-chevrons-left',
- nextYear: 'fc-icon-chevrons-right',
- };
- StandardTheme.prototype.rtlIconClasses = {
- prev: 'fc-icon-chevron-right',
- next: 'fc-icon-chevron-left',
- prevYear: 'fc-icon-chevrons-right',
- nextYear: 'fc-icon-chevrons-left',
- };
- StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly
- StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon';
- StandardTheme.prototype.iconOverridePrefix = 'fc-icon-';
- function compileViewDefs(defaultConfigs, overrideConfigs) {
- let hash = {};
- let viewType;
- for (viewType in defaultConfigs) {
- ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
- }
- for (viewType in overrideConfigs) {
- ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
- }
- return hash;
- }
- function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
- if (hash[viewType]) {
- return hash[viewType];
- }
- let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
- if (viewDef) {
- hash[viewType] = viewDef;
- }
- return viewDef;
- }
- function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
- let defaultConfig = defaultConfigs[viewType];
- let overrideConfig = overrideConfigs[viewType];
- let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
- ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
- let theComponent = queryProp('component');
- let superType = queryProp('superType');
- let superDef = null;
- if (superType) {
- if (superType === viewType) {
- throw new Error('Can\'t have a custom view type that references itself');
- }
- superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
- }
- if (!theComponent && superDef) {
- theComponent = superDef.component;
- }
- if (!theComponent) {
- return null; // don't throw a warning, might be settings for a single-unit view
- }
- return {
- type: viewType,
- component: theComponent,
- defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})),
- overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})),
- };
- }
- function parseViewConfigs(inputs) {
- return mapHash(inputs, parseViewConfig);
- }
- function parseViewConfig(input) {
- let rawOptions = typeof input === 'function' ?
- { component: input } :
- input;
- let { component } = rawOptions;
- if (rawOptions.content) {
- // TODO: remove content/classNames/didMount/etc from options?
- component = createViewHookComponent(rawOptions);
- }
- else if (component && !(component.prototype instanceof BaseComponent)) {
- // WHY?: people were using `component` property for `content`
- // TODO: converge on one setting name
- component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component }));
- }
- return {
- superType: rawOptions.type,
- component: component,
- rawOptions, // includes type and component too :(
- };
- }
- function createViewHookComponent(options) {
- return (viewProps) => (createElement(ViewContextType.Consumer, null, (context) => (createElement(ContentContainer, { elTag: "div", elClasses: buildViewClassNames(context.viewSpec), renderProps: Object.assign(Object.assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }), generatorName: undefined, customGenerator: options.content, classNameGenerator: options.classNames, didMount: options.didMount, willUnmount: options.willUnmount }))));
- }
- function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
- let defaultConfigs = parseViewConfigs(defaultInputs);
- let overrideConfigs = parseViewConfigs(optionOverrides.views);
- let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
- return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults));
- }
- function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
- let durationInput = viewDef.overrides.duration ||
- viewDef.defaults.duration ||
- dynamicOptionOverrides.duration ||
- optionOverrides.duration;
- let duration = null;
- let durationUnit = '';
- let singleUnit = '';
- let singleUnitOverrides = {};
- if (durationInput) {
- duration = createDurationCached(durationInput);
- if (duration) { // valid?
- let denom = greatestDurationDenominator(duration);
- durationUnit = denom.unit;
- if (denom.value === 1) {
- singleUnit = durationUnit;
- singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
- }
- }
- }
- let queryButtonText = (optionsSubset) => {
- let buttonTextMap = optionsSubset.buttonText || {};
- let buttonTextKey = viewDef.defaults.buttonTextKey;
- if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) {
- return buttonTextMap[buttonTextKey];
- }
- if (buttonTextMap[viewDef.type] != null) {
- return buttonTextMap[viewDef.type];
- }
- if (buttonTextMap[singleUnit] != null) {
- return buttonTextMap[singleUnit];
- }
- return null;
- };
- let queryButtonTitle = (optionsSubset) => {
- let buttonHints = optionsSubset.buttonHints || {};
- let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text
- if (buttonKey != null && buttonHints[buttonKey] != null) {
- return buttonHints[buttonKey];
- }
- if (buttonHints[viewDef.type] != null) {
- return buttonHints[viewDef.type];
- }
- if (buttonHints[singleUnit] != null) {
- return buttonHints[singleUnit];
- }
- return null;
- };
- return {
- type: viewDef.type,
- component: viewDef.component,
- duration,
- durationUnit,
- singleUnit,
- optionDefaults: viewDef.defaults,
- optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides),
- buttonTextOverride: queryButtonText(dynamicOptionOverrides) ||
- queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence
- viewDef.overrides.buttonText,
- buttonTextDefault: queryButtonText(localeDefaults) ||
- viewDef.defaults.buttonText ||
- queryButtonText(BASE_OPTION_DEFAULTS) ||
- viewDef.type,
- // not DRY
- buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) ||
- queryButtonTitle(optionOverrides) ||
- viewDef.overrides.buttonHint,
- buttonTitleDefault: queryButtonTitle(localeDefaults) ||
- viewDef.defaults.buttonHint ||
- queryButtonTitle(BASE_OPTION_DEFAULTS),
- // will eventually fall back to buttonText
- };
- }
- // hack to get memoization working
- let durationInputMap = {};
- function createDurationCached(durationInput) {
- let json = JSON.stringify(durationInput);
- let res = durationInputMap[json];
- if (res === undefined) {
- res = createDuration(durationInput);
- durationInputMap[json] = res;
- }
- return res;
- }
- function reduceViewType(viewType, action) {
- switch (action.type) {
- case 'CHANGE_VIEW_TYPE':
- viewType = action.viewType;
- }
- return viewType;
- }
- function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
- switch (action.type) {
- case 'SET_OPTION':
- return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue });
- default:
- return dynamicOptionOverrides;
- }
- }
- function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) {
- let dp;
- switch (action.type) {
- case 'CHANGE_VIEW_TYPE':
- return dateProfileGenerator.build(action.dateMarker || currentDate);
- case 'CHANGE_DATE':
- return dateProfileGenerator.build(action.dateMarker);
- case 'PREV':
- dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate);
- if (dp.isValid) {
- return dp;
- }
- break;
- case 'NEXT':
- dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate);
- if (dp.isValid) {
- return dp;
- }
- break;
- }
- return currentDateProfile;
- }
- function initEventSources(calendarOptions, dateProfile, context) {
- let activeRange = dateProfile ? dateProfile.activeRange : null;
- return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context);
- }
- function reduceEventSources(eventSources, action, dateProfile, context) {
- let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
- switch (action.type) {
- case 'ADD_EVENT_SOURCES': // already parsed
- return addSources(eventSources, action.sources, activeRange, context);
- case 'REMOVE_EVENT_SOURCE':
- return removeSource(eventSources, action.sourceId);
- case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
- case 'NEXT':
- case 'CHANGE_DATE':
- case 'CHANGE_VIEW_TYPE':
- if (dateProfile) {
- return fetchDirtySources(eventSources, activeRange, context);
- }
- return eventSources;
- case 'FETCH_EVENT_SOURCES':
- return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type?
- arrayToHash(action.sourceIds) :
- excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context);
- case 'RECEIVE_EVENTS':
- case 'RECEIVE_EVENT_ERROR':
- return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange);
- case 'REMOVE_ALL_EVENT_SOURCES':
- return {};
- default:
- return eventSources;
- }
- }
- function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) {
- let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
- return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context);
- }
- function computeEventSourcesLoading(eventSources) {
- for (let sourceId in eventSources) {
- if (eventSources[sourceId].isFetching) {
- return true;
- }
- }
- return false;
- }
- function addSources(eventSourceHash, sources, fetchRange, context) {
- let hash = {};
- for (let source of sources) {
- hash[source.sourceId] = source;
- }
- if (fetchRange) {
- hash = fetchDirtySources(hash, fetchRange, context);
- }
- return Object.assign(Object.assign({}, eventSourceHash), hash);
- }
- function removeSource(eventSourceHash, sourceId) {
- return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId);
- }
- function fetchDirtySources(sourceHash, fetchRange, context) {
- return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context);
- }
- function isSourceDirty(eventSource, fetchRange, context) {
- if (!doesSourceNeedRange(eventSource, context)) {
- return !eventSource.latestFetchId;
- }
- return !context.options.lazyFetching ||
- !eventSource.fetchRange ||
- eventSource.isFetching || // always cancel outdated in-progress fetches
- fetchRange.start < eventSource.fetchRange.start ||
- fetchRange.end > eventSource.fetchRange.end;
- }
- function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) {
- let nextSources = {};
- for (let sourceId in prevSources) {
- let source = prevSources[sourceId];
- if (sourceIdHash[sourceId]) {
- nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context);
- }
- else {
- nextSources[sourceId] = source;
- }
- }
- return nextSources;
- }
- function fetchSource(eventSource, fetchRange, isRefetch, context) {
- let { options, calendarApi } = context;
- let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId];
- let fetchId = guid();
- sourceDef.fetch({
- eventSource,
- range: fetchRange,
- isRefetch,
- context,
- }, (res) => {
- let { rawEvents } = res;
- if (options.eventSourceSuccess) {
- rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents;
- }
- if (eventSource.success) {
- rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents;
- }
- context.dispatch({
- type: 'RECEIVE_EVENTS',
- sourceId: eventSource.sourceId,
- fetchId,
- fetchRange,
- rawEvents,
- });
- }, (error) => {
- let errorHandled = false;
- if (options.eventSourceFailure) {
- options.eventSourceFailure.call(calendarApi, error);
- errorHandled = true;
- }
- if (eventSource.failure) {
- eventSource.failure(error);
- errorHandled = true;
- }
- if (!errorHandled) {
- console.warn(error.message, error);
- }
- context.dispatch({
- type: 'RECEIVE_EVENT_ERROR',
- sourceId: eventSource.sourceId,
- fetchId,
- fetchRange,
- error,
- });
- });
- return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId });
- }
- function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) {
- let eventSource = sourceHash[sourceId];
- if (eventSource && // not already removed
- fetchId === eventSource.latestFetchId) {
- return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) });
- }
- return sourceHash;
- }
- function excludeStaticSources(eventSources, context) {
- return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context));
- }
- function parseInitialSources(rawOptions, context) {
- let refiners = buildEventSourceRefiners(context);
- let rawSources = [].concat(rawOptions.eventSources || []);
- let sources = []; // parsed
- if (rawOptions.initialEvents) {
- rawSources.unshift(rawOptions.initialEvents);
- }
- if (rawOptions.events) {
- rawSources.unshift(rawOptions.events);
- }
- for (let rawSource of rawSources) {
- let source = parseEventSource(rawSource, context, refiners);
- if (source) {
- sources.push(source);
- }
- }
- return sources;
- }
- function doesSourceNeedRange(eventSource, context) {
- let defs = context.pluginHooks.eventSourceDefs;
- return !defs[eventSource.sourceDefId].ignoreRange;
- }
- function reduceDateSelection(currentSelection, action) {
- switch (action.type) {
- case 'UNSELECT_DATES':
- return null;
- case 'SELECT_DATES':
- return action.selection;
- default:
- return currentSelection;
- }
- }
- function reduceSelectedEvent(currentInstanceId, action) {
- switch (action.type) {
- case 'UNSELECT_EVENT':
- return '';
- case 'SELECT_EVENT':
- return action.eventInstanceId;
- default:
- return currentInstanceId;
- }
- }
- function reduceEventDrag(currentDrag, action) {
- let newDrag;
- switch (action.type) {
- case 'UNSET_EVENT_DRAG':
- return null;
- case 'SET_EVENT_DRAG':
- newDrag = action.state;
- return {
- affectedEvents: newDrag.affectedEvents,
- mutatedEvents: newDrag.mutatedEvents,
- isEvent: newDrag.isEvent,
- };
- default:
- return currentDrag;
- }
- }
- function reduceEventResize(currentResize, action) {
- let newResize;
- switch (action.type) {
- case 'UNSET_EVENT_RESIZE':
- return null;
- case 'SET_EVENT_RESIZE':
- newResize = action.state;
- return {
- affectedEvents: newResize.affectedEvents,
- mutatedEvents: newResize.mutatedEvents,
- isEvent: newResize.isEvent,
- };
- default:
- return currentResize;
- }
- }
- function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
- let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
- let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
- return { header, footer };
- }
- function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
- let sectionWidgets = {};
- let viewsWithButtons = [];
- let hasTitle = false;
- for (let sectionName in sectionStrHash) {
- let sectionStr = sectionStrHash[sectionName];
- let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi);
- sectionWidgets[sectionName] = sectionRes.widgets;
- viewsWithButtons.push(...sectionRes.viewsWithButtons);
- hasTitle = hasTitle || sectionRes.hasTitle;
- }
- return { sectionWidgets, viewsWithButtons, hasTitle };
- }
- /*
- BAD: querying icons and text here. should be done at render time
- */
- function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined
- calendarOptionOverrides, // overrides only!, unrefined :(
- theme, viewSpecs, calendarApi) {
- let isRtl = calendarOptions.direction === 'rtl';
- let calendarCustomButtons = calendarOptions.customButtons || {};
- let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {};
- let calendarButtonText = calendarOptions.buttonText || {};
- let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {};
- let calendarButtonHints = calendarOptions.buttonHints || {};
- let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
- let viewsWithButtons = [];
- let hasTitle = false;
- let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => {
- if (buttonName === 'title') {
- hasTitle = true;
- return { buttonName };
- }
- let customButtonProps;
- let viewSpec;
- let buttonClick;
- let buttonIcon; // only one of these will be set
- let buttonText; // "
- let buttonHint;
- // ^ for the title="" attribute, for accessibility
- if ((customButtonProps = calendarCustomButtons[buttonName])) {
- buttonClick = (ev) => {
- if (customButtonProps.click) {
- customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context?
- }
- };
- (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) ||
- (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
- (buttonText = customButtonProps.text);
- buttonHint = customButtonProps.hint || customButtonProps.text;
- }
- else if ((viewSpec = viewSpecs[buttonName])) {
- viewsWithButtons.push(buttonName);
- buttonClick = () => {
- calendarApi.changeView(buttonName);
- };
- (buttonText = viewSpec.buttonTextOverride) ||
- (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
- (buttonText = viewSpec.buttonTextDefault);
- let textFallback = viewSpec.buttonTextOverride ||
- viewSpec.buttonTextDefault;
- buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride ||
- viewSpec.buttonTitleDefault ||
- calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName
- textFallback);
- }
- else if (calendarApi[buttonName]) { // a calendarApi method
- buttonClick = () => {
- calendarApi[buttonName]();
- };
- (buttonText = calendarButtonTextOverrides[buttonName]) ||
- (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
- (buttonText = calendarButtonText[buttonName]); // everything else is considered default
- if (buttonName === 'prevYear' || buttonName === 'nextYear') {
- let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next';
- buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] ||
- calendarButtonHints[prevOrNext], [
- calendarButtonText.year || 'year',
- 'year',
- ], calendarButtonText[buttonName]);
- }
- else {
- buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] ||
- calendarButtonHints[buttonName], [
- calendarButtonText[navUnit] || navUnit,
- navUnit,
- ], calendarButtonText[buttonName]);
- }
- }
- return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint };
- })));
- return { widgets, viewsWithButtons, hasTitle };
- }
- // always represents the current view. otherwise, it'd need to change value every time date changes
- class ViewImpl {
- constructor(type, getCurrentData, dateEnv) {
- this.type = type;
- this.getCurrentData = getCurrentData;
- this.dateEnv = dateEnv;
- }
- get calendar() {
- return this.getCurrentData().calendarApi;
- }
- get title() {
- return this.getCurrentData().viewTitle;
- }
- get activeStart() {
- return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
- }
- get activeEnd() {
- return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
- }
- get currentStart() {
- return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
- }
- get currentEnd() {
- return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
- }
- getOption(name) {
- return this.getCurrentData().options[name]; // are the view-specific options
- }
- }
- let eventSourceDef$2 = {
- ignoreRange: true,
- parseMeta(refined) {
- if (Array.isArray(refined.events)) {
- return refined.events;
- }
- return null;
- },
- fetch(arg, successCallback) {
- successCallback({
- rawEvents: arg.eventSource.meta,
- });
- },
- };
- const arrayEventSourcePlugin = createPlugin({
- name: 'array-event-source',
- eventSourceDefs: [eventSourceDef$2],
- });
- let eventSourceDef$1 = {
- parseMeta(refined) {
- if (typeof refined.events === 'function') {
- return refined.events;
- }
- return null;
- },
- fetch(arg, successCallback, errorCallback) {
- const { dateEnv } = arg.context;
- const func = arg.eventSource.meta;
- unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback);
- },
- };
- const funcEventSourcePlugin = createPlugin({
- name: 'func-event-source',
- eventSourceDefs: [eventSourceDef$1],
- });
- const JSON_FEED_EVENT_SOURCE_REFINERS = {
- method: String,
- extraParams: identity,
- startParam: String,
- endParam: String,
- timeZoneParam: String,
- };
- let eventSourceDef = {
- parseMeta(refined) {
- if (refined.url && (refined.format === 'json' || !refined.format)) {
- return {
- url: refined.url,
- format: 'json',
- method: (refined.method || 'GET').toUpperCase(),
- extraParams: refined.extraParams,
- startParam: refined.startParam,
- endParam: refined.endParam,
- timeZoneParam: refined.timeZoneParam,
- };
- }
- return null;
- },
- fetch(arg, successCallback, errorCallback) {
- const { meta } = arg.eventSource;
- const requestParams = buildRequestParams(meta, arg.range, arg.context);
- requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => {
- successCallback({ rawEvents, response });
- }, errorCallback);
- },
- };
- const jsonFeedEventSourcePlugin = createPlugin({
- name: 'json-event-source',
- eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS,
- eventSourceDefs: [eventSourceDef],
- });
- function buildRequestParams(meta, range, context) {
- let { dateEnv, options } = context;
- let startParam;
- let endParam;
- let timeZoneParam;
- let customRequestParams;
- let params = {};
- startParam = meta.startParam;
- if (startParam == null) {
- startParam = options.startParam;
- }
- endParam = meta.endParam;
- if (endParam == null) {
- endParam = options.endParam;
- }
- timeZoneParam = meta.timeZoneParam;
- if (timeZoneParam == null) {
- timeZoneParam = options.timeZoneParam;
- }
- // retrieve any outbound GET/POST data from the options
- if (typeof meta.extraParams === 'function') {
- // supplied as a function that returns a key/value object
- customRequestParams = meta.extraParams();
- }
- else {
- // probably supplied as a straight key/value object
- customRequestParams = meta.extraParams || {};
- }
- Object.assign(params, customRequestParams);
- params[startParam] = dateEnv.formatIso(range.start);
- params[endParam] = dateEnv.formatIso(range.end);
- if (dateEnv.timeZone !== 'local') {
- params[timeZoneParam] = dateEnv.timeZone;
- }
- return params;
- }
- const SIMPLE_RECURRING_REFINERS = {
- daysOfWeek: identity,
- startTime: createDuration,
- endTime: createDuration,
- duration: createDuration,
- startRecur: identity,
- endRecur: identity,
- };
- let recurring = {
- parse(refined, dateEnv) {
- if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) {
- let recurringData = {
- daysOfWeek: refined.daysOfWeek || null,
- startTime: refined.startTime || null,
- endTime: refined.endTime || null,
- startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null,
- endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null,
- };
- let duration;
- if (refined.duration) {
- duration = refined.duration;
- }
- if (!duration && refined.startTime && refined.endTime) {
- duration = subtractDurations(refined.endTime, refined.startTime);
- }
- return {
- allDayGuess: Boolean(!refined.startTime && !refined.endTime),
- duration,
- typeData: recurringData, // doesn't need endTime anymore but oh well
- };
- }
- return null;
- },
- expand(typeData, framingRange, dateEnv) {
- let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur });
- if (clippedFramingRange) {
- return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv);
- }
- return [];
- },
- };
- const simpleRecurringEventsPlugin = createPlugin({
- name: 'simple-recurring-event',
- recurringTypes: [recurring],
- eventRefiners: SIMPLE_RECURRING_REFINERS,
- });
- function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) {
- let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null;
- let dayMarker = startOfDay(framingRange.start);
- let endMarker = framingRange.end;
- let instanceStarts = [];
- while (dayMarker < endMarker) {
- let instanceStart;
- // if everyday, or this particular day-of-week
- if (!dowHash || dowHash[dayMarker.getUTCDay()]) {
- if (startTime) {
- instanceStart = dateEnv.add(dayMarker, startTime);
- }
- else {
- instanceStart = dayMarker;
- }
- instanceStarts.push(instanceStart);
- }
- dayMarker = addDays(dayMarker, 1);
- }
- return instanceStarts;
- }
- const changeHandlerPlugin = createPlugin({
- name: 'change-handler',
- optionChangeHandlers: {
- events(events, context) {
- handleEventSources([events], context);
- },
- eventSources: handleEventSources,
- },
- });
- /*
- BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out
- */
- function handleEventSources(inputs, context) {
- let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources);
- if (unfoundSources.length === 1 &&
- inputs.length === 1 &&
- Array.isArray(unfoundSources[0]._raw) &&
- Array.isArray(inputs[0])) {
- context.dispatch({
- type: 'RESET_RAW_EVENTS',
- sourceId: unfoundSources[0].sourceId,
- rawEvents: inputs[0],
- });
- return;
- }
- let newInputs = [];
- for (let input of inputs) {
- let inputFound = false;
- for (let i = 0; i < unfoundSources.length; i += 1) {
- if (unfoundSources[i]._raw === input) {
- unfoundSources.splice(i, 1); // delete
- inputFound = true;
- break;
- }
- }
- if (!inputFound) {
- newInputs.push(input);
- }
- }
- for (let unfoundSource of unfoundSources) {
- context.dispatch({
- type: 'REMOVE_EVENT_SOURCE',
- sourceId: unfoundSource.sourceId,
- });
- }
- for (let newInput of newInputs) {
- context.calendarApi.addEventSource(newInput);
- }
- }
- function handleDateProfile(dateProfile, context) {
- context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi }));
- }
- function handleEventStore(eventStore, context) {
- let { emitter } = context;
- if (emitter.hasHandlers('eventsSet')) {
- emitter.trigger('eventsSet', buildEventApis(eventStore, context));
- }
- }
- /*
- this array is exposed on the root namespace so that UMD plugins can add to it.
- see the rollup-bundles script.
- */
- const globalPlugins = [
- arrayEventSourcePlugin,
- funcEventSourcePlugin,
- jsonFeedEventSourcePlugin,
- simpleRecurringEventsPlugin,
- changeHandlerPlugin,
- createPlugin({
- name: 'misc',
- isLoadingFuncs: [
- (state) => computeEventSourcesLoading(state.eventSources),
- ],
- propSetHandlers: {
- dateProfile: handleDateProfile,
- eventStore: handleEventStore,
- },
- }),
- ];
- class TaskRunner {
- constructor(runTaskOption, drainedOption) {
- this.runTaskOption = runTaskOption;
- this.drainedOption = drainedOption;
- this.queue = [];
- this.delayedRunner = new DelayedRunner(this.drain.bind(this));
- }
- request(task, delay) {
- this.queue.push(task);
- this.delayedRunner.request(delay);
- }
- pause(scope) {
- this.delayedRunner.pause(scope);
- }
- resume(scope, force) {
- this.delayedRunner.resume(scope, force);
- }
- drain() {
- let { queue } = this;
- while (queue.length) {
- let completedTasks = [];
- let task;
- while ((task = queue.shift())) {
- this.runTask(task);
- completedTasks.push(task);
- }
- this.drained(completedTasks);
- } // keep going, in case new tasks were added in the drained handler
- }
- runTask(task) {
- if (this.runTaskOption) {
- this.runTaskOption(task);
- }
- }
- drained(completedTasks) {
- if (this.drainedOption) {
- this.drainedOption(completedTasks);
- }
- }
- }
- // Computes what the title at the top of the calendarApi should be for this view
- function buildTitle(dateProfile, viewOptions, dateEnv) {
- let range;
- // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
- if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
- range = dateProfile.currentRange;
- }
- else { // for day units or smaller, use the actual day range
- range = dateProfile.activeRange;
- }
- return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), {
- isEndExclusive: dateProfile.isRangeAllDay,
- defaultSeparator: viewOptions.titleRangeSeparator,
- });
- }
- // Generates the format string that should be used to generate the title for the current date range.
- // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
- function buildTitleFormat(dateProfile) {
- let { currentRangeUnit } = dateProfile;
- if (currentRangeUnit === 'year') {
- return { year: 'numeric' };
- }
- if (currentRangeUnit === 'month') {
- return { year: 'numeric', month: 'long' }; // like "September 2014"
- }
- let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
- if (days !== null && days > 1) {
- // multi-day range. shorter, like "Sep 9 - 10 2014"
- return { year: 'numeric', month: 'short', day: 'numeric' };
- }
- // one day. longer, like "September 9 2014"
- return { year: 'numeric', month: 'long', day: 'numeric' };
- }
- // in future refactor, do the redux-style function(state=initial) for initial-state
- // also, whatever is happening in constructor, have it happen in action queue too
- class CalendarDataManager {
- constructor(props) {
- this.computeCurrentViewData = memoize(this._computeCurrentViewData);
- this.organizeRawLocales = memoize(organizeRawLocales);
- this.buildLocale = memoize(buildLocale);
- this.buildPluginHooks = buildBuildPluginHooks();
- this.buildDateEnv = memoize(buildDateEnv$1);
- this.buildTheme = memoize(buildTheme);
- this.parseToolbars = memoize(parseToolbars);
- this.buildViewSpecs = memoize(buildViewSpecs);
- this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
- this.buildViewApi = memoize(buildViewApi);
- this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
- this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual);
- this.buildEventUiBases = memoize(buildEventUiBases);
- this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
- this.buildTitle = memoize(buildTitle);
- this.emitter = new Emitter();
- this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this));
- this.currentCalendarOptionsInput = {};
- this.currentCalendarOptionsRefined = {};
- this.currentViewOptionsInput = {};
- this.currentViewOptionsRefined = {};
- this.currentCalendarOptionsRefiners = {};
- this.optionsForRefining = [];
- this.optionsForHandling = [];
- this.getCurrentData = () => this.data;
- this.dispatch = (action) => {
- this.actionRunner.request(action); // protects against recursive calls to _handleAction
- };
- this.props = props;
- this.actionRunner.pause();
- let dynamicOptionOverrides = {};
- let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
- let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView;
- let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
- // wire things up
- // TODO: not DRY
- props.calendarApi.currentDataManager = this;
- this.emitter.setThisContext(props.calendarApi);
- this.emitter.setOptions(currentViewData.options);
- let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv);
- let dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
- if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) {
- currentDate = dateProfile.currentRange.start;
- }
- let calendarContext = {
- dateEnv: optionsData.dateEnv,
- options: optionsData.calendarOptions,
- pluginHooks: optionsData.pluginHooks,
- calendarApi: props.calendarApi,
- dispatch: this.dispatch,
- emitter: this.emitter,
- getCurrentData: this.getCurrentData,
- };
- // needs to be after setThisContext
- for (let callback of optionsData.pluginHooks.contextInit) {
- callback(calendarContext);
- }
- // NOT DRY
- let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext);
- let initialState = {
- dynamicOptionOverrides,
- currentViewType,
- currentDate,
- dateProfile,
- businessHours: this.parseContextBusinessHours(calendarContext),
- eventSources,
- eventUiBases: {},
- eventStore: createEmptyEventStore(),
- renderableEventStore: createEmptyEventStore(),
- dateSelection: null,
- eventSelection: '',
- eventDrag: null,
- eventResize: null,
- selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig,
- };
- let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState);
- for (let reducer of optionsData.pluginHooks.reducers) {
- Object.assign(initialState, reducer(null, null, contextAndState));
- }
- if (computeIsLoading(initialState, calendarContext)) {
- this.emitter.trigger('loading', true); // NOT DRY
- }
- this.state = initialState;
- this.updateData();
- this.actionRunner.resume();
- }
- resetOptions(optionOverrides, changedOptionNames) {
- let { props } = this;
- if (changedOptionNames === undefined) {
- props.optionOverrides = optionOverrides;
- }
- else {
- props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides);
- this.optionsForRefining.push(...changedOptionNames);
- }
- if (changedOptionNames === undefined || changedOptionNames.length) {
- this.actionRunner.request({
- type: 'NOTHING',
- });
- }
- }
- _handleAction(action) {
- let { props, state, emitter } = this;
- let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action);
- let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
- let currentViewType = reduceViewType(state.currentViewType, action);
- let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
- // wire things up
- // TODO: not DRY
- props.calendarApi.currentDataManager = this;
- emitter.setThisContext(props.calendarApi);
- emitter.setOptions(currentViewData.options);
- let calendarContext = {
- dateEnv: optionsData.dateEnv,
- options: optionsData.calendarOptions,
- pluginHooks: optionsData.pluginHooks,
- calendarApi: props.calendarApi,
- dispatch: this.dispatch,
- emitter,
- getCurrentData: this.getCurrentData,
- };
- let { currentDate, dateProfile } = state;
- if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack
- dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
- }
- currentDate = reduceCurrentDate(currentDate, action);
- dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator);
- if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator
- action.type === 'NEXT' || // "
- !rangeContainsMarker(dateProfile.currentRange, currentDate)) {
- currentDate = dateProfile.currentRange.start;
- }
- let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext);
- let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext);
- let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading
- let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ?
- (state.renderableEventStore || eventStore) : // try from previous state
- eventStore;
- let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj
- let eventUiBySource = this.buildEventUiBySource(eventSources);
- let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
- let newState = {
- dynamicOptionOverrides,
- currentViewType,
- currentDate,
- dateProfile,
- eventSources,
- eventStore,
- renderableEventStore,
- selectionConfig,
- eventUiBases,
- businessHours: this.parseContextBusinessHours(calendarContext),
- dateSelection: reduceDateSelection(state.dateSelection, action),
- eventSelection: reduceSelectedEvent(state.eventSelection, action),
- eventDrag: reduceEventDrag(state.eventDrag, action),
- eventResize: reduceEventResize(state.eventResize, action),
- };
- let contextAndState = Object.assign(Object.assign({}, calendarContext), newState);
- for (let reducer of optionsData.pluginHooks.reducers) {
- Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value
- }
- let wasLoading = computeIsLoading(state, calendarContext);
- let isLoading = computeIsLoading(newState, calendarContext);
- // TODO: use propSetHandlers in plugin system
- if (!wasLoading && isLoading) {
- emitter.trigger('loading', true);
- }
- else if (wasLoading && !isLoading) {
- emitter.trigger('loading', false);
- }
- this.state = newState;
- if (props.onAction) {
- props.onAction(action);
- }
- }
- updateData() {
- let { props, state } = this;
- let oldData = this.data;
- let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi);
- let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides);
- let data = this.data = Object.assign(Object.assign(Object.assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state);
- let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
- let oldCalendarOptions = oldData && oldData.calendarOptions;
- let newCalendarOptions = optionsData.calendarOptions;
- if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) {
- if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
- // hack
- state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data);
- state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv);
- state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv);
- }
- for (let optionName in changeHandlers) {
- if (this.optionsForHandling.indexOf(optionName) !== -1 ||
- oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
- changeHandlers[optionName](newCalendarOptions[optionName], data);
- }
- }
- }
- this.optionsForHandling = [];
- if (props.onData) {
- props.onData(data);
- }
- }
- computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
- // TODO: blacklist options that are handled by optionChangeHandlers
- if (!this.optionsForRefining.length &&
- optionOverrides === this.stableOptionOverrides &&
- dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
- return this.stableCalendarOptionsData;
- }
- let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
- warnUnknownOptions(extra);
- let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator);
- let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults);
- let theme = this.buildTheme(refinedOptions, pluginHooks);
- let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi);
- return this.stableCalendarOptionsData = {
- calendarOptions: refinedOptions,
- pluginHooks,
- dateEnv,
- viewSpecs,
- theme,
- toolbarConfig,
- localeDefaults,
- availableRawLocales: availableLocaleData.map,
- };
- }
- // always called from behind a memoizer
- processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
- let { locales, locale } = mergeRawOptions([
- BASE_OPTION_DEFAULTS,
- optionOverrides,
- dynamicOptionOverrides,
- ]);
- let availableLocaleData = this.organizeRawLocales(locales);
- let availableRawLocales = availableLocaleData.map;
- let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
- let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
- let refiners = this.currentCalendarOptionsRefiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
- let extra = {};
- let raw = mergeRawOptions([
- BASE_OPTION_DEFAULTS,
- localeDefaults,
- optionOverrides,
- dynamicOptionOverrides,
- ]);
- let refined = {};
- let currentRaw = this.currentCalendarOptionsInput;
- let currentRefined = this.currentCalendarOptionsRefined;
- let anyChanges = false;
- for (let optionName in raw) {
- if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
- (optionName in currentRaw) &&
- COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) {
- refined[optionName] = currentRefined[optionName];
- }
- else if (refiners[optionName]) {
- refined[optionName] = refiners[optionName](raw[optionName]);
- anyChanges = true;
- }
- else {
- extra[optionName] = currentRaw[optionName];
- }
- }
- if (anyChanges) {
- this.currentCalendarOptionsInput = raw;
- this.currentCalendarOptionsRefined = refined;
- this.stableOptionOverrides = optionOverrides;
- this.stableDynamicOptionOverrides = dynamicOptionOverrides;
- }
- this.optionsForHandling.push(...this.optionsForRefining);
- this.optionsForRefining = [];
- return {
- rawOptions: this.currentCalendarOptionsInput,
- refinedOptions: this.currentCalendarOptionsRefined,
- pluginHooks,
- availableLocaleData,
- localeDefaults,
- extra,
- };
- }
- _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
- let viewSpec = optionsData.viewSpecs[viewType];
- if (!viewSpec) {
- throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
- }
- let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
- warnUnknownOptions(extra);
- let dateProfileGenerator = this.buildDateProfileGenerator({
- dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
- duration: viewSpec.duration,
- durationUnit: viewSpec.durationUnit,
- usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
- dateEnv: optionsData.dateEnv,
- calendarApi: this.props.calendarApi,
- slotMinTime: refinedOptions.slotMinTime,
- slotMaxTime: refinedOptions.slotMaxTime,
- showNonCurrentDates: refinedOptions.showNonCurrentDates,
- dayCount: refinedOptions.dayCount,
- dateAlignment: refinedOptions.dateAlignment,
- dateIncrement: refinedOptions.dateIncrement,
- hiddenDays: refinedOptions.hiddenDays,
- weekends: refinedOptions.weekends,
- nowInput: refinedOptions.now,
- validRangeInput: refinedOptions.validRange,
- visibleRangeInput: refinedOptions.visibleRange,
- fixedWeekCount: refinedOptions.fixedWeekCount,
- });
- let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
- return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
- }
- processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
- let raw = mergeRawOptions([
- BASE_OPTION_DEFAULTS,
- viewSpec.optionDefaults,
- localeDefaults,
- optionOverrides,
- viewSpec.optionOverrides,
- dynamicOptionOverrides,
- ]);
- let refiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
- let refined = {};
- let currentRaw = this.currentViewOptionsInput;
- let currentRefined = this.currentViewOptionsRefined;
- let anyChanges = false;
- let extra = {};
- for (let optionName in raw) {
- if (raw[optionName] === currentRaw[optionName] ||
- (COMPLEX_OPTION_COMPARATORS[optionName] &&
- COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) {
- refined[optionName] = currentRefined[optionName];
- }
- else {
- if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
- (COMPLEX_OPTION_COMPARATORS[optionName] &&
- COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
- if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
- refined[optionName] = this.currentCalendarOptionsRefined[optionName];
- }
- }
- else if (refiners[optionName]) {
- refined[optionName] = refiners[optionName](raw[optionName]);
- }
- else {
- extra[optionName] = raw[optionName];
- }
- anyChanges = true;
- }
- }
- if (anyChanges) {
- this.currentViewOptionsInput = raw;
- this.currentViewOptionsRefined = refined;
- }
- return {
- rawOptions: this.currentViewOptionsInput,
- refinedOptions: this.currentViewOptionsRefined,
- extra,
- };
- }
- }
- function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) {
- let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
- return new DateEnv({
- calendarSystem: 'gregory',
- timeZone,
- namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl,
- locale,
- weekNumberCalculation,
- firstDay,
- weekText,
- cmdFormatter: pluginHooks.cmdFormatter,
- defaultSeparator,
- });
- }
- function buildTheme(options, pluginHooks) {
- let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme;
- return new ThemeClass(options);
- }
- function buildDateProfileGenerator(props) {
- let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
- return new DateProfileGeneratorClass(props);
- }
- function buildViewApi(type, getCurrentData, dateEnv) {
- return new ViewImpl(type, getCurrentData, dateEnv);
- }
- function buildEventUiBySource(eventSources) {
- return mapHash(eventSources, (eventSource) => eventSource.ui);
- }
- function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
- let eventUiBases = { '': eventUiSingleBase };
- for (let defId in eventDefs) {
- let def = eventDefs[defId];
- if (def.sourceId && eventUiBySource[def.sourceId]) {
- eventUiBases[defId] = eventUiBySource[def.sourceId];
- }
- }
- return eventUiBases;
- }
- function buildViewUiProps(calendarContext) {
- let { options } = calendarContext;
- return {
- eventUiSingleBase: createEventUi({
- display: options.eventDisplay,
- editable: options.editable,
- startEditable: options.eventStartEditable,
- durationEditable: options.eventDurationEditable,
- constraint: options.eventConstraint,
- overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
- allow: options.eventAllow,
- backgroundColor: options.eventBackgroundColor,
- borderColor: options.eventBorderColor,
- textColor: options.eventTextColor,
- color: options.eventColor,
- // classNames: options.eventClassNames // render hook will handle this
- }, calendarContext),
- selectionConfig: createEventUi({
- constraint: options.selectConstraint,
- overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
- allow: options.selectAllow,
- }, calendarContext),
- };
- }
- function computeIsLoading(state, context) {
- for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
- if (isLoadingFunc(state)) {
- return true;
- }
- }
- return false;
- }
- function parseContextBusinessHours(calendarContext) {
- return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
- }
- function warnUnknownOptions(options, viewName) {
- for (let optionName in options) {
- console.warn(`Unknown option '${optionName}'` +
- (viewName ? ` for view '${viewName}'` : ''));
- }
- }
- class ToolbarSection extends BaseComponent {
- render() {
- let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
- return createElement('div', { className: 'fc-toolbar-chunk' }, ...children);
- }
- renderWidgetGroup(widgetGroup) {
- let { props } = this;
- let { theme } = this.context;
- let children = [];
- let isOnlyButtons = true;
- for (let widget of widgetGroup) {
- let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget;
- if (buttonName === 'title') {
- isOnlyButtons = false;
- children.push(createElement("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title));
- }
- else {
- let isPressed = buttonName === props.activeButton;
- let isDisabled = (!props.isTodayEnabled && buttonName === 'today') ||
- (!props.isPrevEnabled && buttonName === 'prev') ||
- (!props.isNextEnabled && buttonName === 'next');
- let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')];
- if (isPressed) {
- buttonClasses.push(theme.getClass('buttonActive'));
- }
- children.push(createElement("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? createElement("span", { className: buttonIcon, role: "img" }) : '')));
- }
- }
- if (children.length > 1) {
- let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || '';
- return createElement('div', { className: groupClassName }, ...children);
- }
- return children[0];
- }
- }
- class Toolbar extends BaseComponent {
- render() {
- let { model, extraClassName } = this.props;
- let forceLtr = false;
- let startContent;
- let endContent;
- let sectionWidgets = model.sectionWidgets;
- let centerContent = sectionWidgets.center;
- if (sectionWidgets.left) {
- forceLtr = true;
- startContent = sectionWidgets.left;
- }
- else {
- startContent = sectionWidgets.start;
- }
- if (sectionWidgets.right) {
- forceLtr = true;
- endContent = sectionWidgets.right;
- }
- else {
- endContent = sectionWidgets.end;
- }
- let classNames = [
- extraClassName || '',
- 'fc-toolbar',
- forceLtr ? 'fc-toolbar-ltr' : '',
- ];
- return (createElement("div", { className: classNames.join(' ') },
- this.renderSection('start', startContent || []),
- this.renderSection('center', centerContent || []),
- this.renderSection('end', endContent || [])));
- }
- renderSection(key, widgetGroups) {
- let { props } = this;
- return (createElement(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, navUnit: props.navUnit, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled, titleId: props.titleId }));
- }
- }
- class ViewHarness extends BaseComponent {
- constructor() {
- super(...arguments);
- this.state = {
- availableWidth: null,
- };
- this.handleEl = (el) => {
- this.el = el;
- setRef(this.props.elRef, el);
- this.updateAvailableWidth();
- };
- this.handleResize = () => {
- this.updateAvailableWidth();
- };
- }
- render() {
- let { props, state } = this;
- let { aspectRatio } = props;
- let classNames = [
- 'fc-view-harness',
- (aspectRatio || props.liquid || props.height)
- ? 'fc-view-harness-active' // harness controls the height
- : 'fc-view-harness-passive', // let the view do the height
- ];
- let height = '';
- let paddingBottom = '';
- if (aspectRatio) {
- if (state.availableWidth !== null) {
- height = state.availableWidth / aspectRatio;
- }
- else {
- // while waiting to know availableWidth, we can't set height to *zero*
- // because will cause lots of unnecessary scrollbars within scrollgrid.
- // BETTER: don't start rendering ANYTHING yet until we know container width
- // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
- paddingBottom = `${(1 / aspectRatio) * 100}%`;
- }
- }
- else {
- height = props.height || '';
- }
- return (createElement("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children));
- }
- componentDidMount() {
- this.context.addResizeHandler(this.handleResize);
- }
- componentWillUnmount() {
- this.context.removeResizeHandler(this.handleResize);
- }
- updateAvailableWidth() {
- if (this.el && // needed. but why?
- this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
- ) {
- this.setState({ availableWidth: this.el.offsetWidth });
- }
- }
- }
- /*
- Detects when the user clicks on an event within a DateComponent
- */
- class EventClicking extends Interaction {
- constructor(settings) {
- super(settings);
- this.handleSegClick = (ev, segEl) => {
- let { component } = this;
- let { context } = component;
- let seg = getElSeg(segEl);
- if (seg && // might be the <div> surrounding the more link
- component.isValidSegDownEl(ev.target)) {
- // our way to simulate a link click for elements that can't be <a> tags
- // grab before trigger fired in case trigger trashes DOM thru rerendering
- let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
- let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
- context.emitter.trigger('eventClick', {
- el: segEl,
- event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance),
- jsEvent: ev,
- view: context.viewApi,
- });
- if (url && !ev.defaultPrevented) {
- window.location.href = url;
- }
- }
- };
- this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events
- this.handleSegClick);
- }
- }
- /*
- Triggers events and adds/removes core classNames when the user's pointer
- enters/leaves event-elements of a component.
- */
- class EventHovering extends Interaction {
- constructor(settings) {
- super(settings);
- // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
- this.handleEventElRemove = (el) => {
- if (el === this.currentSegEl) {
- this.handleSegLeave(null, this.currentSegEl);
- }
- };
- this.handleSegEnter = (ev, segEl) => {
- if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
- this.currentSegEl = segEl;
- this.triggerEvent('eventMouseEnter', ev, segEl);
- }
- };
- this.handleSegLeave = (ev, segEl) => {
- if (this.currentSegEl) {
- this.currentSegEl = null;
- this.triggerEvent('eventMouseLeave', ev, segEl);
- }
- };
- this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events
- this.handleSegEnter, this.handleSegLeave);
- }
- destroy() {
- this.removeHoverListeners();
- }
- triggerEvent(publicEvName, ev, segEl) {
- let { component } = this;
- let { context } = component;
- let seg = getElSeg(segEl);
- if (!ev || component.isValidSegDownEl(ev.target)) {
- context.emitter.trigger(publicEvName, {
- el: segEl,
- event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance),
- jsEvent: ev,
- view: context.viewApi,
- });
- }
- }
- }
- class CalendarContent extends PureComponent {
- constructor() {
- super(...arguments);
- this.buildViewContext = memoize(buildViewContext);
- this.buildViewPropTransformers = memoize(buildViewPropTransformers);
- this.buildToolbarProps = memoize(buildToolbarProps);
- this.headerRef = createRef();
- this.footerRef = createRef();
- this.interactionsStore = {};
- // eslint-disable-next-line
- this.state = {
- viewLabelId: getUniqueDomId(),
- };
- // Component Registration
- // -----------------------------------------------------------------------------------------------------------------
- this.registerInteractiveComponent = (component, settingsInput) => {
- let settings = parseInteractionSettings(component, settingsInput);
- let DEFAULT_INTERACTIONS = [
- EventClicking,
- EventHovering,
- ];
- let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions);
- let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
- this.interactionsStore[component.uid] = interactions;
- interactionSettingsStore[component.uid] = settings;
- };
- this.unregisterInteractiveComponent = (component) => {
- let listeners = this.interactionsStore[component.uid];
- if (listeners) {
- for (let listener of listeners) {
- listener.destroy();
- }
- delete this.interactionsStore[component.uid];
- }
- delete interactionSettingsStore[component.uid];
- };
- // Resizing
- // -----------------------------------------------------------------------------------------------------------------
- this.resizeRunner = new DelayedRunner(() => {
- this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ?
- this.props.emitter.trigger('windowResize', { view: this.props.viewApi });
- });
- this.handleWindowResize = (ev) => {
- let { options } = this.props;
- if (options.handleWindowResize &&
- ev.target === window // avoid jqui events
- ) {
- this.resizeRunner.request(options.windowResizeDelay);
- }
- };
- }
- /*
- renders INSIDE of an outer div
- */
- render() {
- let { props } = this;
- let { toolbarConfig, options } = props;
- let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer????
- props.viewTitle);
- let viewVGrow = false;
- let viewHeight = '';
- let viewAspectRatio;
- if (props.isHeightAuto || props.forPrint) {
- viewHeight = '';
- }
- else if (options.height != null) {
- viewVGrow = true;
- }
- else if (options.contentHeight != null) {
- viewHeight = options.contentHeight;
- }
- else {
- viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
- }
- let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
- let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
- ? this.state.viewLabelId
- : undefined;
- return (createElement(ViewContextType.Provider, { value: viewContext },
- toolbarConfig.header && (createElement(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))),
- createElement(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId },
- this.renderView(props),
- this.buildAppendContent()),
- toolbarConfig.footer && (createElement(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps)))));
- }
- componentDidMount() {
- let { props } = this;
- this.calendarInteractions = props.pluginHooks.calendarInteractions
- .map((CalendarInteractionClass) => new CalendarInteractionClass(props));
- window.addEventListener('resize', this.handleWindowResize);
- let { propSetHandlers } = props.pluginHooks;
- for (let propName in propSetHandlers) {
- propSetHandlers[propName](props[propName], props);
- }
- }
- componentDidUpdate(prevProps) {
- let { props } = this;
- let { propSetHandlers } = props.pluginHooks;
- for (let propName in propSetHandlers) {
- if (props[propName] !== prevProps[propName]) {
- propSetHandlers[propName](props[propName], props);
- }
- }
- }
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleWindowResize);
- this.resizeRunner.clear();
- for (let interaction of this.calendarInteractions) {
- interaction.destroy();
- }
- this.props.emitter.trigger('_unmount');
- }
- buildAppendContent() {
- let { props } = this;
- let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props));
- return createElement(Fragment, {}, ...children);
- }
- renderView(props) {
- let { pluginHooks } = props;
- let { viewSpec } = props;
- let viewProps = {
- dateProfile: props.dateProfile,
- businessHours: props.businessHours,
- eventStore: props.renderableEventStore,
- eventUiBases: props.eventUiBases,
- dateSelection: props.dateSelection,
- eventSelection: props.eventSelection,
- eventDrag: props.eventDrag,
- eventResize: props.eventResize,
- isHeightAuto: props.isHeightAuto,
- forPrint: props.forPrint,
- };
- let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
- for (let transformer of transformers) {
- Object.assign(viewProps, transformer.transform(viewProps, props));
- }
- let ViewComponent = viewSpec.component;
- return (createElement(ViewComponent, Object.assign({}, viewProps)));
- }
- }
- function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) {
- // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
- let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason
- let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false);
- let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false);
- return {
- title,
- activeButton: viewSpec.type,
- navUnit: viewSpec.singleUnit,
- isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
- isPrevEnabled: prevInfo.isValid,
- isNextEnabled: nextInfo.isValid,
- };
- }
- // Plugin
- // -----------------------------------------------------------------------------------------------------------------
- function buildViewPropTransformers(theClasses) {
- return theClasses.map((TheClass) => new TheClass());
- }
- class Calendar extends CalendarImpl {
- constructor(el, optionOverrides = {}) {
- super();
- this.isRendering = false;
- this.isRendered = false;
- this.currentClassNames = [];
- this.customContentRenderId = 0;
- this.handleAction = (action) => {
- // actions we know we want to render immediately
- switch (action.type) {
- case 'SET_EVENT_DRAG':
- case 'SET_EVENT_RESIZE':
- this.renderRunner.tryDrain();
- }
- };
- this.handleData = (data) => {
- this.currentData = data;
- this.renderRunner.request(data.calendarOptions.rerenderDelay);
- };
- this.handleRenderRequest = () => {
- if (this.isRendering) {
- this.isRendered = true;
- let { currentData } = this;
- flushSync(() => {
- render(createElement(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => {
- this.setClassNames(classNames);
- this.setHeight(height);
- return (createElement(RenderId.Provider, { value: this.customContentRenderId },
- createElement(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData))));
- }), this.el);
- });
- }
- else if (this.isRendered) {
- this.isRendered = false;
- render(null, this.el);
- this.setClassNames([]);
- this.setHeight('');
- }
- };
- ensureElHasStyles(el);
- this.el = el;
- this.renderRunner = new DelayedRunner(this.handleRenderRequest);
- new CalendarDataManager({
- optionOverrides,
- calendarApi: this,
- onAction: this.handleAction,
- onData: this.handleData,
- });
- }
- render() {
- let wasRendering = this.isRendering;
- if (!wasRendering) {
- this.isRendering = true;
- }
- else {
- this.customContentRenderId += 1;
- }
- this.renderRunner.request();
- if (wasRendering) {
- this.updateSize();
- }
- }
- destroy() {
- if (this.isRendering) {
- this.isRendering = false;
- this.renderRunner.request();
- }
- }
- updateSize() {
- flushSync(() => {
- super.updateSize();
- });
- }
- batchRendering(func) {
- this.renderRunner.pause('batchRendering');
- func();
- this.renderRunner.resume('batchRendering');
- }
- pauseRendering() {
- this.renderRunner.pause('pauseRendering');
- }
- resumeRendering() {
- this.renderRunner.resume('pauseRendering', true);
- }
- resetOptions(optionOverrides, changedOptionNames) {
- this.currentDataManager.resetOptions(optionOverrides, changedOptionNames);
- }
- setClassNames(classNames) {
- if (!isArraysEqual(classNames, this.currentClassNames)) {
- let { classList } = this.el;
- for (let className of this.currentClassNames) {
- classList.remove(className);
- }
- for (let className of classNames) {
- classList.add(className);
- }
- this.currentClassNames = classNames;
- }
- }
- setHeight(height) {
- applyStyleProp(this.el, 'height', height);
- }
- }
- function formatDate(dateInput, options = {}) {
- let dateEnv = buildDateEnv(options);
- let formatter = createFormatter(options);
- let dateMeta = dateEnv.createMarkerMeta(dateInput);
- if (!dateMeta) { // TODO: warning?
- return '';
- }
- return dateEnv.format(dateMeta.marker, formatter, {
- forcedTzo: dateMeta.forcedTzo,
- });
- }
- function formatRange(startInput, endInput, options) {
- let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object
- let formatter = createFormatter(options);
- let startMeta = dateEnv.createMarkerMeta(startInput);
- let endMeta = dateEnv.createMarkerMeta(endInput);
- if (!startMeta || !endMeta) { // TODO: warning?
- return '';
- }
- return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, {
- forcedStartTzo: startMeta.forcedTzo,
- forcedEndTzo: endMeta.forcedTzo,
- isEndExclusive: options.isEndExclusive,
- defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator,
- });
- }
- // TODO: more DRY and optimized
- function buildDateEnv(settings) {
- let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere
- return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale }));
- }
- // HELPERS
- /*
- if nextDayThreshold is specified, slicing is done in an all-day fashion.
- you can get nextDayThreshold from context.nextDayThreshold
- */
- function sliceEvents(props, allDay) {
- return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg;
- }
- const version = '6.1.14';
- export { Calendar, createPlugin, formatDate, formatRange, globalLocales, globalPlugins, sliceEvents, version };
|