Enables all-day event conversion on column hover

Allows users to convert all-day events to timed events by dragging them over a day column.

This implementation adds logic to the DragDropManager to detect when an all-day event is dragged over a column.
It then emits a new event, 'drag:mouseenter-column', carrying the event data and target column information.

The event rendering service handles this event.
This commit is contained in:
Janus C. H. Knudsen 2025-10-10 16:41:48 +02:00
parent 7a79297854
commit 78ca23c07a
5 changed files with 69 additions and 125 deletions

View file

@ -289,46 +289,6 @@ export class SwpEventElement extends BaseSwpEventElement {
}; };
} }
/**
* Factory method to convert an all-day HTML element to a timed SwpEventElement
*/
public static fromAllDayElement(allDayElement: HTMLElement): SwpEventElement {
const eventId = allDayElement.dataset.eventId || '';
const title = allDayElement.dataset.title || allDayElement.textContent || 'Untitled';
const type = allDayElement.dataset.type || 'work';
const startStr = allDayElement.dataset.start;
const endStr = allDayElement.dataset.end;
const durationStr = allDayElement.dataset.duration;
if (!startStr || !endStr) {
throw new Error('All-day element missing start/end dates');
}
const originalStart = new Date(startStr);
const duration = durationStr ? parseInt(durationStr) : 60;
const now = new Date();
const startDate = new Date(originalStart);
startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0);
const endDate = new Date(startDate);
endDate.setMinutes(endDate.getMinutes() + duration);
const calendarEvent: CalendarEvent = {
id: eventId,
title: title,
start: startDate,
end: endDate,
type: type,
allDay: false,
syncStatus: 'synced',
metadata: {
duration: duration.toString()
}
};
return SwpEventElement.fromCalendarEvent(calendarEvent);
}
} }
/** /**

View file

@ -14,6 +14,7 @@ import {
DragEndEventPayload, DragEndEventPayload,
DragMouseEnterHeaderEventPayload, DragMouseEnterHeaderEventPayload,
DragMouseLeaveHeaderEventPayload, DragMouseLeaveHeaderEventPayload,
DragMouseEnterColumnEventPayload,
DragColumnChangeEventPayload DragColumnChangeEventPayload
} from '../types/EventTypes'; } from '../types/EventTypes';
import { MousePosition } from '../types/DragDropTypes'; import { MousePosition } from '../types/DragDropTypes';
@ -116,6 +117,8 @@ export class DragDropManager {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (target.closest('swp-calendar-header')) { if (target.closest('swp-calendar-header')) {
this.handleHeaderMouseEnter(e as MouseEvent); this.handleHeaderMouseEnter(e as MouseEvent);
} else if (target.closest('swp-day-column')) {
this.handleColumnMouseEnter(e as MouseEvent);
} else if (target.closest('swp-event')) { } else if (target.closest('swp-event')) {
// Entered an event - activate hover tracking and set color // Entered an event - activate hover tracking and set color
const eventElement = target.closest<HTMLElement>('swp-event'); const eventElement = target.closest<HTMLElement>('swp-event');
@ -652,6 +655,46 @@ export class DragDropManager {
} }
} }
/**
* Handle mouse enter on day column - for converting all-day to timed events
*/
private handleColumnMouseEnter(event: MouseEvent): void {
// Only handle if we're dragging an all-day event
if (!this.isDragStarted || !this.draggedClone || !this.draggedClone.hasAttribute('data-allday')) {
return;
}
console.log('🎯 DragDropManager: Mouse entered day column');
const position: MousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
console.warn("No column detected when entering day column");
return;
}
// Calculate snapped Y position
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
// Extract CalendarEvent from the dragged clone
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = {
targetColumn: targetColumn,
mousePosition: position,
snappedY: snappedY,
originalElement: this.draggedElement,
draggedClone: this.draggedClone,
calendarEvent: calendarEvent,
// Delegate pattern - allows EventRenderer to replace the clone
replaceClone: (newClone: HTMLElement) => {
this.draggedClone = newClone;
}
};
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
}
/** /**
* Handle mouse leave from calendar header - simplified using native events * Handle mouse leave from calendar header - simplified using native events
*/ */

View file

