From 125cd678a31518ef07ff95a882bbb295d757a2f6 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Sat, 4 Oct 2025 21:12:52 +0200 Subject: [PATCH] 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. --- src/managers/DragDropManager.ts | 127 +++++++++++++++++--------------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 4eff03e..a626851 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -93,12 +93,28 @@ export class DragDropManager { this.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement; const calendarContainer = document.querySelector('swp-calendar-container'); + if (calendarContainer) { calendarContainer.addEventListener('mouseleave', () => { if (this.draggedElement && this.isDragStarted) { 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 @@ -129,18 +145,14 @@ export class DragDropManager { const target = event.target as HTMLElement; let eventElement = target; - while (eventElement && eventElement.tagName !== 'SWP-EVENTS-LAYER') { - if (eventElement.tagName === 'SWP-EVENT') { + while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') { + if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') { break; } eventElement = eventElement.parentElement as HTMLElement; 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 if (eventElement) { @@ -165,12 +177,7 @@ export class DragDropManager { 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 - - // Check for header enter/leave during drag - if (this.draggedClone) { - this.checkHeaderEnterLeave(event); - } + const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; // Check if we need to start drag (movement threshold) if (!this.isDragStarted && this.draggedElement) { @@ -290,8 +297,7 @@ export class DragDropManager { }; this.eventBus.emit('drag:end', dragEndPayload); - - this.draggedElement = null; + this.cleanupDragState(); } else { // This was just a click - emit click event instead @@ -363,7 +369,7 @@ export class DragDropManager { private calculateSnapPosition(mouseY: number, column: ColumnBounds): number { // Calculate where the event top would be (accounting for mouse offset) const eventTopY = mouseY - this.mouseOffset.y; - + // Snap the event top position, not the mouse position const snappedY = PositionUtils.getPositionFromCoordinate(eventTopY, column); @@ -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 { - - let position: MousePosition = { x: event.clientX, y: event.clientY }; - const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY); - 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); - } + private handleHeaderMouseEnter(event: MouseEvent): void { + // Only handle if we're dragging a timed event (not all-day) + if (!this.isDragStarted || !this.draggedClone) { + return; } - // Detect header leave - if (isCurrentlyInHeader && this.draggedClone?.hasAttribute("data-allday")) { + const position: MousePosition = { x: event.clientX, y: event.clientY }; + 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 - const targetColumn = ColumnDetectionUtils.getColumnBounds(position); - if (!targetColumn) { - console.warn("No column detected, unknown reason"); - return; + // Extract CalendarEvent from the dragged clone + const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone); + + const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent); - } - - const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = { - targetDate: targetColumn.date, - mousePosition: { x: event.clientX, y: event.clientY }, + const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = { + targetColumn: targetColumn, + mousePosition: position, 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); + } }