2025-08-27 22:50:13 +02:00
|
|
|
/**
|
2025-11-01 21:07:07 +01:00
|
|
|
* DragDropManager - Advanced drag-and-drop system with smooth animations and event type conversion
|
|
|
|
|
*
|
|
|
|
|
* ARCHITECTURE OVERVIEW:
|
|
|
|
|
* =====================
|
|
|
|
|
* DragDropManager provides a sophisticated drag-and-drop system for calendar events that supports:
|
|
|
|
|
* - Smooth animated dragging with requestAnimationFrame
|
|
|
|
|
* - Automatic event type conversion (timed events ↔ all-day events)
|
|
|
|
|
* - Scroll compensation during edge scrolling
|
|
|
|
|
* - Grid snapping for precise event placement
|
|
|
|
|
* - Column detection and change tracking
|
|
|
|
|
*
|
|
|
|
|
* KEY FEATURES:
|
|
|
|
|
* =============
|
|
|
|
|
* 1. DRAG DETECTION
|
|
|
|
|
* - Movement threshold (5px) to distinguish clicks from drags
|
|
|
|
|
* - Immediate visual feedback with cloned element
|
|
|
|
|
* - Mouse offset tracking for natural drag feel
|
|
|
|
|
*
|
|
|
|
|
* 2. SMOOTH ANIMATION
|
|
|
|
|
* - Uses requestAnimationFrame for 60fps animations
|
|
|
|
|
* - Interpolated movement (30% per frame) for smooth transitions
|
|
|
|
|
* - Continuous drag:move events for real-time updates
|
|
|
|
|
*
|
|
|
|
|
* 3. EVENT TYPE CONVERSION
|
|
|
|
|
* - Timed → All-day: When dragging into calendar header
|
|
|
|
|
* - All-day → Timed: When dragging into day columns
|
|
|
|
|
* - Automatic clone replacement with appropriate element type
|
|
|
|
|
*
|
|
|
|
|
* 4. SCROLL COMPENSATION
|
|
|
|
|
* - Tracks scroll delta during edge-scrolling
|
|
|
|
|
* - Compensates dragged element position during scroll
|
|
|
|
|
* - Prevents visual "jumping" when scrolling while dragging
|
|
|
|
|
*
|
|
|
|
|
* 5. GRID SNAPPING
|
|
|
|
|
* - Snaps to time grid on mouse up
|
|
|
|
|
* - Uses PositionUtils for consistent positioning
|
|
|
|
|
* - Accounts for mouse offset within event
|
|
|
|
|
*
|
|
|
|
|
* STATE MANAGEMENT:
|
|
|
|
|
* =================
|
|
|
|
|
* Mouse Tracking:
|
|
|
|
|
* - mouseDownPosition: Initial click position
|
|
|
|
|
* - currentMousePosition: Latest mouse position
|
|
|
|
|
* - mouseOffset: Click offset within event (for natural dragging)
|
|
|
|
|
*
|
|
|
|
|
* Drag State:
|
|
|
|
|
* - originalElement: Source event being dragged
|
|
|
|
|
* - draggedClone: Animated clone following mouse
|
|
|
|
|
* - currentColumn: Column mouse is currently over
|
|
|
|
|
* - previousColumn: Last column (for detecting changes)
|
|
|
|
|
* - isDragStarted: Whether drag threshold exceeded
|
|
|
|
|
*
|
|
|
|
|
* Scroll State:
|
|
|
|
|
* - scrollDeltaY: Accumulated scroll offset during drag
|
|
|
|
|
* - lastScrollTop: Previous scroll position
|
|
|
|
|
* - isScrollCompensating: Whether edge-scroll is active
|
|
|
|
|
*
|
|
|
|
|
* Animation State:
|
|
|
|
|
* - dragAnimationId: requestAnimationFrame ID
|
|
|
|
|
* - targetY: Desired position for smooth interpolation
|
|
|
|
|
* - currentY: Current interpolated position
|
|
|
|
|
*
|
|
|
|
|
* EVENT FLOW:
|
|
|
|
|
* ===========
|
|
|
|
|
* 1. Mouse Down (handleMouseDown)
|
|
|
|
|
* ├─ Store originalElement and mouse offset
|
|
|
|
|
* └─ Wait for movement
|
|
|
|
|
*
|
|
|
|
|
* 2. Mouse Move (handleMouseMove)
|
|
|
|
|
* ├─ Check movement threshold
|
|
|
|
|
* ├─ Initialize drag if threshold exceeded (initializeDrag)
|
|
|
|
|
* │ ├─ Create clone
|
|
|
|
|
* │ ├─ Emit drag:start
|
|
|
|
|
* │ └─ Start animation loop
|
|
|
|
|
* ├─ Continue drag (continueDrag)
|
|
|
|
|
* │ ├─ Calculate target position with scroll compensation
|
|
|
|
|
* │ └─ Update animation target
|
|
|
|
|
* └─ Detect column changes (detectColumnChange)
|
|
|
|
|
* └─ Emit drag:column-change
|
|
|
|
|
*
|
|
|
|
|
* 3. Animation Loop (animateDrag)
|
|
|
|
|
* ├─ Interpolate currentY toward targetY
|
|
|
|
|
* ├─ Emit drag:move on each frame
|
|
|
|
|
* └─ Schedule next frame until target reached
|
|
|
|
|
*
|
|
|
|
|
* 4. Event Type Conversion
|
|
|
|
|
* ├─ Entering header (handleHeaderMouseEnter)
|
|
|
|
|
* │ ├─ Emit drag:mouseenter-header
|
|
|
|
|
* │ └─ AllDayManager creates all-day clone
|
|
|
|
|
* └─ Entering column (handleColumnMouseEnter)
|
|
|
|
|
* ├─ Emit drag:mouseenter-column
|
|
|
|
|
* └─ EventRenderingService creates timed clone
|
|
|
|
|
*
|
|
|
|
|
* 5. Mouse Up (handleMouseUp)
|
|
|
|
|
* ├─ Stop animation
|
|
|
|
|
* ├─ Snap to grid
|
|
|
|
|
* ├─ Detect drop target (header or column)
|
|
|
|
|
* ├─ Emit drag:end with final position
|
|
|
|
|
* └─ Cleanup drag state
|
|
|
|
|
*
|
|
|
|
|
* SCROLL COMPENSATION SYSTEM:
|
|
|
|
|
* ===========================
|
|
|
|
|
* Problem: When EdgeScrollManager scrolls the grid during drag, the dragged element
|
|
|
|
|
* can appear to "jump" because the mouse position stays the same but the
|
|
|
|
|
* coordinate system (scrollable content) has moved.
|
|
|
|
|
*
|
|
|
|
|
* Solution: Track cumulative scroll delta and add it to mouse position calculations
|
|
|
|
|
*
|
|
|
|
|
* Flow:
|
|
|
|
|
* 1. EdgeScrollManager starts scrolling → emit edgescroll:started
|
|
|
|
|
* 2. DragDropManager sets isScrollCompensating = true
|
|
|
|
|
* 3. On each scroll event:
|
|
|
|
|
* ├─ Calculate scrollDelta = currentScrollTop - lastScrollTop
|
|
|
|
|
* ├─ Accumulate into scrollDeltaY
|
|
|
|
|
* └─ Call continueDrag with adjusted position
|
|
|
|
|
* 4. continueDrag adds scrollDeltaY to mouse Y coordinate
|
|
|
|
|
* 5. On event conversion, reset scrollDeltaY (new clone, new coordinate system)
|
|
|
|
|
*
|
|
|
|
|
* PERFORMANCE OPTIMIZATIONS:
|
|
|
|
|
* ==========================
|
|
|
|
|
* - Uses ColumnDetectionUtils cache for fast column lookups
|
|
|
|
|
* - Single requestAnimationFrame loop (not per-mousemove)
|
|
|
|
|
* - Interpolated animation reduces update frequency
|
|
|
|
|
* - Passive scroll listeners
|
|
|
|
|
* - Event delegation for header/column detection
|
|
|
|
|
*
|
|
|
|
|
* USAGE:
|
|
|
|
|
* ======
|
|
|
|
|
* const dragDropManager = new DragDropManager(eventBus, positionUtils);
|
|
|
|
|
* // Automatically attaches event listeners and manages drag lifecycle
|
|
|
|
|
* // Other managers listen to drag:start, drag:move, drag:end, etc.
|
2025-08-27 22:50:13 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { IEventBus } from '../types/CalendarTypes';
|
2025-09-13 00:39:56 +02:00
|
|
|
import { PositionUtils } from '../utils/PositionUtils';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { IColumnBounds, 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 {
|
2025-11-03 21:30:50 +01:00
|
|
|
IDragStartEventPayload,
|
|
|
|
|
IDragMoveEventPayload,
|
|
|
|
|
IDragEndEventPayload,
|
|
|
|
|
IDragMouseEnterHeaderEventPayload,
|
|
|
|
|
IDragMouseLeaveHeaderEventPayload,
|
|
|
|
|
IDragMouseEnterColumnEventPayload,
|
|
|
|
|
IDragColumnChangeEventPayload
|
2025-09-21 15:48:13 +02:00
|
|
|
} from '../types/EventTypes';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { IMousePosition } from '../types/DragDropTypes';
|
2025-10-13 23:05:03 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
2025-09-03 19:05:03 +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-11-03 21:30:50 +01:00
|
|
|
private mouseDownPosition: IMousePosition = { x: 0, y: 0 };
|
|
|
|
|
private currentMousePosition: IMousePosition = { x: 0, y: 0 };
|
|
|
|
|
private mouseOffset: IMousePosition = { x: 0, y: 0 };
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
// Drag state
|
2025-10-12 22:00:02 +02:00
|
|
|
private originalElement!: HTMLElement | null;
|
2025-09-26 22:53:49 +02:00
|
|
|
private draggedClone!: HTMLElement | null;
|
2025-11-03 21:30:50 +01:00
|
|
|
private currentColumn: IColumnBounds | null = null;
|
|
|
|
|
private previousColumn: IColumnBounds | null = null;
|
2025-09-09 14:35:21 +02:00
|
|
|
private isDragStarted = false;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
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-10-13 17:20:17 +02:00
|
|
|
// Scroll compensation
|
|
|
|
|
private scrollableContent: HTMLElement | null = null;
|
2025-10-13 21:22:33 +02:00
|
|
|
private scrollDeltaY = 0; // Current scroll delta to apply in continueDrag
|
2025-10-13 21:02:09 +02:00
|
|
|
private lastScrollTop = 0; // Last scroll position for delta calculation
|
2025-10-13 17:20:17 +02:00
|
|
|
private isScrollCompensating = false; // Track if scroll compensation is active
|
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;
|
2025-11-03 21:30:50 +01:00
|
|
|
private targetColumn: IColumnBounds | null = null;
|
2025-10-30 23:47:30 +01:00
|
|
|
private positionUtils: PositionUtils;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-30 23:47:30 +01:00
|
|
|
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
|
2025-08-27 22:50:13 +02:00
|
|
|
this.eventBus = eventBus;
|
2025-10-30 23:47:30 +01:00
|
|
|
this.positionUtils = positionUtils;
|
2025-10-13 17:20:17 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
this.init();
|
|
|
|
|
}
|
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-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', () => {
|
2025-10-12 22:00:02 +02:00
|
|
|
if (this.originalElement && this.isDragStarted) {
|
2025-09-22 17:51:24 +02:00
|
|
|
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-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-10-13 17:20:17 +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-10-13 17:20:17 +02:00
|
|
|
this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => {
|
|
|
|
|
this.handleGridRendered(event as CustomEvent);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Listen to edge-scroll events to control scroll compensation
|
|
|
|
|
this.eventBus.on('edgescroll:started', () => {
|
|
|
|
|
this.isScrollCompensating = true;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 21:02:09 +02:00
|
|
|
// Gem nuværende scroll position for delta beregning
|
|
|
|
|
if (this.scrollableContent) {
|
|
|
|
|
this.lastScrollTop = this.scrollableContent.scrollTop;
|
|
|
|
|
}
|
2025-10-13 17:20:17 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.eventBus.on('edgescroll:stopped', () => {
|
|
|
|
|
this.isScrollCompensating = false;
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-13 22:17:17 +02:00
|
|
|
// Reset scrollDeltaY when event converts (new clone created)
|
|
|
|
|
this.eventBus.on('drag:mouseenter-header', () => {
|
|
|
|
|
this.scrollDeltaY = 0;
|
|
|
|
|
this.lastScrollTop = 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.eventBus.on('drag:mouseenter-column', () => {
|
|
|
|
|
this.scrollDeltaY = 0;
|
|
|
|
|
this.lastScrollTop = 0;
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-13 17:20:17 +02:00
|
|
|
}
|
|
|
|
|
private handleGridRendered(event: CustomEvent) {
|
|
|
|
|
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
|
|
|
|
this.scrollableContent!.addEventListener('scroll', this.handleScroll.bind(this), { passive: true });
|
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-10-12 22:00:02 +02:00
|
|
|
//this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
|
|
|
|
//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;
|
2025-10-12 22:00:02 +02:00
|
|
|
if (target.closest('swp-resize-handle')) return;
|
|
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
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-08-27 22:50:13 +02:00
|
|
|
if (eventElement) {
|
2025-10-12 22:00:02 +02:00
|
|
|
|
2025-10-08 00:58:38 +02:00
|
|
|
// Normal drag - prepare for potential dragging
|
2025-10-12 22:00:02 +02:00
|
|
|
this.originalElement = eventElement;
|
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-10-12 22:00:02 +02:00
|
|
|
this.mouseDownPosition = { x: event.clientX, y: event.clientY };
|
|
|
|
|
|
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 handleMouseMove(event: MouseEvent): void {
|
2025-09-30 00:13:52 +02:00
|
|
|
|
|
|
|
|
if (event.buttons === 1) {
|
2025-10-13 20:24:19 +02:00
|
|
|
// Always update mouse position from event
|
|
|
|
|
this.currentMousePosition = { 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-10-12 22:00:02 +02:00
|
|
|
if (!this.isDragStarted && this.originalElement) {
|
2025-10-13 20:24:19 +02:00
|
|
|
if (!this.initializeDrag(this.currentMousePosition)) {
|
2025-10-08 23:29:56 +02:00
|
|
|
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-13 21:02:09 +02:00
|
|
|
// Continue drag if started (også under scroll - accumulatedScrollDelta kompenserer)
|
2025-10-12 22:00:02 +02:00
|
|
|
if (this.isDragStarted && this.originalElement && this.draggedClone) {
|
2025-10-13 20:24:19 +02:00
|
|
|
this.continueDrag(this.currentMousePosition);
|
|
|
|
|
this.detectColumnChange(this.currentMousePosition);
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private initializeDrag(currentPosition: IMousePosition): boolean {
|
2025-10-12 22:00:02 +02:00
|
|
|
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
|
|
|
|
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
2025-10-08 23:29:56 +02:00
|
|
|
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
|
|
|
|
|
|
|
|
if (totalMovement < this.dragThreshold) {
|
|
|
|
|
return false; // Not enough movement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start drag
|
|
|
|
|
this.isDragStarted = true;
|
|
|
|
|
|
2025-10-13 17:20:17 +02:00
|
|
|
|
|
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
// Set high z-index on event-group if exists, otherwise on event itself
|
2025-10-12 22:00:02 +02:00
|
|
|
const eventGroup = this.originalElement!.closest<HTMLElement>('swp-event-group');
|
2025-10-08 23:29:56 +02:00
|
|
|
if (eventGroup) {
|
|
|
|
|
eventGroup.style.zIndex = '9999';
|
|
|
|
|
} else {
|
2025-10-12 22:00:02 +02:00
|
|
|
this.originalElement!.style.zIndex = '9999';
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
const originalElement = this.originalElement as BaseSwpEventElement;
|
|
|
|
|
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
2025-10-08 23:29:56 +02:00
|
|
|
this.draggedClone = originalElement.createClone();
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragStartPayload: IDragStartEventPayload = {
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement!,
|
2025-10-13 17:20:17 +02:00
|
|
|
draggedClone: this.draggedClone,
|
2025-10-12 22:00:02 +02:00
|
|
|
mousePosition: this.mouseDownPosition,
|
2025-10-08 23:29:56 +02:00
|
|
|
mouseOffset: this.mouseOffset,
|
2025-10-12 22:00:02 +02:00
|
|
|
columnBounds: this.currentColumn
|
2025-10-08 23:29:56 +02:00
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:start', dragStartPayload);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 09:54:20 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
private continueDrag(currentPosition: IMousePosition): void {
|
2025-10-13 17:20:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (column) {
|
|
|
|
|
// Calculate raw Y position relative to column (accounting for mouse offset)
|
|
|
|
|
const columnRect = column.boundingClientRect;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 21:22:33 +02:00
|
|
|
// Beregn position fra mus + scroll delta kompensation
|
|
|
|
|
const adjustedMouseY = currentPosition.y + this.scrollDeltaY;
|
|
|
|
|
const eventTopY = adjustedMouseY - columnRect.top - this.mouseOffset.y;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
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-08-27 22:50:13 +02:00
|
|
|
}
|
2025-10-08 23:29:56 +02:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Detect column change and emit event
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private detectColumnChange(currentPosition: IMousePosition): void {
|
2025-10-08 23:29:56 +02:00
|
|
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
|
|
|
|
if (newColumn == null) return;
|
|
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
if (newColumn.index !== this.currentColumn?.index) {
|
|
|
|
|
this.previousColumn = this.currentColumn;
|
|
|
|
|
this.currentColumn = newColumn;
|
2025-10-08 23:29:56 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragColumnChangePayload: IDragColumnChangeEventPayload = {
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement!,
|
2025-10-08 23:29:56 +02:00
|
|
|
draggedClone: this.draggedClone!,
|
2025-10-12 22:00:02 +02:00
|
|
|
previousColumn: this.previousColumn,
|
2025-10-08 23:29:56 +02:00
|
|
|
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 {
|
2025-10-08 18:30:03 +02:00
|
|
|
this.stopDragAnimation();
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
if (this.originalElement) {
|
2025-09-21 15:48:13 +02:00
|
|
|
|
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) {
|
2025-11-03 21:30:50 +01:00
|
|
|
const mousePosition: IMousePosition = { 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);
|
|
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
if (!column) return;
|
2025-10-08 19:01:35 +02:00
|
|
|
|
|
|
|
|
// Get current position and snap it to grid
|
|
|
|
|
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-11-03 21:30:50 +01:00
|
|
|
const dragEndPayload: IDragEndEventPayload = {
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement,
|
2025-09-28 13:25:09 +02:00
|
|
|
draggedClone: this.draggedClone,
|
2025-09-21 15:48:13 +02:00
|
|
|
mousePosition,
|
2025-10-12 22:00:02 +02:00
|
|
|
sourceColumn: this.previousColumn!!,
|
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
|
|
|
};
|
2025-10-12 22:00:02 +02:00
|
|
|
|
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-10-12 22:00:02 +02:00
|
|
|
clickedElement: this.originalElement,
|
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) {
|
|
|
|
|
allClones.forEach(clone => clone.remove());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cancel drag operation when mouse leaves grid container
|
2025-10-13 22:41:20 +02:00
|
|
|
* Animates clone back to original position before cleanup
|
2025-09-22 17:51:24 +02:00
|
|
|
*/
|
|
|
|
|
private cancelDrag(): void {
|
2025-10-13 22:41:20 +02:00
|
|
|
if (!this.originalElement || !this.draggedClone) return;
|
2025-09-22 17:51:24 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
// Get current clone position
|
|
|
|
|
const cloneRect = this.draggedClone.getBoundingClientRect();
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
// Get original element position
|
|
|
|
|
const originalRect = this.originalElement.getBoundingClientRect();
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
// Calculate distance to animate
|
|
|
|
|
const deltaX = originalRect.left - cloneRect.left;
|
|
|
|
|
const deltaY = originalRect.top - cloneRect.top;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
// Add transition for smooth animation
|
|
|
|
|
this.draggedClone.style.transition = 'transform 300ms ease-out';
|
|
|
|
|
this.draggedClone.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
// Wait for animation to complete, then cleanup
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.cleanupAllClones();
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
if (this.originalElement) {
|
|
|
|
|
this.originalElement.style.opacity = '';
|
|
|
|
|
this.originalElement.style.cursor = '';
|
|
|
|
|
}
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
this.eventBus.emit('drag:cancelled', {
|
|
|
|
|
originalElement: this.originalElement,
|
|
|
|
|
reason: 'mouse-left-grid'
|
|
|
|
|
});
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 22:41:20 +02:00
|
|
|
this.cleanupDragState();
|
|
|
|
|
this.stopDragAnimation();
|
|
|
|
|
}, 300);
|
2025-09-21 21:30:51 +02:00
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
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-11-03 21:30:50 +01:00
|
|
|
private calculateSnapPosition(mouseY: number, column: IColumnBounds): 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
|
2025-10-30 23:47:30 +01:00
|
|
|
const snappedY = this.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:38:15 +02:00
|
|
|
* Emits drag:move events with current draggedClone reference on each frame
|
2025-10-08 18:30:03 +02:00
|
|
|
*/
|
2025-10-12 22:00:02 +02:00
|
|
|
private animateDrag(): void { //TODO: this can be optimized... WAIT !!!
|
|
|
|
|
|
2025-10-08 18:30:03 +02:00
|
|
|
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;
|
2025-10-11 01:38:15 +02:00
|
|
|
|
|
|
|
|
// Emit drag:move event with current draggedClone reference
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragMovePayload: IDragMoveEventPayload = {
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement!,
|
2025-10-11 01:38:15 +02:00
|
|
|
draggedClone: this.draggedClone, // Always uses current reference
|
2025-10-12 23:17:22 +02:00
|
|
|
mousePosition: this.currentMousePosition, // Use current mouse position!
|
2025-10-11 01:38:15 +02:00
|
|
|
snappedY: this.currentY,
|
|
|
|
|
columnBounds: this.targetColumn,
|
|
|
|
|
mouseOffset: this.mouseOffset
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:move', dragMovePayload);
|
|
|
|
|
|
2025-10-08 18:30:03 +02:00
|
|
|
this.dragAnimationId = requestAnimationFrame(() => this.animateDrag());
|
|
|
|
|
} else {
|
|
|
|
|
// Close enough - snap to target
|
|
|
|
|
this.currentY = this.targetY;
|
2025-10-11 01:38:15 +02:00
|
|
|
|
|
|
|
|
// Emit final position
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragMovePayload: IDragMoveEventPayload = {
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement!,
|
2025-10-11 01:38:15 +02:00
|
|
|
draggedClone: this.draggedClone,
|
2025-10-12 23:17:22 +02:00
|
|
|
mousePosition: this.currentMousePosition, // Use current mouse position!
|
2025-10-11 01:38:15 +02:00
|
|
|
snappedY: this.currentY,
|
|
|
|
|
columnBounds: this.targetColumn,
|
|
|
|
|
mouseOffset: this.mouseOffset
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:move', dragMovePayload);
|
|
|
|
|
|
2025-10-08 18:30:03 +02:00
|
|
|
this.dragAnimationId = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 17:20:17 +02:00
|
|
|
/**
|
2025-10-13 21:22:33 +02:00
|
|
|
* Handle scroll during drag - update scrollDeltaY and call continueDrag
|
2025-10-13 17:20:17 +02:00
|
|
|
*/
|
|
|
|
|
private handleScroll(): void {
|
|
|
|
|
if (!this.isDragStarted || !this.draggedClone || !this.scrollableContent || !this.isScrollCompensating) return;
|
|
|
|
|
|
|
|
|
|
const currentScrollTop = this.scrollableContent.scrollTop;
|
2025-10-13 21:02:09 +02:00
|
|
|
const scrollDelta = currentScrollTop - this.lastScrollTop;
|
2025-10-13 23:05:03 +02:00
|
|
|
|
2025-10-13 21:22:33 +02:00
|
|
|
// Gem scroll delta for continueDrag
|
|
|
|
|
this.scrollDeltaY += scrollDelta;
|
2025-10-13 21:02:09 +02:00
|
|
|
this.lastScrollTop = currentScrollTop;
|
2025-10-13 17:20:17 +02:00
|
|
|
|
2025-10-13 21:22:33 +02:00
|
|
|
// Kald continueDrag med nuværende mus position
|
|
|
|
|
this.continueDrag(this.currentMousePosition);
|
2025-10-13 17:20:17 +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-10-12 22:00:02 +02:00
|
|
|
this.previousColumn = null;
|
|
|
|
|
this.originalElement = null;
|
2025-09-26 22:53:49 +02:00
|
|
|
this.draggedClone = null;
|
2025-10-12 22:00:02 +02:00
|
|
|
this.currentColumn = null;
|
2025-09-09 14:35:21 +02:00
|
|
|
this.isDragStarted = false;
|
2025-10-13 21:22:33 +02:00
|
|
|
this.scrollDeltaY = 0;
|
2025-10-13 21:02:09 +02:00
|
|
|
this.lastScrollTop = 0;
|
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-11-03 21:30:50 +01:00
|
|
|
private detectDropTarget(position: IMousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
2025-09-28 13:25:09 +02:00
|
|
|
|
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-11-03 21:30:50 +01:00
|
|
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
2025-10-04 21:12:52 +02:00
|
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
if (targetColumn) {
|
|
|
|
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
2025-10-04 16:20:09 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragMouseEnterPayload: IDragMouseEnterHeaderEventPayload = {
|
2025-10-04 21:12:52 +02:00
|
|
|
targetColumn: targetColumn,
|
|
|
|
|
mousePosition: position,
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement,
|
2025-10-04 21:12:52 +02:00
|
|
|
draggedClone: this.draggedClone,
|
2025-10-04 23:10:09 +02:00
|
|
|
calendarEvent: calendarEvent,
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
2025-10-10 16:41:48 +02:00
|
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
|
|
|
|
|
|
|
|
|
if (!targetColumn) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate snapped Y position
|
|
|
|
|
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
// Extract ICalendarEvent from the dragged clone
|
2025-10-10 16:41:48 +02:00
|
|
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragMouseEnterPayload: IDragMouseEnterColumnEventPayload = {
|
2025-10-10 16:41:48 +02:00
|
|
|
targetColumn: targetColumn,
|
|
|
|
|
mousePosition: position,
|
|
|
|
|
snappedY: snappedY,
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement,
|
2025-10-10 16:41:48 +02:00
|
|
|
draggedClone: this.draggedClone,
|
|
|
|
|
calendarEvent: calendarEvent,
|
|
|
|
|
replaceClone: (newClone: HTMLElement) => {
|
|
|
|
|
this.draggedClone = newClone;
|
2025-10-11 01:30:41 +02:00
|
|
|
this.dragAnimationId === null;
|
|
|
|
|
this.stopDragAnimation();
|
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-11-03 21:30:50 +01:00
|
|
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
2025-10-04 21:12:52 +02:00
|
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-04 21:12:52 +02:00
|
|
|
if (!targetColumn) {
|
|
|
|
|
return;
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|
2025-10-04 21:12:52 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragMouseLeavePayload: IDragMouseLeaveHeaderEventPayload = {
|
2025-10-04 21:12:52 +02:00
|
|
|
targetDate: targetColumn.date,
|
|
|
|
|
mousePosition: position,
|
2025-10-12 22:00:02 +02:00
|
|
|
originalElement: this.originalElement,
|
2025-10-04 21:12:52 +02:00
|
|
|
draggedClone: this.draggedClone
|
|
|
|
|
};
|
|
|
|
|
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
|
2025-09-21 15:48:13 +02:00
|
|
|
}
|
|
|
|
|
}
|