@ -36,7 +36,7 @@ export class HeaderManager {
* Get cached calendar header element * Get cached calendar header element
*/ */
private getCalendarHeader(): HTMLElement | null { private getCalendarHeader(): HTMLElement | null {
return document.querySelector('swp-calendar-header'); return document.querySelector('swp-calendar-header');
} }
/** /**
@ -71,13 +71,6 @@ export class HeaderManager {
originalElement: !!originalElement, originalElement: !!originalElement,
cloneElement: !!cloneElement cloneElement: !!cloneElement
}); });
eventBus.emit('header:mouseleave', {
element: this.getCalendarHeader(),
targetDate,
originalElement,
cloneElement
});
}; };
// Listen for drag events from DragDropManager // Listen for drag events from DragDropManager
@ -117,7 +110,7 @@ export class HeaderManager {
const calendarHeader = this.getOrCreateCalendarHeader(); const calendarHeader = this.getOrCreateCalendarHeader();
if (!calendarHeader) return; if (!calendarHeader) return;
// Clear existing content // Clear existing content
calendarHeader.innerHTML = ''; calendarHeader.innerHTML = '';
// Render new header content // Render new header content

View file

@ -88,17 +88,6 @@ export class EventRenderingService {
// Handle all drag events and delegate to appropriate renderer // Handle all drag events and delegate to appropriate renderer
this.setupDragEventListeners(); this.setupDragEventListeners();
// Listen for conversion from all-day event to time event
this.eventBus.on('drag:convert-to-time_event', (event: Event) => {
const { draggedElement, mousePosition, column } = (event as CustomEvent).detail;
console.log('🔄 EventRendererManager: Received drag:convert-to-time_event', {
draggedElement: draggedElement?.dataset.eventId,
mousePosition,
column
});
this.handleConvertToTimeEvent(draggedElement, mousePosition, column);
});
} }
@ -302,59 +291,6 @@ export class EventRenderingService {
}); });
} }
/**
* Handle conversion from all-day event to time event
*/
private handleConvertToTimeEvent(draggedElement: HTMLElement, mousePosition: { x: number; y: number }, 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 dayElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement);
// Remove the all-day clone - it's no longer needed since we're converting to day event
allDayClone.remove();
// Set clone ID
dayElement.dataset.eventId = `clone-${draggedEventId}`;
// Find target column
const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`);
if (!columnElement) {
console.warn('EventRendererManager: Target column not found', { column });
return;
}
// Find events layer in the column
const eventsLayer = columnElement.querySelector('swp-events-layer');
if (!eventsLayer) {
console.warn('EventRendererManager: Events layer not found in column');
return;
}
// Add to events layer
eventsLayer.appendChild(dayElement);
// Position based on mouse Y coordinate
const columnRect = columnElement.getBoundingClientRect();
const relativeY = Math.max(0, mousePosition.y - columnRect.top);
dayElement.style.top = `${relativeY}px`;
// Set drag styling
dayElement.style.zIndex = '1000';
dayElement.style.cursor = 'grabbing';
dayElement.style.opacity = '';
dayElement.style.transform = '';
console.log('✅ EventRendererManager: Converted all-day event to time event', {
draggedEventId,
column,
mousePosition,
relativeY
});
}
/** /**
* Re-render affected columns after drag to recalculate stacking/grouping * Re-render affected columns after drag to recalculate stacking/grouping

View file

@ -67,6 +67,18 @@ export interface DragMouseLeaveHeaderEventPayload {
draggedClone: HTMLElement| null; draggedClone: HTMLElement| null;
} }
// Drag mouse enter column event payload
export interface DragMouseEnterColumnEventPayload {
targetColumn: ColumnBounds;
mousePosition: MousePosition;
snappedY: number;
originalElement: HTMLElement | null;
draggedClone: HTMLElement;
calendarEvent: CalendarEvent;
// Delegate pattern - allows subscriber to replace the dragged clone
replaceClone: (newClone: HTMLElement) => void;
}
// Drag column change event payload // Drag column change event payload
export interface DragColumnChangeEventPayload { export interface DragColumnChangeEventPayload {
originalElement: HTMLElement; originalElement: HTMLElement;