fullcalendar
Version:
FullCalendar Vanilla JS package for rendering a calendar
238 lines (232 loc) • 10.3 kB
JavaScript
import { E as Emitter } from './56f74c4a.js';
import { ac as compileEventUis, n as mapHash, u as EventImpl, ad as buildDateSpanApiWithContext, X as expandRecurring, U as filterEventStoreDefs } from './2add5508.js';
import { rangesIntersect, rangeContainsRange } from '@full-ui/headless-calendar';
import { e as excludeInstances } from './d5a70381.js';
/*
An abstraction for a dragging interaction originating on an event.
Does higher-level things than PointerDragger, such as possibly:
- a "mirror" that moves with the pointer
- a minimum number of pixels or other criteria for a true drag to begin
subclasses must emit:
- pointerdown
- dragstart
- dragmove
- pointerup
- dragend
*/
class ElementDragging {
constructor(el, selector) {
this.emitter = new Emitter();
}
destroy() {
}
setMirrorIsVisible(bool) {
// optional if subclass doesn't want to support a mirror
}
setMirrorNeedsRevert(bool) {
// optional if subclass doesn't want to support a mirror
}
setAutoScrollEnabled(bool) {
// optional
}
}
// TODO: get rid of this in favor of options system,
// tho it's really easy to access this globally rather than pass thru options.
const config = {};
// high-level segmenting-aware tester functions
// ------------------------------------------------------------------------------------------------------------------------
function isInteractionValid(interaction, dateProfile, context) {
let { instances } = interaction.mutatedEvents;
for (let instanceId in instances) {
if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
return false;
}
}
return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions
}
function isDateSelectionValid(dateSelection, dateProfile, context) {
if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
return false;
}
return isNewPropsValid({ dateSelection }, context);
}
function isNewPropsValid(newProps, context) {
let calendarState = context.getCurrentData();
let props = {
businessHours: calendarState.businessHours,
dateSelection: '',
eventStore: calendarState.eventStore,
eventUiBases: calendarState.eventUiBases,
eventSelection: '',
eventDrag: null,
eventResize: null,
...newProps,
};
return (context.pluginHooks.isPropsValid || isPropsValid)(props, context);
}
function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) {
if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
return false;
}
if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
return false;
}
return true;
}
// Moving Event Validation
// ------------------------------------------------------------------------------------------------------------------------
function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) {
let currentState = context.getCurrentData();
let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions
let subjectEventStore = interaction.mutatedEvents;
let subjectDefs = subjectEventStore.defs;
let subjectInstances = subjectEventStore.instances;
let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ?
state.eventUiBases :
{ '': currentState.selectionConfig });
if (filterConfig) {
subjectConfigs = mapHash(subjectConfigs, filterConfig);
}
// exclude the subject events. TODO: exclude defs too?
let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances);
let otherDefs = otherEventStore.defs;
let otherInstances = otherEventStore.instances;
let otherConfigs = compileEventUis(otherDefs, state.eventUiBases);
for (let subjectInstanceId in subjectInstances) {
let subjectInstance = subjectInstances[subjectInstanceId];
let subjectRange = subjectInstance.range;
let subjectConfig = subjectConfigs[subjectInstance.defId];
let subjectDef = subjectDefs[subjectInstance.defId];
// constraint
if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
return false;
}
// overlap
let { eventOverlap } = context.options;
let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null;
for (let otherInstanceId in otherInstances) {
let otherInstance = otherInstances[otherInstanceId];
// intersect! evaluate
if (rangesIntersect(subjectRange, otherInstance.range)) {
let otherOverlap = otherConfigs[otherInstance.defId].overlap;
// consider the other event's overlap. only do this if the subject event is a "real" event
if (otherOverlap === false && interaction.isEvent) {
return false;
}
if (subjectConfig.overlap === false) {
return false;
}
if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event
new EventImpl(context, subjectDef, subjectInstance))) {
return false;
}
}
}
// allow (a function)
let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state
for (let subjectAllow of subjectConfig.allows) {
let subjectDateSpan = {
...dateSpanMeta,
range: subjectInstance.range,
allDay: subjectDef.allDay,
};
let origDef = calendarEventStore.defs[subjectDef.defId];
let origInstance = calendarEventStore.instances[subjectInstanceId];
let eventApi;
if (origDef) { // was previously in the calendar
eventApi = new EventImpl(context, origDef, origInstance);
}
else { // was an external event
eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates
}
if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) {
return false;
}
}
}
return true;
}
// Date Selection Validation
// ------------------------------------------------------------------------------------------------------------------------
function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) {
let relevantEventStore = state.eventStore;
let relevantDefs = relevantEventStore.defs;
let relevantInstances = relevantEventStore.instances;
let selection = state.dateSelection;
let selectionRange = selection.range;
let { selectionConfig } = context.getCurrentData();
if (filterConfig) {
selectionConfig = filterConfig(selectionConfig);
}
// constraint
if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
return false;
}
// overlap
let { selectOverlap } = context.options;
let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null;
for (let relevantInstanceId in relevantInstances) {
let relevantInstance = relevantInstances[relevantInstanceId];
// intersect! evaluate
if (rangesIntersect(selectionRange, relevantInstance.range)) {
if (selectionConfig.overlap === false) {
return false;
}
if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) {
return false;
}
}
}
// allow (a function)
for (let selectionAllow of selectionConfig.allows) {
let fullDateSpan = { ...dateSpanMeta, ...selection };
if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) {
return false;
}
}
return true;
}
// Constraint Utils
// ------------------------------------------------------------------------------------------------------------------------
function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) {
for (let constraint of constraints) {
if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) {
return false;
}
}
return true;
}
function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours
otherEventStore, // for if constraint is an even group ID
businessHoursUnexpanded, // for if constraint is 'businessHours'
context) {
if (constraint === 'businessHours') {
return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context));
}
if (typeof constraint === 'string') { // an group ID
return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint));
}
if (typeof constraint === 'object' && constraint) { // non-null object
return eventStoreToRanges(expandRecurring(constraint, subjectRange, context));
}
return []; // if it's false
}
// TODO: move to event-store file?
function eventStoreToRanges(eventStore) {
let { instances } = eventStore;
let ranges = [];
for (let instanceId in instances) {
ranges.push(instances[instanceId].range);
}
return ranges;
}
// TODO: move to geom file?
function anyRangesContainRange(outerRanges, innerRange) {
for (let outerRange of outerRanges) {
if (rangeContainsRange(outerRange, innerRange)) {
return true;
}
}
return false;
}
export { ElementDragging as E, isInteractionValid as a, isPropsValid as b, config as c, isDateSelectionValid as i };