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-10-04 23:10:09 +02:00
|
|
|
import { SwpEventElement, BaseSwpEventElement } 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,
|
2025-10-10 16:41:48 +02:00
|
|
|
DragMouseEnterColumnEventPayload,
|
2025-09-26 22:11:57 +02:00
|
|
|
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-10-08 22:18:06 +02:00
|
|
|
private initialColumnBounds: ColumnBounds | null = null; // Track source column
|
2025-09-09 14:35:21 +02:00
|
|
|
private isDragStarted = false;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-08 00:58:38 +02:00
|
|
|
// Hover state
|
|
|
|
|
private isHoverTrackingActive = false;
|
|
|
|
|
private currentHoveredEvent: HTMLElement | null = null;
|
|
|
|
|
|
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-10-08 18:30:03 +02:00
|
|
|
// Smooth drag animation
|
|
|
|
|
private dragAnimationId: number | null = null;
|
|
|
|
|
private targetY = 0;
|
|
|
|
|
private currentY = 0;
|
|
|
|
|
private targetColumn: ColumnBounds | null = null;
|
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-10-01 22:38:15 +02:00
|
|
|
// Add event listeners
|
|
|
|
|
document.body.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
|
|
|
|
document.body.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
|
|
|
|
document.body.addEventListener('mouseup', this.handleMouseUp.bind(this));
|
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');
|
2025-10-08 19:35:29 +02:00
|
|
|
|
2025-09-22 17:51:24 +02:00
|
|
|
if (calendarContainer) {
|
|
|
|
|
calendarContainer.addEventListener('mouseleave', () => {
|
|
|
|
|
if (this.draggedElement && this.isDragStarted) {
|
|
|
|
|
this.cancelDrag();
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-10-04 21:12:52 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2025-10-10 16:41:48 +02:00
|
|
|
} else if (target.closest('swp-day-column')) {
|
|
|
|
|
this.handleColumnMouseEnter(e as MouseEvent);
|
2025-10-08 00:58:38 +02:00
|
|
|
} else if (target.closest('swp-event')) {
|
|
|
|
|
// Entered an event - activate hover tracking and set color
|
|
|
|
|
const eventElement = target.closest<HTMLElement>('swp-event');
|
|
|
|
|
const mouseEvent = e as MouseEvent;
|
|
|
|
|
|
|
|
|
|
// Only handle hover if mouse button is up
|
|
|
|
|
if (eventElement && !this.isDragStarted && mouseEvent.buttons === 0) {
|
|
|
|
|
// Clear any previous hover first
|
|
|
|
|
if (this.currentHoveredEvent && this.currentHoveredEvent !== eventElement) {
|
2025-10-08 18:15:00 +02:00
|
|
|
this.currentHoveredEvent.classList.remove('hover');
|
2025-10-08 00:58:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.isHoverTrackingActive = true;
|
|
|
|
|
this.currentHoveredEvent = eventElement;
|
2025-10-08 18:15:00 +02:00
|
|
|
eventElement.classList.add('hover');
|
2025-10-08 00:58:38 +02:00
|
|
|
}
|
2025-10-04 21:12:52 +02:00
|
|
|
}
|
|
|
|
|
}, 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);
|
|
|
|
|
}
|
2025-10-08 00:58:38 +02:00
|
|
|
// Don't handle swp-event mouseleave here - let mousemove handle it
|
2025-10-04 21:12:52 +02:00
|
|
|
}, true); // Use capture phase
|
2025-09-22 17:51:24 +02:00
|
|
|
}
|
|
|
|
|
|
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-09-30 00:13:52 +02:00
|
|
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
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-10-04 21:12:52 +02:00
|
|
|
while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') {
|
|
|
|
|
if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-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-10-08 21:43:02 +02:00
|
|
|
// Found an event - check if clicking on resize handle first
|
2025-08-27 22:50:13 +02:00
|
|
|
if (eventElement) {
|
2025-10-08 21:43:02 +02:00
|
|
|
// Check if click is on resize handle
|
|
|
|
|
if (target.closest('swp-resize-handle')) {
|
|
|
|
|
return; // Exit early - this is a resize operation, let ResizeHandleManager handle it
|
2025-10-08 00:58:38 +02:00
|
|
|
}
|
|
|
|
|
// Normal drag - prepare for potential dragging
|
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 };
|
|
|
|
|
|
2025-10-08 00:58:38 +02:00
|
|
|
// Check for event hover (coordinate-based) - only when mouse button is up
|
|
|
|
|
if (this.isHoverTrackingActive && event.buttons === 0) {
|
|
|
|
|
this.checkEventHover(event);
|
|
|
|
|
}
|
2025-09-30 00:13:52 +02:00
|
|
|
|
|
|
|
|
if (event.buttons === 1) {
|
2025-10-04 21:12:52 +02:00
|
|
|
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
2025-09-30 00:13:52 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
// Try to initialize drag if not started
|
2025-09-30 00:13:52 +02:00
|
|
|
if (!this.isDragStarted && this.draggedElement) {
|
2025-10-08 23:29:56 +02:00
|
|
|
if (!this.tryInitializeDrag(currentPosition)) {
|
|
|
|
|
return; // Not enough movement yet
|
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-10-08 23:29:56 +02:00
|
|
|
// Continue drag if started
|
2025-09-30 00:13:52 +02:00
|
|
|
if (this.isDragStarted && this.draggedElement && this.draggedClone) {
|
2025-10-11 01:30:41 +02:00
|
|
|
console.log("Continue drag if started", this.draggedClone);
|
2025-10-08 23:29:56 +02:00
|
|
|
this.continueDrag(currentPosition);
|
|
|
|
|
this.detectAndEmitColumnChange(currentPosition);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-02 23:11:26 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
/**
|
|
|
|
|
* Try to initialize drag based on movement threshold
|
|
|
|
|
* Returns true if drag was initialized, false if not enough movement
|
|
|
|
|
*/
|
|
|
|
|
private tryInitializeDrag(currentPosition: MousePosition): boolean {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (totalMovement < this.dragThreshold) {
|
|
|
|
|
return false; // Not enough movement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start drag
|
|
|
|
|
this.isDragStarted = true;
|
|
|
|
|
|
|
|
|
|
// Set high z-index on event-group if exists, otherwise on event itself
|
|
|
|
|
const eventGroup = this.draggedElement!.closest<HTMLElement>('swp-event-group');
|
|
|
|
|
if (eventGroup) {
|
|
|
|
|
eventGroup.style.zIndex = '9999';
|
|
|
|
|
} else {
|
|
|
|
|
this.draggedElement!.style.zIndex = '9999';
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
// Detect current column and save as initial source column
|
|
|
|
|
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
|
|
|
|
this.initialColumnBounds = this.currentColumnBounds;
|
|
|
|
|
|
|
|
|
|
// Cast to BaseSwpEventElement and create clone
|
|
|
|
|
const originalElement = this.draggedElement as BaseSwpEventElement;
|
|
|
|
|
this.draggedClone = originalElement.createClone();
|
|
|
|
|
|
|
|
|
|
const dragStartPayload: DragStartEventPayload = {
|
|
|
|
|
draggedElement: this.draggedElement!,
|
|
|
|
|
draggedClone: this.draggedClone,
|
|
|
|
|
mousePosition: this.initialMousePosition,
|
|
|
|
|
mouseOffset: this.mouseOffset,
|
|
|
|
|
columnBounds: this.currentColumnBounds
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:start', dragStartPayload);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Continue drag movement - update position and auto-scroll
|
|
|
|
|
*/
|
|
|
|
|
private continueDrag(currentPosition: MousePosition): void {
|
2025-10-11 01:30:41 +02:00
|
|
|
if (!this.draggedClone!.hasAttribute("data-allday")) {
|
2025-10-08 23:29:56 +02:00
|
|
|
// Calculate raw position from mouse (no snapping)
|
|
|
|
|
const column = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
2025-10-11 01:30:41 +02:00
|
|
|
console.log("continueDrag");
|
2025-10-08 23:29:56 +02:00
|
|
|
|
|
|
|
|
if (column) {
|
|
|
|
|
// Calculate raw Y position relative to column (accounting for mouse offset)
|
|
|
|
|
const columnRect = column.boundingClientRect;
|
|
|
|
|
const eventTopY = currentPosition.y - columnRect.top - this.mouseOffset.y;
|
|
|
|
|
this.targetY = Math.max(0, eventTopY);
|
|
|
|
|
this.targetColumn = column;
|
|
|
|
|
|
|
|
|
|
// Start animation loop if not already running
|
|
|
|
|
if (this.dragAnimationId === null) {
|
|
|
|
|
this.currentY = parseFloat(this.draggedClone!.style.top) || 0;
|
|
|
|
|
this.animateDrag();
|
2025-09-09 14:35:21 +02:00
|
|
|
}
|
2025-10-11 01:30:41 +02:00
|
|
|
|
|
|
|
|
// Emit drag:move event with current draggedClone reference
|
|
|
|
|
if (this.draggedClone) {
|
|
|
|
|
const dragMovePayload: DragMoveEventPayload = {
|
|
|
|
|
draggedElement: this.draggedElement!,
|
|
|
|
|
draggedClone: this.draggedClone,
|
|
|
|
|
mousePosition: currentPosition,
|
|
|
|
|
snappedY: this.currentY,
|
|
|
|
|
columnBounds: column,
|
|
|
|
|
mouseOffset: this.mouseOffset
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:move', dragMovePayload);
|
|
|
|
|
}
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-10-08 23:29:56 +02:00
|
|
|
|
|
|
|
|
// Check for auto-scroll
|
|
|
|
|
this.checkAutoScroll(currentPosition);
|
|
|
|
|
}
|
2025-10-11 01:30:41 +02:00
|
|
|
else
|
|
|
|
|
console.log("hasAttribute(data-allday)");
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Detect column change and emit event
|
|
|
|
|
*/
|
|
|
|
|
private detectAndEmitColumnChange(currentPosition: MousePosition): void {
|
|
|
|
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
|
|
|
|
if (newColumn == null) return;
|
|
|
|
|
|
|
|
|
|
if (newColumn.index !== this.currentColumnBounds?.index) {
|
|
|
|
|
const previousColumn = this.currentColumnBounds;
|
|
|
|
|
this.currentColumnBounds = newColumn;
|
|
|
|
|
|
|
|
|
|
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
|
|
|
|
originalElement: this.draggedElement!,
|
|
|
|
|
draggedClone: this.draggedClone!,
|
|
|
|
|
previousColumn,
|
|
|
|
|
newColumn,
|
|
|
|
|
mousePosition: currentPosition
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:column-change', dragColumnChangePayload);
|
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-10-08 18:30:03 +02:00
|
|
|
this.stopDragAnimation();
|
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-10-08 19:01:35 +02:00
|
|
|
// Snap to grid on mouse up (like ResizeHandleManager)
|
|
|
|
|
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
|
|
|
|
|
|
|
|
|
if (!column) {
|
|
|
|
|
console.warn('No column detected on mouseUp');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current position and snap it to grid
|
|
|
|
|
const currentY = parseFloat(this.draggedClone?.style.top || '0');
|
|
|
|
|
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
|
|
|
|
|
|
|
|
|
// Update clone to snapped position immediately
|
|
|
|
|
if (this.draggedClone) {
|
|
|
|
|
this.draggedClone.style.top = `${snappedY}px`;
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
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-10-01 18:41:28 +02:00
|
|
|
if (!dropTarget)
|
2025-09-30 00:13:52 +02:00
|
|
|
throw "dropTarget is null";
|
|
|
|
|
|
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-10-08 19:01:35 +02:00
|
|
|
finalColumn: column,
|
|
|
|
|
finalY: 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-30 00:13:52 +02:00
|
|
|
originalElement: this.draggedElement,
|
2025-09-28 13:25:09 +02:00
|
|
|
draggedClone: this.draggedClone,
|
2025-10-08 22:18:06 +02:00
|
|
|
sourceColumn: this.initialColumnBounds, // Where drag started
|
2025-09-21 15:48:13 +02:00
|
|
|
mousePosition,
|
2025-10-08 22:18:06 +02:00
|
|
|
finalPosition: { column, snappedY }, // Where drag ended
|
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-10-02 23:11:26 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
this.cleanupDragState();
|
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-10-08 18:30:03 +02:00
|
|
|
this.stopDragAnimation();
|
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 {
|
2025-10-03 19:09:44 +02:00
|
|
|
// Calculate where the event top would be (accounting for mouse offset)
|
|
|
|
|
const eventTopY = mouseY - this.mouseOffset.y;
|
2025-10-04 21:12:52 +02:00
|
|
|
|
2025-10-03 19:09:44 +02:00
|
|
|
// Snap the event top position, not the mouse position
|
|
|
|
|
const snappedY = PositionUtils.getPositionFromCoordinate(eventTopY, 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-10-08 18:30:03 +02:00
|
|
|
/**
|
|
|
|
|
* Smooth drag animation using requestAnimationFrame
|
2025-10-11 01:30:41 +02:00
|
|
|
* Only interpolates currentY - events are emitted from continueDrag()
|
2025-10-08 18:30:03 +02:00
|
|
|
*/
|
|
|
|
|
private animateDrag(): void {
|
|
|
|
|
if (!this.isDragStarted || !this.draggedClone || !this.targetColumn) {
|
|
|
|
|
this.dragAnimationId = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Smooth interpolation towards target
|
|
|
|
|
const diff = this.targetY - this.currentY;
|
|
|
|
|
const step = diff * 0.3; // 30% of distance per frame
|
|
|
|
|
|
|
|
|
|
// Update if difference is significant
|
|
|
|
|
if (Math.abs(diff) > 0.5) {
|
|
|
|
|
this.currentY += step;
|
|
|
|
|
this.dragAnimationId = requestAnimationFrame(() => this.animateDrag());
|
|
|
|
|
} else {
|
|
|
|
|
// Close enough - snap to target
|
|
|
|
|
this.currentY = this.targetY;
|
|
|
|
|
this.dragAnimationId = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-10-08 18:30:03 +02:00
|
|
|
/**
|
|
|
|
|
* Stop drag animation
|
|
|
|
|
*/
|
|
|
|
|
private stopDragAnimation(): void {
|
|
|
|
|
if (this.dragAnimationId !== null) {
|
|
|
|
|
cancelAnimationFrame(this.dragAnimationId);
|
|
|
|
|
this.dragAnimationId = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-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
|
|
|
/**
|
2025-10-04 21:12:52 +02:00
|
|
|
* Handle mouse enter on calendar header - simplified using native events
|
2025-09-21 15:48:13 +02:00
|
|
|
*/
|
2025-10-04 21:12:52 +02:00
|
|
|
private handleHeaderMouseEnter(event: MouseEvent): void {
|
|
|
|
|
// Only handle if we're dragging a timed event (not all-day)
|
|
|
|
|
if (!this.isDragStarted || !this.draggedClone) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
|
|
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
if (targetColumn) {
|
|
|
|
|
console.log('🎯 DragDropManager: Mouse entered header', { targetDate: targetColumn });
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
// Extract CalendarEvent from the dragged clone
|
|
|
|
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
2025-10-04 16:20:09 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
|
|
|
|
targetColumn: targetColumn,
|
|
|
|
|
mousePosition: position,
|
|
|
|
|
originalElement: this.draggedElement,
|
|
|
|
|
draggedClone: this.draggedClone,
|
2025-10-04 23:10:09 +02:00
|
|
|
calendarEvent: calendarEvent,
|
|
|
|
|
// Delegate pattern - allows AllDayManager to replace the clone
|
|
|
|
|
replaceClone: (newClone: HTMLElement) => {
|
|
|
|
|
this.draggedClone = newClone;
|
2025-10-11 01:30:41 +02:00
|
|
|
this.dragAnimationId === null;
|
2025-10-04 23:10:09 +02:00
|
|
|
}
|
2025-10-04 21:12:52 +02:00
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|
2025-10-04 21:12:52 +02:00
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-10 16:41:48 +02:00
|
|
|
/**
|
|
|
|
|
* 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;
|
2025-10-11 01:30:41 +02:00
|
|
|
this.dragAnimationId === null;
|
|
|
|
|
this.stopDragAnimation();
|
|
|
|
|
console.log("replacing clone with", newClone)
|
2025-10-10 16:41:48 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
console.log('🚪 DragDropManager: Mouse left header');
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
|
|
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
if (!targetColumn) {
|
|
|
|
|
console.warn("No column detected when leaving header");
|
|
|
|
|
return;
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|
2025-10-04 21:12:52 +02:00
|
|
|
|
|
|
|
|
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
|
|
|
|
targetDate: targetColumn.date,
|
|
|
|
|
mousePosition: position,
|
|
|
|
|
originalElement: this.draggedElement,
|
|
|
|
|
draggedClone: this.draggedClone
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|
2025-10-08 00:58:38 +02:00
|
|
|
|
|
|
|
|
private checkEventHover(event: MouseEvent): void {
|
|
|
|
|
// Use currentHoveredEvent to check if mouse is still within bounds
|
|
|
|
|
if (!this.currentHoveredEvent) return;
|
|
|
|
|
|
|
|
|
|
const rect = this.currentHoveredEvent.getBoundingClientRect();
|
|
|
|
|
const mouseX = event.clientX;
|
|
|
|
|
const mouseY = event.clientY;
|
|
|
|
|
|
|
|
|
|
// Check if mouse is still within the current hovered event
|
|
|
|
|
const isStillInside = mouseX >= rect.left && mouseX <= rect.right &&
|
2025-10-08 19:35:29 +02:00
|
|
|
mouseY >= rect.top && mouseY <= rect.bottom;
|
2025-10-08 00:58:38 +02:00
|
|
|
|
|
|
|
|
// If mouse left the event
|
|
|
|
|
if (!isStillInside) {
|
|
|
|
|
// Only disable tracking and clear if mouse is NOT pressed (allow resize to work)
|
|
|
|
|
if (event.buttons === 0) {
|
|
|
|
|
this.isHoverTrackingActive = false;
|
|
|
|
|
this.clearEventHover();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private clearEventHover(): void {
|
|
|
|
|
if (this.currentHoveredEvent) {
|
2025-10-08 18:15:00 +02:00
|
|
|
this.currentHoveredEvent.classList.remove('hover');
|
2025-10-08 00:58:38 +02:00
|
|
|
this.currentHoveredEvent = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|