@fullcalendar/vue
Version:
The official Vue 2 component for FullCalendar
259 lines (246 loc) • 9.68 kB
JavaScript
this.FullCalendar = this.FullCalendar || {};
this.FullCalendar.Vue = (function (exports, Vue, core, internal) {
'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var Vue__default = /*#__PURE__*/_interopDefaultLegacy(Vue);
const OPTION_IS_COMPLEX = {
headerToolbar: true,
footerToolbar: true,
events: true,
eventSources: true,
resources: true
};
const dummyContainer$1 = typeof document !== 'undefined' ? document.createDocumentFragment() : null;
const OffscreenFragment = Vue__default["default"].extend({
render(h) {
/*
Choose an exotic tag that FullCalendar's internal (p)react engine won't reuse
(For behavior, see: https://codepen.io/arshaw/pen/wvXPwYG)
*/
return h('aside', {
style: { display: 'none' }
}, this.$slots.default || []);
},
mounted() {
if (dummyContainer$1) {
dummyContainer$1.appendChild(this.$el);
}
},
beforeDestroy() {
if (dummyContainer$1) {
dummyContainer$1.removeChild(this.$el);
}
}
});
const dummyContainer = typeof document !== 'undefined' ? document.createDocumentFragment() : null;
const TransportContainer = Vue__default["default"].extend({
props: {
inPlaceOf: typeof Element !== 'undefined' ? Element : Object,
reportEl: Function,
elTag: String,
elClasses: Array,
elStyle: Object,
elAttrs: Object
},
render(h) {
return h(this.elTag, {
class: this.elClasses,
style: this.elStyle,
attrs: this.elAttrs,
}, this.$slots.default || []);
},
mounted() {
replaceEl(this.$el, this.inPlaceOf);
this.inPlaceOf.style.display = 'none';
this.reportEl(this.$el);
},
updated() {
/*
If the ContentContainer's tagName changed, it will create a new DOM element in its
original place. Detect this and re-replace.
*/
if (dummyContainer && this.inPlaceOf.parentNode !== dummyContainer) {
replaceEl(this.$el, this.inPlaceOf);
this.reportEl(this.$el);
}
},
beforeDestroy() {
// protect against Preact recreating and rerooting inPlaceOf element
if (dummyContainer && this.inPlaceOf.parentNode === dummyContainer) {
dummyContainer.removeChild(this.inPlaceOf);
}
this.reportEl(null);
}
});
function replaceEl(subject, inPlaceOf) {
var _a;
(_a = inPlaceOf.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(subject, inPlaceOf.nextSibling);
if (dummyContainer) {
dummyContainer.appendChild(inPlaceOf);
}
}
const FullCalendar = Vue__default["default"].extend({
props: {
options: Object
},
data() {
return {
renderId: 0,
customRenderingMap: new Map()
};
},
methods: {
getApi() {
return getSecret(this).calendar;
},
buildOptions(suppliedOptions) {
return Object.assign(Object.assign({}, suppliedOptions), { customRenderingMetaMap: kebabToCamelKeys(this.$scopedSlots), handleCustomRendering: getSecret(this).handleCustomRendering, customRenderingReplaces: true });
},
},
render(h) {
const customRenderingNodes = [];
for (const customRendering of this.customRenderingMap.values()) {
const innerContent = typeof customRendering.generatorMeta === 'function' ?
customRendering.generatorMeta(customRendering.renderProps) : // a slot-render-function
customRendering.generatorMeta; // jsx vnode?
customRenderingNodes.push(
// need stable element reference for list-diffing
// TODO: move this functionality within TransportContainer
h('div', { key: customRendering.id }, [
h(TransportContainer, {
key: customRendering.id,
props: {
inPlaceOf: customRendering.containerEl,
reportEl: customRendering.reportNewContainerEl,
elTag: customRendering.elTag,
elClasses: customRendering.elClasses,
elStyle: customRendering.elStyle,
elAttrs: customRendering.elAttrs,
}
}, innerContent)
]));
}
return h('div', {
// when renderId is changed, Vue will trigger a real-DOM async rerender, calling beforeUpdate/updated
attrs: { 'data-fc-render-id': this.renderId }
}, [
// for containing TransportContainer keys
h(OffscreenFragment, customRenderingNodes)
]);
},
mounted() {
const customRenderingStore = new internal.CustomRenderingStore();
getSecret(this).handleCustomRendering = customRenderingStore.handle.bind(customRenderingStore);
const calendarOptions = this.buildOptions(this.options);
const calendar = new core.Calendar(this.$el, calendarOptions);
getSecret(this).calendar = calendar;
calendar.render();
customRenderingStore.subscribe((customRenderingMap) => {
this.customRenderingMap = customRenderingMap; // likely same reference, so won't rerender
this.renderId++; // force rerender
getSecret(this).needCustomRenderingResize = true;
});
},
beforeUpdate() {
this.getApi().resumeRendering(); // the watcher handlers paused it
},
updated() {
if (getSecret(this).needCustomRenderingResize) {
getSecret(this).needCustomRenderingResize = false;
this.getApi().updateSize();
}
},
beforeDestroy() {
this.getApi().destroy();
},
watch: buildWatchers()
});
// storing internal state:
// https://github.com/vuejs/vue/issues/1988#issuecomment-163013818
function getSecret(inst) {
return inst;
}
function buildWatchers() {
let watchers = {
// watches changes of ALL options and their nested objects,
// but this is only a means to be notified of top-level non-complex options changes.
options: {
deep: true,
handler(options) {
let calendar = this.getApi();
calendar.pauseRendering();
let calendarOptions = this.buildOptions(options);
calendar.resetOptions(calendarOptions);
this.renderId++; // will queue a rerender
}
}
};
for (let complexOptionName in OPTION_IS_COMPLEX) {
// handlers called when nested objects change
watchers[`options.${complexOptionName}`] = {
deep: true,
handler(val) {
// unfortunately the handler is called with undefined if new props were set, but the complex one wasn't ever set
if (val !== undefined) {
let calendar = this.getApi();
calendar.pauseRendering();
calendar.resetOptions({
[complexOptionName]: val
}, [complexOptionName]);
this.renderId++; // will queue a rerender
}
}
};
}
return watchers;
}
// General Utils
function kebabToCamelKeys(map) {
const newMap = {};
for (const key in map) {
newMap[kebabToCamel(key)] = map[key];
}
return newMap;
}
function kebabToCamel(s) {
return s
.split('-')
.map((word, index) => index ? capitalize(word) : word)
.join('');
}
function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
/*
Registers the component globally if appropriate.
This modules exposes the component AND an install function.
Derived from:
https://vuejs.org/v2/cookbook/packaging-sfc-for-npm.html
*/
let installed = false;
// declare install function executed by Vue.use()
function install(Vue) {
if (!installed) {
installed = true;
Vue.component('FullCalendar', FullCalendar);
}
}
// detect a globally availble version of Vue (eg. in browser via <script> tag)
let GlobalVue;
if (typeof globalThis !== 'undefined') {
GlobalVue = globalThis.Vue;
}
else {
GlobalVue = window.Vue;
}
// auto-install if possible
if (GlobalVue) {
GlobalVue.use({
install
});
}
exports["default"] = FullCalendar;
exports.install = install;
Object.defineProperty(exports, '__esModule', { value: true });
return exports;
})({}, Vue, FullCalendar, FullCalendar.Internal);