@event-calendar/core
Version:
Full-sized drag & drop event calendar with resource & timeline views
171 lines (156 loc) • 6.2 kB
JavaScript
import {get, writable} from 'svelte/store';
import {tick} from 'svelte';
import {createOptions, createParsers} from './options.js';
import {activeRange, currentRange, dayGrid, events, now, today, view as view2, viewDates, viewTitle, filteredEvents} from './stores.js';
import {identity, intl, intlRange, isFunction, keys, toViewWithLocalDates} from '#lib';
export default class {
constructor(plugins, input) {
plugins = plugins || [];
// Create options
let options = createOptions(plugins);
let parsers = createParsers(plugins);
// Parse options
options = parseOpts(options, parsers);
input = parseOpts(input, parsers);
// Create stores for options
for (let [option, value] of Object.entries(options)) {
this[option] = writable(value);
}
// Private stores
this._queue = writable(new Map()); // debounce queue (beforeUpdate)
this._tasks = new Map(); // timeout IDs for tasks
this._auxiliary = writable([]); // auxiliary components
this._dayGrid = dayGrid(this);
this._currentRange = currentRange(this);
this._activeRange = activeRange(this);
this._fetchedRange = writable({start: undefined, end: undefined});
this._events = events(this);
this._now = now();
this._today = today(this);
this._intlEventTime = intlRange(this.locale, this.eventTimeFormat);
this._intlSlotLabel = intl(this.locale, this.slotLabelFormat);
this._intlDayHeader = intl(this.locale, this.dayHeaderFormat);
this._intlDayHeaderAL = intl(this.locale, this.dayHeaderAriaLabelFormat);
this._intlTitle = intlRange(this.locale, this.titleFormat);
this._bodyEl = writable(undefined);
this._scrollable = writable(false);
this._recheckScrollable = writable(false);
this._viewTitle = viewTitle(this);
this._viewDates = viewDates(this);
this._view = view2(this);
this._viewComponent = writable(undefined);
this._filteredEvents = filteredEvents(this);
// Interaction
this._interaction = writable({});
this._iEvents = writable([null, null]); // interaction events: [drag/resize, pointer]
this._iClasses = writable(identity); // interaction event css classes
this._iClass = writable(undefined); // interaction css class for entire calendar
// Set & Get
this._set = (key, value) => {
if (validKey(key, this)) {
if (parsers[key]) {
value = parsers[key](value);
}
this[key].set(value);
}
};
this._get = key => validKey(key, this) ? get(this[key]) : undefined;
// Let plugins create their private stores
for (let plugin of plugins) {
plugin.createStores?.(this);
}
if (input.view) {
// Set initial view based on input
this.view.set(input.view);
}
// Set options for each view
let views = new Set([...keys(options.views), ...keys(input.views ?? {})]);
for (let view of views) {
let defOpts = mergeOpts(options, options.views[view] ?? {});
let opts = mergeOpts(defOpts, input, input.views?.[view] ?? {});
let component = opts.component;
// Make sure we deal with valid opts from now on
filterOpts(opts, this);
// Process options
for (let key of keys(opts)) {
let {set, _set = set, ...rest} = this[key];
this[key] = {
// Set value in all views
set: ['buttonText', 'theme'].includes(key)
? value => {
if (isFunction(value)) {
let result = value(defOpts[key]);
opts[key] = result;
set(set === _set ? result : value);
} else {
opts[key] = value;
set(value);
}
}
: value => {
opts[key] = value;
set(value);
},
_set,
...rest
};
}
// When view changes...
this.view.subscribe(newView => {
if (newView === view) {
// switch view component
this._viewComponent.set(component);
if (isFunction(opts.viewDidMount)) {
tick().then(() => opts.viewDidMount({
view: toViewWithLocalDates(get(this._view))
}));
}
// update store values
for (let key of keys(opts)) {
this[key]._set(opts[key]);
}
}
});
}
}
}
function parseOpts(opts, parsers) {
let result = {...opts};
for (let key of keys(parsers)) {
if (key in result) {
result[key] = parsers[key](result[key]);
}
}
if (opts.views) {
result.views = {};
for (let view of keys(opts.views)) {
result.views[view] = parseOpts(opts.views[view], parsers);
}
}
return result;
}
function mergeOpts(...args) {
let result = {};
for (let opts of args) {
let override = {};
for (let key of ['buttonText', 'theme']) {
if (isFunction(opts[key])) {
override[key] = opts[key](result[key]);
}
}
result = {
...result,
...opts,
...override
};
}
return result;
}
function filterOpts(opts, state) {
keys(opts)
.filter(key => !validKey(key, state) || key === 'view')
.forEach(key => delete opts[key]);
}
function validKey(key, state) {
return state.hasOwnProperty(key) && key[0] !== '_';
}