Refactors drag header interaction logic

Improves efficiency and reliability of drag-and-drop operations involving calendar headers.

Transitions from continuous polling within `handleMouseMove` to using native `mouseenter` and `mouseleave` events with delegation on `swp-calendar-header` elements. This change ensures more precise and performant detection of header interactions during a drag.

Also enhances the initial event detection logic to correctly identify `SWP-ALLDAY-EVENT` elements when starting a drag.
This commit is contained in:
Janus C. H. Knudsen 2025-10-04 21:12:52 +02:00
parent 420036d939
commit 125cd678a3

View file

@ -93,12 +93,28 @@ export class DragDropManager {
this.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement; this.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement;
const calendarContainer = document.querySelector('swp-calendar-container'); const calendarContainer = document.querySelector('swp-calendar-container');
if (calendarContainer) { if (calendarContainer) {
calendarContainer.addEventListener('mouseleave', () => { calendarContainer.addEventListener('mouseleave', () => {
if (this.draggedElement && this.isDragStarted) { if (this.draggedElement && this.isDragStarted) {
this.cancelDrag(); this.cancelDrag();
} }
}); });
// Event delegation for header enter/leave
calendarContainer.addEventListener('mouseenter', (e) => {
const target = e.target as HTMLElement;
if (target.closest('swp-calendar-header')) {
this.handleHeaderMouseEnter(e as MouseEvent);
}
}, true); // Use capture phase
calendarContainer.addEventListener('mouseleave', (e) => {
const target = e.target as HTMLElement;
if (target.closest('swp-calendar-header')) {
this.handleHeaderMouseLeave(e as MouseEvent);
}
}, true); // Use capture phase
} }
// Initialize column bounds cache // Initialize column bounds cache
@ -129,18 +145,14 @@ export class DragDropManager {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
let eventElement = target; let eventElement = target;
while (eventElement && eventElement.tagName !== 'SWP-EVENTS-LAYER') { while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') {
if (eventElement.tagName === 'SWP-EVENT') { if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') {
break; break;
} }
eventElement = eventElement.parentElement as HTMLElement; eventElement = eventElement.parentElement as HTMLElement;
if (!eventElement) return; if (!eventElement) return;
} }
// If we reached SWP-EVENTS-LAYER without finding an event, return
if (!eventElement || eventElement.tagName === 'SWP-EVENTS-LAYER') {
return;
}
// Found an event - prepare for potential dragging // Found an event - prepare for potential dragging
if (eventElement) { if (eventElement) {
@ -165,12 +177,7 @@ export class DragDropManager {
if (event.buttons === 1) { if (event.buttons === 1) {
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; //TODO: Is this really needed? why not just use event.clientX + Y directly const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
// Check for header enter/leave during drag
if (this.draggedClone) {
this.checkHeaderEnterLeave(event);
}
// Check if we need to start drag (movement threshold) // Check if we need to start drag (movement threshold)
if (!this.isDragStarted && this.draggedElement) { if (!this.isDragStarted && this.draggedElement) {
@ -290,8 +297,7 @@ export class DragDropManager {
}; };
this.eventBus.emit('drag:end', dragEndPayload); this.eventBus.emit('drag:end', dragEndPayload);
this.cleanupDragState();
this.draggedElement = null;
} else { } else {
// This was just a click - emit click event instead // This was just a click - emit click event instead
@ -477,58 +483,61 @@ export class DragDropManager {
} }
/** /**
* Check for header enter/leave during drag operations * Handle mouse enter on calendar header - simplified using native events
*/ */
private checkHeaderEnterLeave(event: MouseEvent): void { private handleHeaderMouseEnter(event: MouseEvent): void {
// Only handle if we're dragging a timed event (not all-day)
let position: MousePosition = { x: event.clientX, y: event.clientY }; if (!this.isDragStarted || !this.draggedClone) {
const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY); return;
if (!elementAtPosition) return;
const headerElement = elementAtPosition.closest('swp-day-header, swp-calendar-header');
const isCurrentlyInHeader = !!headerElement;
if (isCurrentlyInHeader && !this.draggedClone?.hasAttribute("data-allday")) {
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (targetColumn) {
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate: targetColumn });
// Extract CalendarEvent from the dragged clone
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone!!);
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
targetColumn: targetColumn,
mousePosition: { x: event.clientX, y: event.clientY },
originalElement: this.draggedElement,
draggedClone: this.draggedClone!!,
calendarEvent: calendarEvent
};
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
}
} }
// Detect header leave const position: MousePosition = { x: event.clientX, y: event.clientY };
if (isCurrentlyInHeader && this.draggedClone?.hasAttribute("data-allday")) { const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
console.log('🚪 DragDropManager: Emitting drag:mouseleave-header'); if (targetColumn) {
console.log('🎯 DragDropManager: Mouse entered header', { targetDate: targetColumn });
// Calculate target date using existing method // Extract CalendarEvent from the dragged clone
const targetColumn = ColumnDetectionUtils.getColumnBounds(position); const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
if (!targetColumn) {
console.warn("No column detected, unknown reason");
return;
} const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = { const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
targetDate: targetColumn.date, targetColumn: targetColumn,
mousePosition: { x: event.clientX, y: event.clientY }, mousePosition: position,
originalElement: this.draggedElement, originalElement: this.draggedElement,
draggedClone: this.draggedClone draggedClone: this.draggedClone,
calendarEvent: calendarEvent
}; };
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload); this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
} }
} }
/**
* Handle mouse leave from calendar header - simplified using native events
*/
private handleHeaderMouseLeave(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 left header');
const position: MousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
console.warn("No column detected when leaving header");
return;
}
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
targetDate: targetColumn.date,
mousePosition: position,
originalElement: this.draggedElement,
draggedClone: this.draggedClone
};
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
}
} }