vue-cal
Version:
A Vue JS full calendar, no dependency, no BS. :metal:
260 lines (259 loc) • 11 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
/**
* vue-cal v4.10.2
* (c) 2025 Antoni Andre <antoniandre.web@gmail.com>
* @license MIT
*/
const holdOverTimeout = 800;
let changeViewTimeout = null;
let pressPrevOrNextInterval = null;
let viewBeforeDrag = { id: null, date: null };
let viewChanged = false;
let cancelViewChange = true;
let dragOverCell = { el: null, cell: null, timeout: null };
const dragging = {
_eid: null,
fromVueCal: null,
toVueCal: null
};
const DragAndDrop = class {
constructor(vuecal) {
__publicField(this, "_vuecal");
this._vuecal = vuecal;
}
/**
* When click and drag an event the cursor can be anywhere in the event,
* when dropping the event, we need to subtract the cursor position in the event.
*
* @param {Object} e The associated DOM event.
*/
_getEventStart(e) {
const { timeStep, timeCellHeight, timeFrom, utils } = this._vuecal;
let { y } = utils.cell.getPosition(e);
y -= e.dataTransfer.getData("cursor-grab-at") * 1;
return Math.round(y * timeStep / parseInt(timeCellHeight) + timeFrom);
}
/**
* On drop, update the event start and end date directly into the event.
*
* @param {Object} e The associated DOM event.
* @param {Object} event The event being dragged.
* @param {Object} transferData The transfer data from the HTML5 dragging event.
* @param {Date} cellDate The hovered cell starting date.
*/
_updateEventStartEnd(e, event, transferData, cellDate) {
const eventDuration = transferData.duration * 1 || event.endTimeMinutes - event.startTimeMinutes;
let startTimeMinutes = Math.max(this._getEventStart(e), 0);
if (this._vuecal.snapToTime) {
const plusHalfSnapTime = startTimeMinutes + this._vuecal.snapToTime / 2;
startTimeMinutes = plusHalfSnapTime - plusHalfSnapTime % this._vuecal.snapToTime;
}
event.startTimeMinutes = startTimeMinutes;
event.start = new Date(new Date(cellDate).setMinutes(startTimeMinutes));
event.endTimeMinutes = Math.min(startTimeMinutes + eventDuration, 24 * 60);
event.end = new Date(new Date(cellDate).setMinutes(event.endTimeMinutes));
}
/**
* On event drag start, only possible if editableEvent is true.
* /!\ This is using the native HTML5 drag & drop, not supported on touch devices.
*
* @param {Object} e The associated DOM event.
* @param {Object} event The event being dragged.
*/
eventDragStart(e, event) {
if (e.target.nodeType === 3) return e.preventDefault();
e.dataTransfer.dropEffect = "move";
e.dataTransfer.setData("event", JSON.stringify(event));
e.dataTransfer.setData("cursor-grab-at", e.offsetY);
const { clickHoldAnEvent } = this._vuecal.domEvents;
setTimeout(() => {
clickHoldAnEvent._eid = null;
clearTimeout(clickHoldAnEvent.timeoutId);
event.deleting = false;
}, 0);
this._vuecal.domEvents.dragAnEvent._eid = event._eid;
dragging._eid = event._eid;
dragging.fromVueCal = this._vuecal._.uid;
event.dragging = true;
setTimeout(() => event.draggingStatic = true, 0);
viewChanged = false;
viewBeforeDrag = { id: this._vuecal.view.id, date: this._vuecal.view.startDate };
cancelViewChange = true;
}
/**
* On event drag end, when releasing the event.
*
* @param {Object} event The event being dragged.
*/
eventDragEnd(event) {
this._vuecal.domEvents.dragAnEvent._eid = null;
dragging._eid = null;
event.dragging = false;
event.draggingStatic = false;
const { fromVueCal, toVueCal } = dragging;
if (toVueCal && fromVueCal !== toVueCal) this._vuecal.utils.event.deleteAnEvent(event);
dragging.fromVueCal = null;
dragging.toVueCal = null;
if (viewChanged && cancelViewChange && viewBeforeDrag.id) this._vuecal.switchView(viewBeforeDrag.id, viewBeforeDrag.date, true);
}
/**
* On cell/split enter with a dragging event.
* Highlight the cell, and if on `years`, `year`, `month` view,
* set a timer to go deeper on drag hold over this cell.
*
* @param {Object} e The associated DOM event.
* @param {Object} cell The cell component's $data.
* @param {Date} cellDate The hovered cell starting date.
*/
cellDragEnter(e, cell, cellDate) {
const target = e.currentTarget;
if (e.currentTarget.contains(e.relatedTarget)) return;
if (target === dragOverCell.el || !target.className.includes("vuecal__cell-content")) return false;
if (dragOverCell.el) dragOverCell.cell.highlighted = false;
dragOverCell = { el: target, cell, timeout: clearTimeout(dragOverCell.timeout) };
cell.highlighted = true;
if (["years", "year", "month"].includes(this._vuecal.view.id)) {
dragOverCell.timeout = setTimeout(() => this._vuecal.switchToNarrowerView(cellDate), 2e3);
}
}
/**
* On cell/split drag over, highlight the cell being hovered,
* Useful when starting to drag event on the same cell/split it's in.
* Warning: This is fired repeatedly as long as you stay over this cell/split.
*
* @param {Object} e The associated DOM event.
* @param {Object} cell The cell component's $data.
* @param {Date} cellDate The hovered cell starting date.
* @param {Number|String} split The optional split being hovered if any.
*/
cellDragOver(e, cell, cellDate, split) {
e.preventDefault();
cell.highlighted = true;
if (split || split === 0) cell.highlightedSplit = split;
}
/**
* When event drag leaves a cell/split.
* Remove the cell/split highlighted state.
* Warning: cell dragleave event happens AFTER another cell dragenter!
*
* @param {Object} e The associated DOM event.
* @param {Object} cell The cell component's $data.
*/
cellDragLeave(e, cell) {
e.preventDefault();
if (e.currentTarget.contains(e.relatedTarget)) return;
cell.highlightedSplit = false;
if (dragOverCell.cell === cell) {
clearTimeout(dragOverCell.timeout);
dragOverCell = { el: null, cell: null, timeout: null };
cell.highlighted = false;
}
}
/**
* On successful event drop into a cell/split.
* Change the event start and end time and remove the event dragging state
* and cell/split highlighted state.
*
* @param {Object} e The associated DOM event.
* @param {Object} cell The cell component's $data.
* @param {Date} cellDate The hovered cell starting date.
* @param {Number|String} split The optional split being dropped into, if any.
*/
cellDragDrop(e, cell, cellDate, split) {
e.preventDefault();
clearTimeout(dragOverCell.timeout);
dragOverCell = { el: null, cell: null, timeout: null };
const transferData = JSON.parse(e.dataTransfer.getData("event") || "{}");
let event, addToView;
if (dragging.fromVueCal !== this._vuecal._.uid) {
const { _eid, start, end, duration, ...cleanTransferData } = transferData;
event = this._vuecal.utils.event.createAnEvent(cellDate, duration, { ...cleanTransferData, split });
} else {
event = this._vuecal.view.events.find((evt) => evt._eid === dragging._eid);
if (!event) {
event = this._vuecal.mutableEvents.find((evt) => evt._eid === dragging._eid);
addToView = !!event;
}
if (!event) {
const duration = transferData.endTimeMinutes - transferData.startTimeMinutes;
const { start, end, ...cleanTransferData } = transferData;
event = this._vuecal.utils.event.createAnEvent(cellDate, duration, { ...cleanTransferData, split });
}
}
const { start: oldDate, split: oldSplit } = event;
this._updateEventStartEnd(e, event, transferData, cellDate);
if (addToView) this._vuecal.addEventsToView([event]);
event.dragging = false;
if (split || split === 0) event.split = split;
cell.highlighted = false;
cell.highlightedSplit = null;
cancelViewChange = false;
dragging.toVueCal = this._vuecal._.uid;
const params = {
event: this._vuecal.cleanupEvent(event),
oldDate,
newDate: event.start,
...(split || split === 0) && { oldSplit, newSplit: split },
originalEvent: this._vuecal.cleanupEvent(transferData),
external: !dragging.fromVueCal
// If external event, not coming from any Vue Cal.
};
this._vuecal.$emit("event-drop", params);
this._vuecal.$emit("event-change", { event: params.event, originalEvent: params.originalEvent });
setTimeout(() => {
if (dragging._eid) this.eventDragEnd(event);
}, 300);
}
/**
* On drag enter on a view button or on today, prev & next buttons.
* Sets a highlighted state on the hovered button, and go to requested view.
*
* @param {Object} e The associated DOM event.
* @param {String} id The id of the header element being hovered. One of:
* previous, next, today, years, year, month, week, day.
* @param {Object} headerData The header component's $data.
*/
viewSelectorDragEnter(e, id, headerData) {
if (e.currentTarget.contains(e.relatedTarget)) return;
headerData.highlightedControl = id;
clearTimeout(changeViewTimeout);
changeViewTimeout = setTimeout(() => {
if (["previous", "next"].includes(id)) {
this._vuecal[id]();
clearInterval(pressPrevOrNextInterval);
pressPrevOrNextInterval = setInterval(this._vuecal[id], holdOverTimeout);
} else if (id === "today") {
clearInterval(pressPrevOrNextInterval);
let viewId;
if (this._vuecal.view.id.includes("year")) {
viewId = this._vuecal.enabledViews.filter((view) => !view.includes("year"))[0];
}
this._vuecal.switchView(viewId || this._vuecal.view.id, new Date((/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0)), true);
} else this._vuecal.switchView(id, null, true);
viewChanged = true;
}, holdOverTimeout);
}
/**
* On drag leave on a view button or on today, prev & next buttons.
* Removes the highlighted state on the hovered button, and cancel the timer to
* go to the requested view.
*
* @param {Object} e The associated DOM event.
* @param {String} id The id of the header element being hovered. One of:
* previous, next, today, years, year, month, week, day.
* @param {Object} headerData The header component's $data.
*/
viewSelectorDragLeave(e, id, headerData) {
if (e.currentTarget.contains(e.relatedTarget)) return;
if (headerData.highlightedControl === id) {
headerData.highlightedControl = null;
if (changeViewTimeout) changeViewTimeout = clearTimeout(changeViewTimeout);
if (pressPrevOrNextInterval) pressPrevOrNextInterval = clearInterval(pressPrevOrNextInterval);
}
}
};
export {
DragAndDrop
};