2025-08-27 22:50:13 +02:00
/ * *
2025-09-03 19:05:03 +02:00
* DragDropManager - Optimized drag and drop with consolidated position calculations
* Reduces redundant DOM queries and improves performance through caching
2025-08-27 22:50:13 +02:00
* /
import { IEventBus } from '../types/CalendarTypes' ;
2025-09-03 20:04:47 +02:00
import { calendarConfig } from '../core/CalendarConfig' ;
2025-09-13 00:39:56 +02:00
import { PositionUtils } from '../utils/PositionUtils' ;
2025-09-28 13:25:09 +02:00
import { ColumnBounds , ColumnDetectionUtils } from '../utils/ColumnDetectionUtils' ;
2025-09-26 22:53:49 +02:00
import { SwpEventElement } from '../elements/SwpEventElement' ;
2025-09-21 15:48:13 +02:00
import {
DragStartEventPayload ,
DragMoveEventPayload ,
DragEndEventPayload ,
DragMouseEnterHeaderEventPayload ,
2025-09-26 22:11:57 +02:00
DragMouseLeaveHeaderEventPayload ,
DragColumnChangeEventPayload
2025-09-21 15:48:13 +02:00
} from '../types/EventTypes' ;
2025-09-28 13:25:09 +02:00
import { MousePosition } from '../types/DragDropTypes' ;
2025-09-03 19:05:03 +02:00
interface CachedElements {
scrollContainer : HTMLElement | null ;
}
2025-09-28 13:25:09 +02:00
2025-08-27 22:50:13 +02:00
2025-09-19 00:20:30 +02:00
2025-08-27 22:50:13 +02:00
export class DragDropManager {
private eventBus : IEventBus ;
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
// Mouse tracking with optimized state
2025-09-28 13:25:09 +02:00
private lastMousePosition : MousePosition = { x : 0 , y : 0 } ;
private lastLoggedPosition : MousePosition = { x : 0 , y : 0 } ;
2025-08-27 22:50:13 +02:00
private currentMouseY = 0 ;
2025-09-28 13:25:09 +02:00
private mouseOffset : MousePosition = { x : 0 , y : 0 } ;
private initialMousePosition : MousePosition = { x : 0 , y : 0 } ;
private lastColumn : ColumnBounds | null = null ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// Drag state
2025-09-21 16:03:34 +02:00
private draggedElement ! : HTMLElement | null ;
2025-09-26 22:53:49 +02:00
private draggedClone ! : HTMLElement | null ;
2025-09-28 13:25:09 +02:00
private currentColumnBounds : ColumnBounds | null = null ;
2025-09-09 14:35:21 +02:00
private isDragStarted = false ;
2025-09-21 15:48:13 +02:00
// Header tracking state
private isInHeader = false ;
2025-09-09 14:35:21 +02:00
// Movement threshold to distinguish click from drag
private readonly dragThreshold = 5 ; // pixels
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
private scrollContainer ! : HTMLElement | null ;
2025-09-03 19:05:03 +02:00
// Cached DOM elements for performance
2025-09-28 13:25:09 +02:00
2025-09-21 15:48:13 +02:00
2025-09-22 17:51:24 +02:00
2025-08-27 22:50:13 +02:00
// Auto-scroll properties
private autoScrollAnimationId : number | null = null ;
2025-09-03 19:05:03 +02:00
private readonly scrollSpeed = 10 ; // pixels per frame
private readonly scrollThreshold = 30 ; // pixels from edge
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// Snap configuration
private snapIntervalMinutes = 15 ; // Default 15 minutes
2025-09-03 20:48:23 +02:00
private hourHeightPx : number ; // Will be set from config
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
// Event listener references for proper cleanup
private boundHandlers = {
mouseMove : this.handleMouseMove.bind ( this ) ,
mouseDown : this.handleMouseDown.bind ( this ) ,
mouseUp : this.handleMouseUp.bind ( this )
} ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
private get snapDistancePx ( ) : number {
return ( this . snapIntervalMinutes / 60 ) * this . hourHeightPx ;
}
2025-09-21 15:48:13 +02:00
2025-09-03 20:04:47 +02:00
constructor ( eventBus : IEventBus ) {
2025-08-27 22:50:13 +02:00
this . eventBus = eventBus ;
// Get config values
2025-09-03 20:04:47 +02:00
const gridSettings = calendarConfig . getGridSettings ( ) ;
2025-08-27 22:50:13 +02:00
this . hourHeightPx = gridSettings . hourHeight ;
2025-09-03 20:48:23 +02:00
this . snapIntervalMinutes = gridSettings . snapInterval ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
this . init ( ) ;
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Configure snap interval
* /
public setSnapInterval ( minutes : number ) : void {
this . snapIntervalMinutes = minutes ;
}
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
/ * *
* Initialize with optimized event listener setup
* /
2025-08-27 22:50:13 +02:00
private init ( ) : void {
2025-09-03 19:05:03 +02:00
// Use bound handlers for proper cleanup
document . body . addEventListener ( 'mousemove' , this . boundHandlers . mouseMove ) ;
document . body . addEventListener ( 'mousedown' , this . boundHandlers . mouseDown ) ;
document . body . addEventListener ( 'mouseup' , this . boundHandlers . mouseUp ) ;
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
this . scrollContainer = document . querySelector ( 'swp-scrollable-content' ) as HTMLElement ;
2025-09-22 17:51:24 +02:00
const calendarContainer = document . querySelector ( 'swp-calendar-container' ) ;
if ( calendarContainer ) {
calendarContainer . addEventListener ( 'mouseleave' , ( ) = > {
if ( this . draggedElement && this . isDragStarted ) {
this . cancelDrag ( ) ;
}
} ) ;
}
2025-09-19 00:20:30 +02:00
// Initialize column bounds cache
2025-09-26 22:11:57 +02:00
ColumnDetectionUtils . updateColumnBoundsCache ( ) ;
2025-09-21 15:48:13 +02:00
2025-09-19 00:20:30 +02:00
// Listen to resize events to update cache
window . addEventListener ( 'resize' , ( ) = > {
2025-09-26 22:11:57 +02:00
ColumnDetectionUtils . updateColumnBoundsCache ( ) ;
2025-09-19 00:20:30 +02:00
} ) ;
2025-09-21 15:48:13 +02:00
2025-09-19 00:20:30 +02:00
// Listen to navigation events to update cache
this . eventBus . on ( 'navigation:completed' , ( ) = > {
2025-09-26 22:11:57 +02:00
ColumnDetectionUtils . updateColumnBoundsCache ( ) ;
2025-09-19 00:20:30 +02:00
} ) ;
2025-09-10 22:07:40 +02:00
2025-08-27 22:50:13 +02:00
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
private handleMouseDown ( event : MouseEvent ) : void {
2025-09-27 15:01:22 +02:00
2025-09-28 13:25:09 +02:00
// Clean up drag state first
this . cleanupDragState ( ) ;
2025-08-27 22:50:13 +02:00
this . lastMousePosition = { x : event.clientX , y : event.clientY } ;
this . lastLoggedPosition = { x : event.clientX , y : event.clientY } ;
2025-09-09 14:35:21 +02:00
this . initialMousePosition = { x : event.clientX , y : event.clientY } ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// Check if mousedown is on an event
const target = event . target as HTMLElement ;
let eventElement = target ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
while ( eventElement && eventElement . tagName !== 'SWP-EVENTS-LAYER' ) {
2025-09-21 16:03:34 +02:00
if ( eventElement . tagName === 'SWP-EVENT' ) {
2025-08-27 22:50:13 +02:00
break ;
}
eventElement = eventElement . parentElement as HTMLElement ;
if ( ! eventElement ) return ;
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// If we reached SWP-EVENTS-LAYER without finding an event, return
if ( ! eventElement || eventElement . tagName === 'SWP-EVENTS-LAYER' ) {
return ;
}
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Found an event - prepare for potential dragging
2025-08-27 22:50:13 +02:00
if ( eventElement ) {
2025-09-21 15:48:13 +02:00
this . draggedElement = eventElement ;
2025-09-28 13:25:09 +02:00
this . lastColumn = ColumnDetectionUtils . getColumnBounds ( this . lastMousePosition )
2025-08-27 22:50:13 +02:00
// Calculate mouse offset within event
const eventRect = eventElement . getBoundingClientRect ( ) ;
this . mouseOffset = {
x : event.clientX - eventRect . left ,
y : event.clientY - eventRect . top
} ;
}
}
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
/ * *
* Optimized mouse move handler with consolidated position calculations
* /
2025-08-27 22:50:13 +02:00
private handleMouseMove ( event : MouseEvent ) : void {
this . currentMouseY = event . clientY ;
2025-09-21 15:48:13 +02:00
this . lastMousePosition = { x : event.clientX , y : event.clientY } ;
// Check for header enter/leave during drag
if ( this . draggedElement ) {
this . checkHeaderEnterLeave ( event ) ;
}
if ( event . buttons === 1 && this . draggedElement ) {
2025-09-28 13:25:09 +02:00
const currentPosition : MousePosition = { x : event.clientX , y : event.clientY } ; //TODO: Is this really needed? why not just use event.clientX + Y directly
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Check if we need to start drag (movement threshold)
if ( ! this . isDragStarted ) {
const deltaX = Math . abs ( currentPosition . x - this . initialMousePosition . x ) ;
const deltaY = Math . abs ( currentPosition . y - this . initialMousePosition . y ) ;
const totalMovement = Math . sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
if ( totalMovement >= this . dragThreshold ) {
// Start drag - emit drag:start event
this . isDragStarted = true ;
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
// Detect current column
this . currentColumnBounds = ColumnDetectionUtils . getColumnBounds ( currentPosition ) ;
// Create SwpEventElement from existing DOM element and clone it
const originalSwpEvent = SwpEventElement . fromExistingElement ( this . draggedElement ) ;
const clonedSwpEvent = originalSwpEvent . createClone ( ) ;
// Get the cloned DOM element
this . draggedClone = clonedSwpEvent . getElement ( ) ;
2025-09-26 22:53:49 +02:00
2025-09-21 15:48:13 +02:00
const dragStartPayload : DragStartEventPayload = {
draggedElement : this.draggedElement ,
2025-09-26 22:53:49 +02:00
draggedClone : this.draggedClone ,
2025-09-09 14:35:21 +02:00
mousePosition : this.initialMousePosition ,
mouseOffset : this.mouseOffset ,
2025-09-28 13:25:09 +02:00
columnBounds : this.currentColumnBounds
2025-09-21 15:48:13 +02:00
} ;
this . eventBus . emit ( 'drag:start' , dragStartPayload ) ;
2025-09-09 14:35:21 +02:00
} else {
// Not enough movement yet - don't start drag
return ;
}
2025-08-27 22:50:13 +02:00
}
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Continue with normal drag behavior only if drag has started
if ( this . isDragStarted ) {
const deltaY = Math . abs ( currentPosition . y - this . lastLoggedPosition . y ) ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Check for snap interval vertical movement (normal drag behavior)
if ( deltaY >= this . snapDistancePx ) {
this . lastLoggedPosition = currentPosition ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Consolidated position calculations with snapping for normal drag
const positionData = this . calculateDragPosition ( currentPosition ) ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Emit drag move event with snapped position (normal behavior)
2025-09-21 15:48:13 +02:00
const dragMovePayload : DragMoveEventPayload = {
draggedElement : this.draggedElement ,
2025-09-09 14:35:21 +02:00
mousePosition : currentPosition ,
snappedY : positionData.snappedY ,
2025-09-28 13:25:09 +02:00
columnBounds : positionData.column ,
2025-09-09 14:35:21 +02:00
mouseOffset : this.mouseOffset
2025-09-21 15:48:13 +02:00
} ;
this . eventBus . emit ( 'drag:move' , dragMovePayload ) ;
2025-09-09 14:35:21 +02:00
}
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Check for auto-scroll
2025-09-28 13:25:09 +02:00
this . checkAutoScroll ( currentPosition ) ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Check for column change using cached data
2025-09-28 13:25:09 +02:00
const newColumn = ColumnDetectionUtils . getColumnBounds ( currentPosition ) ;
if ( newColumn && newColumn !== this . currentColumnBounds ) {
const previousColumn = this . currentColumnBounds ;
this . currentColumnBounds = newColumn ;
2025-09-21 15:48:13 +02:00
2025-09-26 22:11:57 +02:00
const dragColumnChangePayload : DragColumnChangeEventPayload = {
2025-09-21 15:48:13 +02:00
draggedElement : this.draggedElement ,
2025-09-26 22:53:49 +02:00
draggedClone : this.draggedClone ,
2025-09-09 14:35:21 +02:00
previousColumn ,
newColumn ,
mousePosition : currentPosition
2025-09-26 22:11:57 +02:00
} ;
this . eventBus . emit ( 'drag:column-change' , dragColumnChangePayload ) ;
2025-09-09 14:35:21 +02:00
}
2025-08-27 22:50:13 +02:00
}
}
}
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
/ * *
* Optimized mouse up handler with consolidated cleanup
* /
2025-08-27 22:50:13 +02:00
private handleMouseUp ( event : MouseEvent ) : void {
this . stopAutoScroll ( ) ;
2025-09-21 15:48:13 +02:00
if ( this . draggedElement ) {
2025-09-09 14:35:21 +02:00
// Only emit drag:end if drag was actually started
2025-09-28 13:25:09 +02:00
if ( this . isDragStarted ) {
const mousePosition : MousePosition = { x : event.clientX , y : event.clientY } ;
2025-09-21 15:48:13 +02:00
2025-09-09 14:35:21 +02:00
// Use consolidated position calculation
2025-09-21 15:48:13 +02:00
const positionData = this . calculateDragPosition ( mousePosition ) ;
2025-09-20 09:40:56 +02:00
// Detect drop target (swp-day-column or swp-day-header)
2025-09-21 15:48:13 +02:00
const dropTarget = this . detectDropTarget ( mousePosition ) ;
2025-09-16 23:09:10 +02:00
console . log ( '🎯 DragDropManager: Emitting drag:end' , {
2025-09-27 15:01:22 +02:00
draggedElement : this.draggedElement.dataset.eventId ,
2025-09-16 23:09:10 +02:00
finalColumn : positionData.column ,
finalY : positionData.snappedY ,
2025-09-20 09:40:56 +02:00
dropTarget : dropTarget ,
2025-09-28 13:25:09 +02:00
isDragStarted : this.isDragStarted
2025-09-16 23:09:10 +02:00
} ) ;
2025-09-21 15:48:13 +02:00
const dragEndPayload : DragEndEventPayload = {
2025-09-27 15:01:22 +02:00
draggedElement : this.draggedElement ,
2025-09-28 13:25:09 +02:00
draggedClone : this.draggedClone ,
2025-09-21 15:48:13 +02:00
mousePosition ,
finalPosition : positionData ,
2025-09-20 09:40:56 +02:00
target : dropTarget
2025-09-21 15:48:13 +02:00
} ;
this . eventBus . emit ( 'drag:end' , dragEndPayload ) ;
2025-09-29 20:50:52 +02:00
2025-09-27 15:01:22 +02:00
this . draggedElement . remove ( ) ; // TODO: this should be changed into a subscriber which only after a succesful placement is fired, not just mouseup as this can remove elements that are not placed.
2025-09-21 21:30:51 +02:00
2025-09-09 14:35:21 +02:00
} else {
// This was just a click - emit click event instead
this . eventBus . emit ( 'event:click' , {
2025-09-27 15:01:22 +02:00
draggedElement : this.draggedElement ,
2025-09-09 14:35:21 +02:00
mousePosition : { x : event.clientX , y : event.clientY }
} ) ;
}
2025-08-27 22:50:13 +02:00
}
}
2025-09-21 21:30:51 +02:00
// Add a cleanup method that finds and removes ALL clones
private cleanupAllClones ( ) : void {
// Remove clones from all possible locations
const allClones = document . querySelectorAll ( '[data-event-id^="clone"]' ) ;
2025-09-28 13:25:09 +02:00
2025-09-22 17:51:24 +02:00
if ( allClones . length > 0 ) {
console . log ( ` 🧹 DragDropManager: Removing ${ allClones . length } clone(s) ` ) ;
allClones . forEach ( clone = > clone . remove ( ) ) ;
}
}
/ * *
* Cancel drag operation when mouse leaves grid container
* /
private cancelDrag ( ) : void {
if ( ! this . draggedElement ) return ;
console . log ( '🚫 DragDropManager: Cancelling drag - mouse left grid container' ) ;
const draggedElement = this . draggedElement ;
// 1. Remove all clones
this . cleanupAllClones ( ) ;
// 2. Restore original element
if ( draggedElement ) {
draggedElement . style . opacity = '' ;
draggedElement . style . cursor = '' ;
}
// 3. Emit cancellation event
this . eventBus . emit ( 'drag:cancelled' , {
draggedElement : draggedElement ,
reason : 'mouse-left-grid'
} ) ;
// 4. Clean up state
this . cleanupDragState ( ) ;
this . stopAutoScroll ( ) ;
2025-09-21 21:30:51 +02:00
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
/ * *
2025-09-13 00:39:56 +02:00
* Consolidated position calculation method using PositionUtils
2025-08-27 22:50:13 +02:00
* /
2025-09-28 13:25:09 +02:00
private calculateDragPosition ( mousePosition : MousePosition ) : { column : ColumnBounds | null ; snappedY : number } {
let column = ColumnDetectionUtils . getColumnBounds ( mousePosition ) ;
let snappedY = 0 ;
if ( column ) {
snappedY = this . calculateSnapPosition ( mousePosition . y , column ) ;
return { column , snappedY } ;
}
2025-09-03 19:05:03 +02:00
2025-09-21 15:48:13 +02:00
return { column , snappedY } ;
2025-09-03 20:48:23 +02:00
}
/ * *
2025-09-13 00:39:56 +02:00
* Optimized snap position calculation using PositionUtils
2025-09-03 19:05:03 +02:00
* /
2025-09-28 13:25:09 +02:00
private calculateSnapPosition ( mouseY : number , column : ColumnBounds ) : number {
const snappedY = PositionUtils . getPositionFromCoordinate ( mouseY , column ) ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
return Math . max ( 0 , snappedY ) ;
}
2025-09-21 15:48:13 +02:00
2025-09-19 00:20:30 +02:00
2025-08-27 22:50:13 +02:00
/ * *
2025-09-03 19:05:03 +02:00
* Optimized auto - scroll check with cached container
2025-08-27 22:50:13 +02:00
* /
2025-09-28 13:25:09 +02:00
private checkAutoScroll ( mousePosition : MousePosition ) : void {
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
if ( this . scrollContainer == null )
return ;
const containerRect = this . scrollContainer . getBoundingClientRect ( ) ;
const mouseY = mousePosition . clientY ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// Calculate distances from edges
2025-09-28 13:25:09 +02:00
const distanceFromTop = mousePosition . y - containerRect . top ;
const distanceFromBottom = containerRect . bottom - mousePosition . y ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
// Check if we need to scroll
if ( distanceFromTop <= this . scrollThreshold && distanceFromTop > 0 ) {
2025-09-28 13:25:09 +02:00
this . startAutoScroll ( 'up' , mousePosition ) ;
2025-08-27 22:50:13 +02:00
} else if ( distanceFromBottom <= this . scrollThreshold && distanceFromBottom > 0 ) {
2025-09-28 13:25:09 +02:00
this . startAutoScroll ( 'down' , mousePosition ) ;
2025-08-27 22:50:13 +02:00
} else {
this . stopAutoScroll ( ) ;
}
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
/ * *
2025-09-03 19:05:03 +02:00
* Optimized auto - scroll with cached container reference
2025-08-27 22:50:13 +02:00
* /
2025-09-28 13:25:09 +02:00
private startAutoScroll ( direction : 'up' | 'down' , event : MousePosition ) : void {
2025-08-27 22:50:13 +02:00
if ( this . autoScrollAnimationId !== null ) return ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
const scroll = ( ) = > {
2025-09-28 13:25:09 +02:00
if ( ! this . scrollContainer || ! this . draggedElement ) {
2025-08-27 22:50:13 +02:00
this . stopAutoScroll ( ) ;
return ;
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
const scrollAmount = direction === 'up' ? - this . scrollSpeed : this.scrollSpeed ;
2025-09-28 13:25:09 +02:00
this . scrollContainer . scrollTop += scrollAmount ;
2025-09-21 15:48:13 +02:00
2025-09-03 20:13:56 +02:00
// Emit updated position during scroll - adjust for scroll movement
2025-09-21 15:48:13 +02:00
if ( this . draggedElement ) {
2025-09-03 20:13:56 +02:00
// During autoscroll, we need to calculate position relative to the scrolled content
// The mouse hasn't moved, but the content has scrolled
2025-09-28 13:25:09 +02:00
const columnElement = ColumnDetectionUtils . getColumnBounds ( event ) ;
2025-09-03 20:13:56 +02:00
if ( columnElement ) {
2025-09-28 13:25:09 +02:00
const columnRect = columnElement . boundingClientRect ;
2025-09-03 20:48:23 +02:00
// Calculate free position relative to column, accounting for scroll movement (no snapping during scroll)
2025-09-03 20:13:56 +02:00
const relativeY = this . currentMouseY - columnRect . top - this . mouseOffset . y ;
2025-09-03 20:48:23 +02:00
const freeY = Math . max ( 0 , relativeY ) ;
2025-09-21 15:48:13 +02:00
2025-09-03 20:13:56 +02:00
this . eventBus . emit ( 'drag:auto-scroll' , {
2025-09-21 15:48:13 +02:00
draggedElement : this.draggedElement ,
2025-09-03 20:48:23 +02:00
snappedY : freeY , // Actually free position during scroll
2025-09-28 13:25:09 +02:00
scrollTop : this.scrollContainer.scrollTop
2025-09-03 20:13:56 +02:00
} ) ;
}
2025-08-27 22:50:13 +02:00
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
this . autoScrollAnimationId = requestAnimationFrame ( scroll ) ;
} ;
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
this . autoScrollAnimationId = requestAnimationFrame ( scroll ) ;
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Stop auto - scroll animation
* /
private stopAutoScroll ( ) : void {
if ( this . autoScrollAnimationId !== null ) {
cancelAnimationFrame ( this . autoScrollAnimationId ) ;
this . autoScrollAnimationId = null ;
}
}
2025-09-21 15:48:13 +02:00
2025-08-27 22:50:13 +02:00
/ * *
2025-09-03 19:05:03 +02:00
* Clean up drag state
* /
private cleanupDragState ( ) : void {
2025-09-21 15:48:13 +02:00
this . draggedElement = null ;
2025-09-26 22:53:49 +02:00
this . draggedClone = null ;
2025-09-09 14:35:21 +02:00
this . isDragStarted = false ;
2025-09-21 15:48:13 +02:00
this . isInHeader = false ;
2025-09-03 19:05:03 +02:00
}
2025-09-20 09:40:56 +02:00
/ * *
* Detect drop target - whether dropped in swp - day - column or swp - day - header
* /
2025-09-28 13:25:09 +02:00
private detectDropTarget ( position : MousePosition ) : 'swp-day-column' | 'swp-day-header' | null {
2025-09-20 09:40:56 +02:00
// Traverse up the DOM tree to find the target container
2025-09-27 15:01:22 +02:00
let currentElement = this . draggedClone ;
2025-09-20 09:40:56 +02:00
while ( currentElement && currentElement !== document . body ) {
2025-09-27 15:01:22 +02:00
if ( currentElement . tagName === 'SWP-ALLDAY-CONTAINER' ) {
2025-09-20 09:40:56 +02:00
return 'swp-day-header' ;
}
if ( currentElement . tagName === 'SWP-DAY-COLUMN' ) {
return 'swp-day-column' ;
}
currentElement = currentElement . parentElement as HTMLElement ;
}
return null ;
}
2025-09-21 15:48:13 +02:00
/ * *
* Check for header enter / leave during drag operations
* /
private checkHeaderEnterLeave ( event : MouseEvent ) : void {
2025-09-28 13:25:09 +02:00
let position : MousePosition = { x : event.clientX , y : event.clientY } ;
2025-09-21 15:48:13 +02:00
const elementAtPosition = document . elementFromPoint ( event . clientX , event . clientY ) ;
if ( ! elementAtPosition ) return ;
// Check if we're in a header area
const headerElement = elementAtPosition . closest ( 'swp-day-header, swp-calendar-header' ) ;
const isCurrentlyInHeader = ! ! headerElement ;
// Detect header enter
if ( ! this . isInHeader && isCurrentlyInHeader ) {
this . isInHeader = true ;
// Calculate target date using existing method
2025-09-28 13:25:09 +02:00
const targetColumn = ColumnDetectionUtils . getColumnBounds ( position ) ;
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
if ( targetColumn ) {
console . log ( '🎯 DragDropManager: Emitting drag:mouseenter-header' , { targetDate : targetColumn } ) ;
2025-09-21 15:48:13 +02:00
// Find clone element (if it exists)
const eventId = this . draggedElement ? . dataset . eventId ;
const cloneElement = document . querySelector ( ` [data-event-id="clone- ${ eventId } "] ` ) as HTMLElement ;
const dragMouseEnterPayload : DragMouseEnterHeaderEventPayload = {
2025-09-28 13:25:09 +02:00
targetColumn : targetColumn ,
2025-09-21 15:48:13 +02:00
mousePosition : { x : event.clientX , y : event.clientY } ,
originalElement : this.draggedElement ,
cloneElement : cloneElement
} ;
this . eventBus . emit ( 'drag:mouseenter-header' , dragMouseEnterPayload ) ;
}
}
// Detect header leave
if ( this . isInHeader && ! isCurrentlyInHeader ) {
this . isInHeader = false ;
console . log ( '🚪 DragDropManager: Emitting drag:mouseleave-header' ) ;
// Calculate target date using existing method
2025-09-28 13:25:09 +02:00
const targetColumn = ColumnDetectionUtils . getColumnBounds ( position ) ;
if ( ! targetColumn ) {
console . warn ( "No column detected, unknown reason" ) ;
return ;
2025-09-21 15:48:13 +02:00
2025-09-28 13:25:09 +02:00
}
2025-09-21 15:48:13 +02:00
// Find clone element (if it exists)
const eventId = this . draggedElement ? . dataset . eventId ;
const cloneElement = document . querySelector ( ` [data-event-id="clone- ${ eventId } "] ` ) as HTMLElement ;
const dragMouseLeavePayload : DragMouseLeaveHeaderEventPayload = {
2025-09-28 13:25:09 +02:00
targetDate : targetColumn.date ,
2025-09-21 15:48:13 +02:00
mousePosition : { x : event.clientX , y : event.clientY } ,
originalElement : this.draggedElement ,
cloneElement : cloneElement
} ;
this . eventBus . emit ( 'drag:mouseleave-header' , dragMouseLeavePayload ) ;
}
}
2025-09-03 19:05:03 +02:00
/ * *
* Clean up all resources and event listeners
2025-08-27 22:50:13 +02:00
* /
public destroy ( ) : void {
this . stopAutoScroll ( ) ;
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
// Remove event listeners using bound references
document . body . removeEventListener ( 'mousemove' , this . boundHandlers . mouseMove ) ;
document . body . removeEventListener ( 'mousedown' , this . boundHandlers . mouseDown ) ;
document . body . removeEventListener ( 'mouseup' , this . boundHandlers . mouseUp ) ;
2025-09-21 15:48:13 +02:00
2025-09-03 19:05:03 +02:00
// Clean up drag state
this . cleanupDragState ( ) ;
2025-08-27 22:50:13 +02:00
}
2025-09-21 15:48:13 +02:00
}