Allows dynamic drag clone replacement

Introduces a polymorphic `createClone` method on base event elements to customize clone generation.
Adds a `replaceClone` delegate to drag event payloads, enabling subscribers to dynamically swap the active dragged clone.
This supports scenarios like converting a standard event clone to an all-day event clone when dragging to the all-day header.
This commit is contained in:
Janus C. H. Knudsen 2025-10-04 23:10:09 +02:00
parent 125cd678a3
commit 5fae433afb
5 changed files with 641 additions and 31 deletions

View file

@ -318,13 +318,20 @@ export class AllDayManager {
let allDayContainer = this.getAllDayContainer();
if (!allDayContainer) return;
// Create SwpAllDayEventElement from CalendarEvent
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
// Apply grid positioning
allDayElement.style.gridRow = '1';
allDayElement.style.gridColumn = payload.targetColumn.index.toString();
// Remove old swp-event clone
payload.draggedClone.remove();
// Call delegate to update DragDropManager's draggedClone reference
payload.replaceClone(allDayElement);
// Append to container
allDayContainer.appendChild(allDayElement);
ColumnDetectionUtils.updateColumnBoundsCache();
@ -372,9 +379,9 @@ export class AllDayManager {
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
const getEventDurationDays = (start: string|undefined, end: string|undefined): number => {
if(!start || !end)
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
if (!start || !end)
throw new Error('Undefined start or end - date');
const startDate = new Date(start);
@ -396,7 +403,6 @@ export class AllDayManager {
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
dragEndEvent.originalElement.dataset.eventId += '_';
// 3. Create temporary array with existing events + the dropped event
let eventId = dragEndEvent.draggedClone.dataset.eventId;
let eventDate = dragEndEvent.finalPosition.column?.date;
let eventType = dragEndEvent.draggedClone.dataset.type;
@ -404,21 +410,16 @@ export class AllDayManager {
if (eventDate == null || eventId == null || eventType == null)
return;
// Calculate original event duration
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
// Get original dates to preserve time
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!);
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!);
// Create new start date with the new day but preserve original time
const newStartDate = new Date(eventDate);
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
// Create new end date with the new day + duration, preserving original end time
const newEndDate = new Date(eventDate);
newEndDate.setDate(newEndDate.getDate() + durationDays);
@ -464,6 +465,8 @@ export class AllDayManager {
element.style.gridRow = layout.row.toString();
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
element.classList.remove('max-event-overflow-hide');
element.classList.remove('max-event-overflow-show');
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS)
if (!this.isExpanded)
@ -486,11 +489,8 @@ export class AllDayManager {
dragEndEvent.draggedClone.style.cursor = '';
dragEndEvent.draggedClone.style.opacity = '';
// 7. Restore original element opacity
//dragEndEvent.originalElement.remove(); //TODO: this should be an event that only fade and remove if confirmed dragdrop
this.fadeOutAndRemove(dragEndEvent.originalElement);
// 8. Check if height adjustment is needed
this.checkAndAnimateAllDayHeight();
}
@ -505,7 +505,7 @@ export class AllDayManager {
let chevron = headerSpacer.querySelector('.allday-chevron') as HTMLElement;
if (show && !chevron) {
// Create chevron button
chevron = document.createElement('button');
chevron.className = 'allday-chevron collapsed';
chevron.innerHTML = `
@ -515,13 +515,16 @@ export class AllDayManager {
`;
chevron.onclick = () => this.toggleExpanded();
headerSpacer.appendChild(chevron);
} else if (!show && chevron) {
// Remove chevron button
chevron.remove();
} else if (chevron) {
// Update chevron state
chevron.classList.toggle('collapsed', !this.isExpanded);
chevron.classList.toggle('expanded', this.isExpanded);
}
}
@ -532,12 +535,15 @@ export class AllDayManager {
this.isExpanded = !this.isExpanded;
this.checkAndAnimateAllDayHeight();
let elements = document.querySelectorAll('swp-allday-container swp-event.max-event-overflow-hide, swp-allday-container swp-event.max-event-overflow-show');
const elements = document.querySelectorAll('swp-allday-container swp-allday-event.max-event-overflow-hide, swp-allday-container swp-allday-event.max-event-overflow-show');
elements.forEach((element) => {
if (element.classList.contains('max-event-overflow-hide')) {
if (this.isExpanded) {
// ALTID vis når expanded=true
element.classList.remove('max-event-overflow-hide');
element.classList.add('max-event-overflow-show');
} else if (element.classList.contains('max-event-overflow-show')) {
} else {
// ALTID skjul når expanded=false
element.classList.remove('max-event-overflow-show');
element.classList.add('max-event-overflow-hide');
}
@ -582,7 +588,7 @@ export class AllDayManager {
existingIndicator.innerHTML = `<span>+${overflowCount + 1} more</span>`;
} else {
// Create new overflow indicator element
let overflowElement = document.createElement('swp-event');
let overflowElement = document.createElement('swp-allday-event');
overflowElement.className = 'max-event-indicator';
overflowElement.setAttribute('data-column', columnBounds.index.toString());
overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString();

View file

@ -7,7 +7,7 @@ import { IEventBus } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
import { PositionUtils } from '../utils/PositionUtils';
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { SwpEventElement } from '../elements/SwpEventElement';
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
import {
DragStartEventPayload,
DragMoveEventPayload,
@ -192,9 +192,9 @@ export class DragDropManager {
// Detect current column
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
// Cast to SwpEventElement and create clone
const originalSwpEvent = this.draggedElement as SwpEventElement;
this.draggedClone = originalSwpEvent.createClone();
// Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement)
const originalElement = this.draggedElement as BaseSwpEventElement;
this.draggedClone = originalElement.createClone();
const dragStartPayload: DragStartEventPayload = {
draggedElement: this.draggedElement,
@ -499,15 +499,17 @@ export class DragDropManager {
// Extract CalendarEvent from the dragged clone
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
targetColumn: targetColumn,
mousePosition: position,
originalElement: this.draggedElement,
draggedClone: this.draggedClone,
calendarEvent: calendarEvent
calendarEvent: calendarEvent,
// Delegate pattern - allows AllDayManager to replace the clone
replaceClone: (newClone: HTMLElement) => {
this.draggedClone = newClone;
}
};
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
}