Refactors event drag-drop and cloning logic

Centralizes drag event listener setup in `EventRendererManager` for better separation of concerns.

Introduces factory and cloning methods in `SwpEventElement` to simplify event cloning and data extraction from DOM elements during drag operations.

Enhances `DragDropManager` to pass the actual dragged element for conversion and accurately detect the drop target (day column or header).

Updates `EventRenderer` to expose drag-handling methods publicly, allowing the `EventRendererManager` to delegate event-specific drag operations based on drop target.
This commit is contained in:
Janus C. H. Knudsen 2025-09-20 09:40:56 +02:00
parent 0b7499521e
commit b4f5b29da3
6 changed files with 357 additions and 304 deletions

View file

@ -101,6 +101,83 @@ export class SwpEventElement extends BaseEventElement {
return new SwpEventElement(event); 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 * Factory method to convert an all-day HTML element to a timed SwpEventElement
*/ */

View file

@ -74,14 +74,26 @@ export class AllDayManager {
}); });
eventBus.on('drag:end', (event) => { 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 // Check if this was an all-day event
const originalElement = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`); 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}"]`); 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 }); 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);
}); });
} }

View file

@ -172,8 +172,11 @@ export class DragDropManager {
const mousePosition = { x: this.lastMousePosition.x, y: this.lastMousePosition.y }; const mousePosition = { x: this.lastMousePosition.x, y: this.lastMousePosition.y };
const column = this.getColumnDateFromX(mousePosition.x); 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', { this.eventBus.emit('drag:convert-to-time_event', {
draggedEventId: this.draggedEventId, draggedElement: draggedElement,
mousePosition: mousePosition, mousePosition: mousePosition,
column: column column: column
}); });
@ -319,10 +322,14 @@ export class DragDropManager {
// Use consolidated position calculation // Use consolidated position calculation
const positionData = this.calculateDragPosition(finalPosition); 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', { console.log('🎯 DragDropManager: Emitting drag:end', {
eventId: eventId, eventId: eventId,
finalColumn: positionData.column, finalColumn: positionData.column,
finalY: positionData.snappedY, finalY: positionData.snappedY,
dropTarget: dropTarget,
isDragStarted: isDragStarted isDragStarted: isDragStarted
}); });
@ -330,7 +337,8 @@ export class DragDropManager {
eventId: eventId, eventId: eventId,
finalPosition, finalPosition,
finalColumn: positionData.column, finalColumn: positionData.column,
finalY: positionData.snappedY finalY: positionData.snappedY,
target: dropTarget
}); });
} else { } else {
// This was just a click - emit click event instead // 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) // Sorter efter x-position (fra venstre til højre)
this.columnBoundsCache.sort((a, b) => a.left - b.left); 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; 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 * Clean up all resources and event listeners
*/ */

View file

@ -44,22 +44,15 @@ export class HeaderManager {
* Setup header drag event listeners - REFACTORED to use mouseenter * Setup header drag event listeners - REFACTORED to use mouseenter
*/ */
public setupHeaderDragListeners(): void { public setupHeaderDragListeners(): void {
const calendarHeader = this.getCalendarHeader(); if (!this.getCalendarHeader()) return;
if (!calendarHeader) return;
console.log('🎯 HeaderManager: Setting up drag listeners with mouseenter'); 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 // Use mouseenter instead of mouseover to avoid continuous firing
this.headerEventListener = (event: Event) => { this.headerEventListener = (event: Event) => {
// OPTIMIZED: Check for active drag operation FIRST before doing any other work
const isDragActive = document.querySelector('.dragging') !== null;
if (!isDragActive) { if (!document.querySelector('.dragging') !== null) {
// Ingen drag operation, spring resten af funktionen over
return; return;
} }
@ -114,9 +107,8 @@ export class HeaderManager {
}); });
}; };
// Use mouseenter with capture to catch events early this.getCalendarHeader()?.addEventListener('mouseenter', this.headerEventListener, true);
calendarHeader.addEventListener('mouseenter', this.headerEventListener, true); this.getCalendarHeader()?.addEventListener('mouseleave', this.headerMouseLeaveListener);
calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener);
console.log('✅ HeaderManager: Event listeners attached (mouseenter + mouseleave)'); console.log('✅ HeaderManager: Event listeners attached (mouseenter + mouseleave)');
} }

View file

