diff --git a/src/elements/SwpEventElement.ts b/src/elements/SwpEventElement.ts
index e34a92e..772d9b9 100644
--- a/src/elements/SwpEventElement.ts
+++ b/src/elements/SwpEventElement.ts
@@ -101,6 +101,83 @@ export class SwpEventElement extends BaseEventElement {
return new SwpEventElement(event);
}
+ /**
+ * Create a clone of this SwpEventElement with "clone-" prefix
+ */
+ public createClone(): SwpEventElement {
+ // Clone the underlying DOM element
+ const clonedElement = this.element.cloneNode(true) as HTMLElement;
+
+ // Create new SwpEventElement instance from the cloned DOM
+ const clonedSwpEvent = SwpEventElement.fromExistingElement(clonedElement);
+
+ // Apply "clone-" prefix to ID
+ clonedSwpEvent.updateEventId(`clone-${this.event.id}`);
+
+ // Cache original duration for drag operations
+ const originalDuration = this.getOriginalEventDuration();
+ clonedSwpEvent.element.dataset.originalDuration = originalDuration.toString();
+
+ // Set height from original element
+ clonedSwpEvent.element.style.height = this.element.style.height || `${this.element.getBoundingClientRect().height}px`;
+
+ return clonedSwpEvent;
+ }
+
+ /**
+ * Factory method to create SwpEventElement from existing DOM element
+ */
+ public static fromExistingElement(element: HTMLElement): SwpEventElement {
+ // Extract CalendarEvent data from DOM element
+ const event = this.extractCalendarEventFromElement(element);
+
+ // Create new instance but replace the created element with the existing one
+ const swpEvent = new SwpEventElement(event);
+ swpEvent.element = element;
+
+ return swpEvent;
+ }
+
+ /**
+ * Update the event ID in both the CalendarEvent and DOM element
+ */
+ private updateEventId(newId: string): void {
+ this.event.id = newId;
+ this.element.dataset.eventId = newId;
+ }
+
+ /**
+ * Extract original event duration from DOM element
+ */
+ private getOriginalEventDuration(): number {
+ const timeElement = this.element.querySelector('swp-event-time');
+ if (timeElement) {
+ const duration = timeElement.getAttribute('data-duration');
+ if (duration) {
+ return parseInt(duration);
+ }
+ }
+ return 60; // Fallback
+ }
+
+ /**
+ * Extract CalendarEvent from DOM element
+ */
+ private static extractCalendarEventFromElement(element: HTMLElement): CalendarEvent {
+ return {
+ id: element.dataset.eventId || '',
+ title: element.dataset.title || '',
+ start: new Date(element.dataset.start || ''),
+ end: new Date(element.dataset.end || ''),
+ type: element.dataset.type || 'work',
+ allDay: false,
+ syncStatus: 'synced',
+ metadata: {
+ duration: element.dataset.duration
+ }
+ };
+ }
+
/**
* Factory method to convert an all-day HTML element to a timed SwpEventElement
*/
diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts
index 6c632f6..e4d0d78 100644
--- a/src/managers/AllDayManager.ts
+++ b/src/managers/AllDayManager.ts
@@ -74,14 +74,26 @@ export class AllDayManager {
});
eventBus.on('drag:end', (event) => {
- const { eventId, finalPosition } = (event as CustomEvent).detail;
+
+ const { eventId, finalColumn, finalY, dropTarget } = (event as CustomEvent).detail;
+
+ if (dropTarget != 'SWP-DAY-HEADER')//we are not inside the swp-day-header, so just ignore.
+ return;
+
+ console.log('🎬 AllDayManager: Received drag:end', {
+ eventId: eventId,
+ finalColumn: finalColumn,
+ finalY: finalY
+ });
// Check if this was an all-day event
const originalElement = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`);
const dragClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${eventId}"]`);
+
+
console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId });
- this.handleDragEnd(originalElement as HTMLElement, dragClone as HTMLElement, finalPosition);
+ this.handleDragEnd(originalElement as HTMLElement, dragClone as HTMLElement, finalColumn);
});
}
diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts
index c9f6d62..da059b6 100644
--- a/src/managers/DragDropManager.ts
+++ b/src/managers/DragDropManager.ts
@@ -172,8 +172,11 @@ export class DragDropManager {
const mousePosition = { x: this.lastMousePosition.x, y: this.lastMousePosition.y };
const column = this.getColumnDateFromX(mousePosition.x);
+ // Find the actual dragged element
+ const draggedElement = document.querySelector(`[data-event-id="${this.draggedEventId}"]`) as HTMLElement;
+
this.eventBus.emit('drag:convert-to-time_event', {
- draggedEventId: this.draggedEventId,
+ draggedElement: draggedElement,
mousePosition: mousePosition,
column: column
});
@@ -319,10 +322,14 @@ export class DragDropManager {
// Use consolidated position calculation
const positionData = this.calculateDragPosition(finalPosition);
+ // Detect drop target (swp-day-column or swp-day-header)
+ const dropTarget = this.detectDropTarget(finalPosition);
+
console.log('🎯 DragDropManager: Emitting drag:end', {
eventId: eventId,
finalColumn: positionData.column,
finalY: positionData.snappedY,
+ dropTarget: dropTarget,
isDragStarted: isDragStarted
});
@@ -330,7 +337,8 @@ export class DragDropManager {
eventId: eventId,
finalPosition,
finalColumn: positionData.column,
- finalY: positionData.snappedY
+ finalY: positionData.snappedY,
+ target: dropTarget
});
} else {
// This was just a click - emit click event instead
@@ -411,9 +419,6 @@ export class DragDropManager {
// Sorter efter x-position (fra venstre til højre)
this.columnBoundsCache.sort((a, b) => a.left - b.left);
- console.log('📏 DragDropManager: Updated column bounds cache', {
- columns: this.columnBoundsCache.length
- });
}
/**
@@ -592,6 +597,28 @@ export class DragDropManager {
return allDayElement !== null;
}
+ /**
+ * Detect drop target - whether dropped in swp-day-column or swp-day-header
+ */
+ private detectDropTarget(position: Position): 'swp-day-column' | 'swp-day-header' | null {
+ const elementAtPosition = document.elementFromPoint(position.x, position.y);
+ if (!elementAtPosition) return null;
+
+ // Traverse up the DOM tree to find the target container
+ let currentElement = elementAtPosition as HTMLElement;
+ while (currentElement && currentElement !== document.body) {
+ if (currentElement.tagName === 'SWP-DAY-HEADER') {
+ return 'swp-day-header';
+ }
+ if (currentElement.tagName === 'SWP-DAY-COLUMN') {
+ return 'swp-day-column';
+ }
+ currentElement = currentElement.parentElement as HTMLElement;
+ }
+
+ return null;
+ }
+
/**
* Clean up all resources and event listeners
*/
diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts
index f7a7815..427b66a 100644
--- a/src/managers/HeaderManager.ts
+++ b/src/managers/HeaderManager.ts
@@ -44,22 +44,15 @@ export class HeaderManager {
* Setup header drag event listeners - REFACTORED to use mouseenter
*/
public setupHeaderDragListeners(): void {
- const calendarHeader = this.getCalendarHeader();
- if (!calendarHeader) return;
+ if (!this.getCalendarHeader()) return;
console.log('🎯 HeaderManager: Setting up drag listeners with mouseenter');
- // Track last processed date to avoid duplicates
- let lastProcessedDate: string | null = null;
- let lastProcessedTime = 0;
// Use mouseenter instead of mouseover to avoid continuous firing
this.headerEventListener = (event: Event) => {
- // OPTIMIZED: Check for active drag operation FIRST before doing any other work
- const isDragActive = document.querySelector('.dragging') !== null;
- if (!isDragActive) {
- // Ingen drag operation, spring resten af funktionen over
+ if (!document.querySelector('.dragging') !== null) {
return;
}
@@ -114,9 +107,8 @@ export class HeaderManager {
});
};
- // Use mouseenter with capture to catch events early
- calendarHeader.addEventListener('mouseenter', this.headerEventListener, true);
- calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener);
+ this.getCalendarHeader()?.addEventListener('mouseenter', this.headerEventListener, true);
+ this.getCalendarHeader()?.addEventListener('mouseleave', this.headerMouseLeaveListener);
console.log('✅ HeaderManager: Event listeners attached (mouseenter + mouseleave)');
}
diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts
index 1184c6f..ba05ba1 100644
--- a/src/renderers/EventRenderer.ts
+++ b/src/renderers/EventRenderer.ts
@@ -16,6 +16,13 @@ import { PositionUtils } from '../utils/PositionUtils';
export interface EventRendererStrategy {
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
clearEvents(container?: HTMLElement): void;
+ handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void;
+ handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: any): void;
+ handleDragAutoScroll?(eventId: string, snappedY: number): void;
+ handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void;
+ handleEventClick?(eventId: string, originalElement: HTMLElement): void;
+ handleColumnChange?(eventId: string, newColumn: string): void;
+ handleNavigationCompleted?(): void;
}
/**
@@ -23,11 +30,11 @@ export interface EventRendererStrategy {
*/
export abstract class BaseEventRenderer implements EventRendererStrategy {
protected dateCalculator: DateCalculator;
-
+
// Drag and drop state
private draggedClone: HTMLElement | null = null;
private originalEvent: HTMLElement | null = null;
-
+
// Resize manager
constructor(dateCalculator?: DateCalculator) {
@@ -70,12 +77,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
const remainingEvents = events.slice(index + 1);
const overlappingEvents = this.overlapDetector.resolveOverlap(currentEvent, remainingEvents);
-
+
if (overlappingEvents.length > 0) {
// Der er overlaps - opret stack links
const result = this.overlapDetector.decorateWithStackLinks(currentEvent, overlappingEvents);
this.renderOverlappingEvents(result, container);
-
+
// Marker alle events i overlap gruppen som processeret
overlappingEvents.forEach(event => processedEvents.add(event.id));
} else {
@@ -90,90 +97,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/**
* Setup listeners for drag events from DragDropManager
+ * NOTE: Event listeners moved to EventRendererManager for better separation of concerns
*/
protected setupDragEventListeners(): void {
- // Handle drag start
- eventBus.on('drag:start', (event) => {
- const { eventId, mouseOffset, column } = (event as CustomEvent).detail;
- // Find element dynamically
- const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
- if (originalElement) {
- this.handleDragStart(originalElement, eventId, mouseOffset, column);
- }
- });
-
- // Handle drag move
- eventBus.on('drag:move', (event) => {
- const { eventId, snappedY, column, mouseOffset } = (event as CustomEvent).detail;
- this.handleDragMove(eventId, snappedY, column, mouseOffset);
- });
-
- // Handle drag auto-scroll (when dragging near edges triggers scroll)
- eventBus.on('drag:auto-scroll', (event) => {
- const { eventId, snappedY } = (event as CustomEvent).detail;
- if (!this.draggedClone) return;
-
- // Update position directly using the calculated snapped position
- this.draggedClone.style.top = snappedY + 'px';
-
- // Update timestamp display
- this.updateCloneTimestamp(this.draggedClone, snappedY);
- });
-
- // Handle drag end
- eventBus.on('drag:end', (event) => {
- const { eventId, finalColumn, finalY } = (event as CustomEvent).detail;
-
- console.log('🎬 EventRenderer: Received drag:end', {
- eventId: eventId,
- finalColumn: finalColumn,
- finalY: finalY
- });
-
- // Find element dynamically - could be swp-event or swp-allday-event
- let originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
- let elementType = 'day-event';
- if (!originalElement) {
- originalElement = document.querySelector(`swp-allday-event[data-event-id="${eventId}"]`) as HTMLElement;
- elementType = 'all-day-event';
- }
-
- console.log('🔍 EventRenderer: Found element', {
- elementType: elementType,
- found: !!originalElement,
- tagName: originalElement?.tagName
- });
-
- if (originalElement) {
- this.handleDragEnd(eventId, originalElement, finalColumn, finalY);
- }
- });
-
- // Handle click (when drag threshold not reached)
- eventBus.on('event:click', (event) => {
- const { eventId } = (event as CustomEvent).detail;
- // Find element dynamically
- let originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
- if (!originalElement) {
- originalElement = document.querySelector(`swp-allday-event[data-event-id="${eventId}"]`) as HTMLElement;
- }
- this.handleEventClick(eventId, originalElement);
- });
-
- // Handle column change
- eventBus.on('drag:column-change', (event) => {
- const { eventId, newColumn } = (event as CustomEvent).detail;
- this.handleColumnChange(eventId, newColumn);
- });
-
-
- // Handle navigation period change (when slide animation completes)
- eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
- // Animate all-day height after navigation completes
- });
+ // All event listeners now handled by EventRendererManager
+ // This method kept for backward compatibility but does nothing
}
-
-
+
+
/**
* Cleanup method for proper resource management
*/
@@ -182,23 +113,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
this.originalEvent = null;
}
- /**
- * Get original event duration from data-duration attribute
- */
- private getOriginalEventDuration(originalEvent: HTMLElement): number {
- // Find the swp-event-time element with data-duration attribute
- const timeElement = originalEvent.querySelector('swp-event-time');
- if (timeElement) {
- const duration = timeElement.getAttribute('data-duration');
- if (duration) {
- const durationMinutes = parseInt(duration);
- return durationMinutes;
- }
- }
-
- // Fallback to 60 minutes if attribute not found
- return 60;
- }
/**
* Apply common drag styling to an element
@@ -207,89 +121,41 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
element.classList.add('dragging');
}
- /**
- * Create event inner structure (swp-event-time and swp-event-title)
- */
- private createEventInnerStructure(event: CalendarEvent): string {
- const timeRange = TimeFormatter.formatTimeRange(event.start, event.end);
- const durationMinutes = (event.end.getTime() - event.start.getTime()) / (1000 * 60);
-
- return `
- ${timeRange}
- ${event.title}
- `;
- }
- /**
- * Apply standard event positioning
- */
- private applyEventPositioning(element: HTMLElement, top: number, height: number): void {
- element.style.position = 'absolute';
- element.style.top = `${top}px`;
- element.style.height = `${height}px`;
- element.style.left = '2px';
- element.style.right = '2px';
- }
-
- /**
- * Create a clone of an event for dragging
- */
- private createEventClone(originalEvent: HTMLElement): HTMLElement {
- const clone = originalEvent.cloneNode(true) as HTMLElement;
-
- // Prefix ID with "clone-"
- const originalId = originalEvent.dataset.eventId;
- if (originalId) {
- clone.dataset.eventId = `clone-${originalId}`;
- }
-
- // Get and cache original duration from data-duration attribute
- const originalDurationMinutes = this.getOriginalEventDuration(originalEvent);
- clone.dataset.originalDuration = originalDurationMinutes.toString();
-
- // Apply common drag styling
- this.applyDragStyling(clone);
-
- // Set height from original event
- clone.style.height = originalEvent.style.height || `${originalEvent.getBoundingClientRect().height}px`;
-
- return clone;
- }
-
/**
* Update clone timestamp based on new position
*/
private updateCloneTimestamp(clone: HTMLElement, snappedY: number): void {
//important as events can pile up, so they will still fire after event has been converted to another rendered type
- if(clone.dataset.allDay == "true") return;
+ if (clone.dataset.allDay == "true") return;
const gridSettings = calendarConfig.getGridSettings();
const hourHeight = gridSettings.hourHeight;
const dayStartHour = gridSettings.dayStartHour;
const snapInterval = gridSettings.snapInterval;
-
+
// Calculate minutes from grid start (not from midnight)
const minutesFromGridStart = (snappedY / hourHeight) * 60;
-
+
// Add dayStartHour offset to get actual time
const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart;
-
+
// Snap to interval
const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval;
-
+
// Use cached original duration (no recalculation)
const cachedDuration = parseInt(clone.dataset.originalDuration || '60');
const endTotalMinutes = snappedStartMinutes + cachedDuration;
-
+
// Update dataset with reference date for performance
const referenceDate = new Date('1970-01-01T00:00:00');
const startDate = new Date(referenceDate);
startDate.setMinutes(startDate.getMinutes() + snappedStartMinutes);
-
+
const endDate = new Date(referenceDate);
endDate.setMinutes(endDate.getMinutes() + endTotalMinutes);
-
+
clone.dataset.start = startDate.toISOString();
clone.dataset.end = endDate.toISOString();
// Update display
@@ -300,18 +166,25 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
timeElement.textContent = `${startTime} - ${endTime}`;
}
}
-
+
/**
* Handle drag start event
*/
- private handleDragStart(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void {
+ public handleDragStart(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void {
this.originalEvent = originalElement;
-
+
// Remove stacking styling during drag will be handled by new system
+
+ // Create SwpEventElement from existing DOM element and clone it
+ const originalSwpEvent = SwpEventElement.fromExistingElement(originalElement);
+ const clonedSwpEvent = originalSwpEvent.createClone();
- // Create clone
- this.draggedClone = this.createEventClone(originalElement);
+ // Get the cloned DOM element
+ this.draggedClone = clonedSwpEvent.getElement();
+ // Apply drag styling
+ this.applyDragStyling(this.draggedClone);
+
// Add to current column's events layer (not directly to column)
const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`);
if (columnElement) {
@@ -323,33 +196,46 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
columnElement.appendChild(this.draggedClone);
}
}
-
+
// Make original semi-transparent
originalElement.style.opacity = '0.3';
originalElement.style.userSelect = 'none';
-
+
}
-
+
/**
* Handle drag move event
*/
- private handleDragMove(eventId: string, snappedY: number, column: string, mouseOffset: any): void {
+ public handleDragMove(eventId: string, snappedY: number, column: string, mouseOffset: any): void {
if (!this.draggedClone) return;
-
+
// Update position
this.draggedClone.style.top = snappedY + 'px';
-
+
// Update timestamp display
this.updateCloneTimestamp(this.draggedClone, snappedY);
-
+
}
-
+
+ /**
+ * Handle drag auto-scroll event
+ */
+ public handleDragAutoScroll(eventId: string, snappedY: number): void {
+ if (!this.draggedClone) return;
+
+ // Update position directly using the calculated snapped position
+ this.draggedClone.style.top = snappedY + 'px';
+
+ // Update timestamp display
+ this.updateCloneTimestamp(this.draggedClone, snappedY);
+ }
+
/**
* Handle column change during drag
*/
- private handleColumnChange(eventId: string, newColumn: string): void {
+ public handleColumnChange(eventId: string, newColumn: string): void {
if (!this.draggedClone) return;
-
+
// Move clone to new column's events layer
const newColumnElement = document.querySelector(`swp-day-column[data-date="${newColumn}"]`);
if (newColumnElement) {
@@ -362,24 +248,24 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
}
}
}
-
+
/**
* Handle drag end event
*/
- private handleDragEnd(eventId: string, originalElement: HTMLElement, finalColumn: string, finalY: number): void {
-
- if (!this.draggedClone || !this.originalEvent) {
- console.warn('Missing draggedClone or originalEvent');
+ public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void {
+
+ if (!draggedClone || !originalElement) {
+ console.warn('Missing draggedClone or originalElement');
return;
}
-
+
// Check om original event var del af en stack
- const originalStackLink = this.originalEvent.dataset.stackLink;
+ const originalStackLink = originalElement.dataset.stackLink;
if (originalStackLink) {
try {
const stackData = JSON.parse(originalStackLink);
-
+
// Saml ALLE event IDs fra hele stack chain
const allStackEventIds: Set = new Set();
@@ -392,10 +278,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
try {
const prevLinkData = JSON.parse(prevElement.dataset.stackLink);
traverseStack(prevLinkData, visitedIds);
- } catch (e) {}
+ } catch (e) { }
}
}
-
+
if (linkData.next && !visitedIds.has(linkData.next)) {
visitedIds.add(linkData.next);
const nextElement = document.querySelector(`swp-time-grid [data-event-id="${linkData.next}"]`) as HTMLElement;
@@ -403,7 +289,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
try {
const nextLinkData = JSON.parse(nextElement.dataset.stackLink);
traverseStack(nextLinkData, visitedIds);
- } catch (e) {}
+ } catch (e) { }
}
}
};
@@ -425,17 +311,17 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
if (!container) {
container = element.closest('swp-events-layer') as HTMLElement;
}
-
+
const event = this.elementToCalendarEvent(element);
if (event) {
stackEvents.push(event);
}
-
+
// Fjern elementet
element.remove();
}
});
-
+
// Re-render stack events hvis vi fandt nogle
if (stackEvents.length > 0 && container) {
this.handleEventOverlaps(stackEvents, container);
@@ -444,93 +330,100 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
console.warn('Failed to parse stackLink data:', e);
}
}
-
+
// Remove original event from any existing groups first
- this.removeEventFromExistingGroups(this.originalEvent);
-
+ this.removeEventFromExistingGroups(originalElement);
+
// Fade out original
- this.fadeOutAndRemove(this.originalEvent);
-
+ this.fadeOutAndRemove(originalElement);
+
// Remove clone prefix and normalize clone to be a regular event
- const cloneId = this.draggedClone.dataset.eventId;
+ const cloneId = draggedClone.dataset.eventId;
if (cloneId && cloneId.startsWith('clone-')) {
- this.draggedClone.dataset.eventId = cloneId.replace('clone-', '');
+ draggedClone.dataset.eventId = cloneId.replace('clone-', '');
}
-
+
// Fully normalize the clone to be a regular event
- this.draggedClone.classList.remove('dragging');
+ draggedClone.classList.remove('dragging');
// Behold z-index hvis det er et stacked event
-
+
// Update dataset with new times after successful drop (only for timed events)
- if (this.draggedClone.dataset.displayType !== 'allday') {
- const newEvent = this.elementToCalendarEvent(this.draggedClone);
+ if (draggedClone.dataset.displayType !== 'allday') {
+ const newEvent = this.elementToCalendarEvent(draggedClone);
if (newEvent) {
- this.draggedClone.dataset.start = newEvent.start.toISOString();
- this.draggedClone.dataset.end = newEvent.end.toISOString();
+ draggedClone.dataset.start = newEvent.start.toISOString();
+ draggedClone.dataset.end = newEvent.end.toISOString();
}
}
-
+
// Detect overlaps with other events in the target column and reposition if needed
- this.handleDragDropOverlaps(this.draggedClone, finalColumn);
-
+ this.handleDragDropOverlaps(draggedClone, finalColumn);
+
// Fjern stackLink data fra dropped element
- if (this.draggedClone.dataset.stackLink) {
- delete this.draggedClone.dataset.stackLink;
+ if (draggedClone.dataset.stackLink) {
+ delete draggedClone.dataset.stackLink;
}
-
- // Clean up
+
+ // Clean up instance state (no longer needed since we get elements as parameters)
this.draggedClone = null;
this.originalEvent = null;
-
+
}
-
+
/**
* Handle event click (when drag threshold not reached)
*/
- private handleEventClick(eventId: string, originalElement: HTMLElement): void {
+ public handleEventClick(eventId: string, originalElement: HTMLElement): void {
console.log('handleEventClick:', eventId);
-
+
// Clean up any drag artifacts from failed drag attempt
if (this.draggedClone) {
this.draggedClone.classList.remove('dragging');
this.draggedClone.remove();
this.draggedClone = null;
}
-
+
// Restore original element styling if it was modified
if (this.originalEvent) {
this.originalEvent.style.opacity = '';
this.originalEvent.style.userSelect = '';
this.originalEvent = null;
}
-
+
// Emit a clean click event for other components to handle
eventBus.emit('event:clicked', {
eventId: eventId,
element: originalElement
});
}
-
+
+ /**
+ * Handle navigation completed event
+ */
+ public handleNavigationCompleted(): void {
+ // Default implementation - can be overridden by subclasses
+ }
+
/**
* Handle overlap detection and re-rendering after drag-drop
*/
private handleDragDropOverlaps(droppedElement: HTMLElement, targetColumn: string): void {
const targetColumnElement = document.querySelector(`swp-day-column[data-date="${targetColumn}"]`);
if (!targetColumnElement) return;
-
+
const eventsLayer = targetColumnElement.querySelector('swp-events-layer') as HTMLElement;
if (!eventsLayer) return;
-
+
// Convert dropped element to CalendarEvent with new position
const droppedEvent = this.elementToCalendarEvent(droppedElement);
if (!droppedEvent) return;
-
+
// Get existing events in the column (excluding the dropped element)
const existingEvents = this.getEventsInColumn(eventsLayer, droppedElement.dataset.eventId);
-
+
// Find overlaps with the dropped event
const overlappingEvents = this.overlapDetector.resolveOverlap(droppedEvent, existingEvents);
-
+
if (overlappingEvents.length > 0) {
// Remove only affected events from DOM
const affectedEventIds = [droppedEvent.id, ...overlappingEvents.map(e => e.id)];
@@ -540,7 +433,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
el.remove();
}
});
-
+
// Re-render affected events with overlap handling
const affectedEvents = [droppedEvent, ...overlappingEvents];
this.handleEventOverlaps(affectedEvents, eventsLayer);
@@ -556,22 +449,22 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
private getEventsInColumn(eventsLayer: HTMLElement, excludeEventId?: string): CalendarEvent[] {
const eventElements = eventsLayer.querySelectorAll('swp-event');
const events: CalendarEvent[] = [];
-
+
eventElements.forEach(el => {
const element = el as HTMLElement;
const eventId = element.dataset.eventId;
-
+
// Skip the excluded event (e.g., the dropped event)
if (excludeEventId && eventId === excludeEventId) {
return;
}
-
+
const event = this.elementToCalendarEvent(element);
if (event) {
events.push(event);
}
});
-
+
return events;
}
@@ -584,23 +477,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// No need to manually track and remove from groups
}
- /**
- * Update element's dataset with new times after successful drop
- */
- private updateElementDataset(element: HTMLElement, event: CalendarEvent): void {
- element.dataset.start = event.start.toISOString();
- element.dataset.end = event.end.toISOString();
-
- // Update the time display
- const timeElement = element.querySelector('swp-event-time');
- if (timeElement) {
- const timeRange = TimeFormatter.formatTimeRange(event.start, event.end);
- timeElement.textContent = timeRange;
- }
- }
-
-
-
/**
* Convert DOM element to CalendarEvent - handles both normal and 1970 reference dates
*/
@@ -610,21 +486,21 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
const type = element.dataset.type;
const start = element.dataset.start;
const end = element.dataset.end;
-
+
if (!eventId || !title || !type || !start || !end) {
return null;
}
-
+
let startDate = new Date(start);
let endDate = new Date(end);
-
+
// Check if we have 1970 reference date (from drag operations)
if (startDate.getFullYear() === 1970) {
// Find the parent column to get the actual date
const columnElement = element.closest('swp-day-column') as HTMLElement;
if (columnElement && columnElement.dataset.date) {
const columnDate = new Date(columnElement.dataset.date);
-
+
// Keep the time portion from the 1970 dates, but use the column's date
startDate = new Date(
columnDate.getFullYear(),
@@ -633,7 +509,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
startDate.getHours(),
startDate.getMinutes()
);
-
+
endDate = new Date(
columnDate.getFullYear(),
columnDate.getMonth(),
@@ -643,7 +519,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
);
}
}
-
+
return {
id: eventId,
title: title,
@@ -657,33 +533,33 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
}
};
}
-
+
/**
* Handle conversion to all-day event
*/
-
+
/**
* Fade out and remove element
*/
private fadeOutAndRemove(element: HTMLElement): void {
element.style.transition = 'opacity 0.3s ease-out';
element.style.opacity = '0';
-
+
setTimeout(() => {
element.remove();
}, 300);
}
-
-
+
+
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
-
+
// NOTE: Removed clearEvents() to support sliding animation
// With sliding animation, multiple grid containers exist simultaneously
// clearEvents() would remove events from all containers, breaking the animation
// Events are now rendered directly into the new container without clearing
// Only handle regular (non-all-day) events
-
+
// Find columns in the specific container for regular events
@@ -691,7 +567,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, events);
-
+
const eventsLayer = column.querySelector('swp-events-layer');
if (eventsLayer) {
// NY TILGANG: Kald vores nye overlap handling
@@ -708,14 +584,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
protected renderEvent(event: CalendarEvent): HTMLElement {
const swpEvent = SwpEventElement.fromCalendarEvent(event);
const eventElement = swpEvent.getElement();
-
+
// Setup resize handles on first mouseover only
eventElement.addEventListener('mouseover', () => {
if (eventElement.dataset.hasResizeHandlers !== 'true') {
eventElement.dataset.hasResizeHandlers = 'true';
}
}, { once: true });
-
+
return eventElement;
}
@@ -729,7 +605,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
const existingEvents = container
? container.querySelectorAll(selector)
: document.querySelectorAll(selector);
-
+
existingEvents.forEach(event => event.remove());
}
@@ -743,16 +619,16 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
for (const [eventId, stackLink] of result.stackLinks.entries()) {
const event = result.overlappingEvents.find(e => e.id === eventId);
if (!event) continue;
-
+
const element = this.renderEvent(event);
-
+
// Gem stack link information på DOM elementet
element.dataset.stackLink = JSON.stringify({
prev: stackLink.prev,
next: stackLink.next,
stackLevel: stackLink.stackLevel
});
-
+
// Check om dette event deler kolonne med foregående (samme start tid)
if (stackLink.prev) {
const prevEvent = result.overlappingEvents.find(e => e.id === stackLink.prev);
@@ -767,7 +643,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// Første event i stack
this.new_applyStackStyling(element, stackLink.stackLevel);
}
-
+
container.appendChild(element);
}
}
@@ -817,8 +693,8 @@ export class DateEventRenderer extends BaseEventRenderer {
const columnEvents = events.filter(event => {
const eventDateStr = DateCalculator.formatISODate(event.start);
const matches = eventDateStr === columnDate;
-
-
+
+
return matches;
});
diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts
index 2e65e8b..9fb71b8 100644
--- a/src/renderers/EventRendererManager.ts
+++ b/src/renderers/EventRendererManager.ts
@@ -71,25 +71,18 @@ export class EventRenderingService {
this.handleViewChanged(event as CustomEvent);
});
- // Simple drag:end listener to clean up day event clones
- this.eventBus.on('drag:end', (event: Event) => {
- const { eventId } = (event as CustomEvent).detail;
- const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`);
-
- if (dayEventClone) {
- dayEventClone.remove();
- }
- });
+ // Handle all drag events and delegate to appropriate renderer
+ this.setupDragEventListeners();
// Listen for conversion from all-day event to time event
this.eventBus.on('drag:convert-to-time_event', (event: Event) => {
- const { draggedEventId, mousePosition, column } = (event as CustomEvent).detail;
+ const { draggedElement, mousePosition, column } = (event as CustomEvent).detail;
console.log('🔄 EventRendererManager: Received drag:convert-to-time_event', {
- draggedEventId,
+ draggedElement: draggedElement?.dataset.eventId,
mousePosition,
column
});
- this.handleConvertToTimeEvent(draggedEventId, mousePosition, column);
+ this.handleConvertToTimeEvent(draggedElement, mousePosition, column);
});
}
@@ -153,18 +146,94 @@ export class EventRenderingService {
}
+ /**
+ * Setup all drag event listeners - moved from EventRenderer for better separation of concerns
+ */
+ private setupDragEventListeners(): void {
+ // Handle drag start
+ this.eventBus.on('drag:start', (event: Event) => {
+ const { eventId, mouseOffset, column } = (event as CustomEvent).detail;
+ // Find element dynamically
+ const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
+ if (originalElement && this.strategy.handleDragStart) {
+ this.strategy.handleDragStart(originalElement, eventId, mouseOffset, column);
+ }
+ });
+
+ // Handle drag move
+ this.eventBus.on('drag:move', (event: Event) => {
+ const { eventId, snappedY, column, mouseOffset } = (event as CustomEvent).detail;
+ if (this.strategy.handleDragMove) {
+ this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset);
+ }
+ });
+
+ // Handle drag auto-scroll
+ this.eventBus.on('drag:auto-scroll', (event: Event) => {
+ const { eventId, snappedY } = (event as CustomEvent).detail;
+ if (this.strategy.handleDragAutoScroll) {
+ this.strategy.handleDragAutoScroll(eventId, snappedY);
+ }
+ });
+
+ // Handle drag end events and delegate to appropriate renderer
+ this.eventBus.on('drag:end', (event: Event) => {
+ const { eventId, finalColumn, finalY, target } = (event as CustomEvent).detail;
+
+ // Only handle day column drops for EventRenderer
+ if (target === 'swp-day-column') {
+ // Find both original element and dragged clone
+ const originalElement = document.querySelector(`swp-day-column swp-event[data-event-id="${eventId}"]`) as HTMLElement;
+ const draggedClone = document.querySelector(`swp-day-column swp-event[data-event-id="clone-${eventId}"]`) as HTMLElement;
+
+ if (originalElement && draggedClone && this.strategy.handleDragEnd) {
+ this.strategy.handleDragEnd(eventId, originalElement, draggedClone, finalColumn, finalY);
+ }
+ }
+
+ // Clean up any remaining day event clones
+ const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`);
+ if (dayEventClone) {
+ dayEventClone.remove();
+ }
+ });
+
+ // Handle click (when drag threshold not reached)
+ this.eventBus.on('event:click', (event: Event) => {
+ const { eventId } = (event as CustomEvent).detail;
+ // Find element dynamically
+ const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
+ if (originalElement && this.strategy.handleEventClick) {
+ this.strategy.handleEventClick(eventId, originalElement);
+ }
+ });
+
+ // Handle column change
+ this.eventBus.on('drag:column-change', (event: Event) => {
+ const { eventId, newColumn } = (event as CustomEvent).detail;
+ if (this.strategy.handleColumnChange) {
+ this.strategy.handleColumnChange(eventId, newColumn);
+ }
+ });
+
+ // Handle navigation period change
+ this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
+ // Delegate to strategy if it handles navigation
+ if (this.strategy.handleNavigationCompleted) {
+ this.strategy.handleNavigationCompleted();
+ }
+ });
+ }
+
/**
* Handle conversion from all-day event to time event
*/
- private handleConvertToTimeEvent(draggedEventId: string, mousePosition: any, column: string): void {
- // Find all-day event clone
- const allDayClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${draggedEventId}"]`);
-
- if (!allDayClone) {
- console.warn('EventRendererManager: All-day clone not found - drag may not have started properly', { draggedEventId });
- return;
- }
+ private handleConvertToTimeEvent(draggedElement: HTMLElement, mousePosition: any, column: string): void {
+ // Use the provided draggedElement directly
+ const allDayClone = draggedElement;
+ const draggedEventId = draggedElement?.dataset.eventId?.replace('clone-', '') || '';
+
// Use SwpEventElement factory to create day event from all-day event
const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement);
const dayElement = dayEventElement.getElement();