@ -16,6 +16,13 @@ import { PositionUtils } from '../utils/PositionUtils';
export interface EventRendererStrategy { export interface EventRendererStrategy {
renderEvents(events: CalendarEvent[], container: HTMLElement): void; renderEvents(events: CalendarEvent[], container: HTMLElement): void;
clearEvents(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;
} }
/** /**
@ -90,87 +97,11 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/** /**
* Setup listeners for drag events from DragDropManager * Setup listeners for drag events from DragDropManager
* NOTE: Event listeners moved to EventRendererManager for better separation of concerns
*/ */
protected setupDragEventListeners(): void { protected setupDragEventListeners(): void {
// Handle drag start // All event listeners now handled by EventRendererManager
eventBus.on('drag:start', (event) => { // This method kept for backward compatibility but does nothing
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
});
} }
@ -182,23 +113,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
this.originalEvent = null; 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 * Apply common drag styling to an element
@ -207,54 +121,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
element.classList.add('dragging'); 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 `
<swp-event-time data-duration="${durationMinutes}">${timeRange}</swp-event-time>
<swp-event-title>${event.title}</swp-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 * Update clone timestamp based on new position
@ -304,13 +170,20 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/** /**
* Handle drag start event * 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; this.originalEvent = originalElement;
// Remove stacking styling during drag will be handled by new system // Remove stacking styling during drag will be handled by new system
// Create clone // Create SwpEventElement from existing DOM element and clone it
this.draggedClone = this.createEventClone(originalElement); const originalSwpEvent = SwpEventElement.fromExistingElement(originalElement);
const clonedSwpEvent = originalSwpEvent.createClone();
// 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) // Add to current column's events layer (not directly to column)
const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`); const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`);
@ -333,7 +206,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/** /**
* Handle drag move event * 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; if (!this.draggedClone) return;
// Update position // Update position
@ -344,10 +217,23 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
} }
/**
* 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 * Handle column change during drag
*/ */
private handleColumnChange(eventId: string, newColumn: string): void { public handleColumnChange(eventId: string, newColumn: string): void {
if (!this.draggedClone) return; if (!this.draggedClone) return;
// Move clone to new column's events layer // Move clone to new column's events layer
@ -366,15 +252,15 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/** /**
* Handle drag end event * Handle drag end event
*/ */
private handleDragEnd(eventId: string, originalElement: HTMLElement, finalColumn: string, finalY: number): void { public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void {
if (!this.draggedClone || !this.originalEvent) { if (!draggedClone || !originalElement) {
console.warn('Missing draggedClone or originalEvent'); console.warn('Missing draggedClone or originalElement');
return; return;
} }
// Check om original event var del af en stack // Check om original event var del af en stack
const originalStackLink = this.originalEvent.dataset.stackLink; const originalStackLink = originalElement.dataset.stackLink;
if (originalStackLink) { if (originalStackLink) {
try { try {
@ -446,39 +332,39 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
} }
// Remove original event from any existing groups first // Remove original event from any existing groups first
this.removeEventFromExistingGroups(this.originalEvent); this.removeEventFromExistingGroups(originalElement);
// Fade out original // Fade out original
this.fadeOutAndRemove(this.originalEvent); this.fadeOutAndRemove(originalElement);
// Remove clone prefix and normalize clone to be a regular event // 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-')) { 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 // 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 // Behold z-index hvis det er et stacked event
// Update dataset with new times after successful drop (only for timed events) // Update dataset with new times after successful drop (only for timed events)
if (this.draggedClone.dataset.displayType !== 'allday') { if (draggedClone.dataset.displayType !== 'allday') {
const newEvent = this.elementToCalendarEvent(this.draggedClone); const newEvent = this.elementToCalendarEvent(draggedClone);
if (newEvent) { if (newEvent) {
this.draggedClone.dataset.start = newEvent.start.toISOString(); draggedClone.dataset.start = newEvent.start.toISOString();
this.draggedClone.dataset.end = newEvent.end.toISOString(); draggedClone.dataset.end = newEvent.end.toISOString();
} }
} }
// Detect overlaps with other events in the target column and reposition if needed // 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 // Fjern stackLink data fra dropped element
if (this.draggedClone.dataset.stackLink) { if (draggedClone.dataset.stackLink) {
delete this.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.draggedClone = null;
this.originalEvent = null; this.originalEvent = null;
@ -487,7 +373,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
/** /**
* Handle event click (when drag threshold not reached) * 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); console.log('handleEventClick:', eventId);
// Clean up any drag artifacts from failed drag attempt // Clean up any drag artifacts from failed drag attempt
@ -511,6 +397,13 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
}); });
} }
/**
* Handle navigation completed event
*/
public handleNavigationCompleted(): void {
// Default implementation - can be overridden by subclasses
}
/** /**
* Handle overlap detection and re-rendering after drag-drop * Handle overlap detection and re-rendering after drag-drop
*/ */
@ -584,23 +477,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// No need to manually track and remove from groups // 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 * Convert DOM element to CalendarEvent - handles both normal and 1970 reference dates
*/ */

View file

@ -71,25 +71,18 @@ export class EventRenderingService {
this.handleViewChanged(event as CustomEvent); this.handleViewChanged(event as CustomEvent);
}); });
// Simple drag:end listener to clean up day event clones // Handle all drag events and delegate to appropriate renderer
this.eventBus.on('drag:end', (event: Event) => { this.setupDragEventListeners();
const { eventId } = (event as CustomEvent).detail;
const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`);
if (dayEventClone) {
dayEventClone.remove();
}
});
// Listen for conversion from all-day event to time event // Listen for conversion from all-day event to time event
this.eventBus.on('drag:convert-to-time_event', (event: 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', { console.log('🔄 EventRendererManager: Received drag:convert-to-time_event', {
draggedEventId, draggedElement: draggedElement?.dataset.eventId,
mousePosition, mousePosition,
column column
}); });
this.handleConvertToTimeEvent(draggedEventId, mousePosition, column); this.handleConvertToTimeEvent(draggedElement, mousePosition, column);
}); });
} }
@ -153,17 +146,93 @@ 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 * Handle conversion from all-day event to time event
*/ */
private handleConvertToTimeEvent(draggedEventId: string, mousePosition: any, column: string): void { private handleConvertToTimeEvent(draggedElement: HTMLElement, mousePosition: any, column: string): void {
// Find all-day event clone // Use the provided draggedElement directly
const allDayClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${draggedEventId}"]`); const allDayClone = draggedElement;
const draggedEventId = draggedElement?.dataset.eventId?.replace('clone-', '') || '';
if (!allDayClone) {
console.warn('EventRendererManager: All-day clone not found - drag may not have started properly', { draggedEventId });
return;
}
// Use SwpEventElement factory to create day event from all-day event // Use SwpEventElement factory to create day event from all-day event
const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement); const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement);