Calendar/wwwroot/js/managers/DragDropManager.d.ts
2026-02-03 00:02:25 +01:00

222 lines
7.7 KiB
TypeScript

/**
* 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.
*/
import { IEventBus } from '../types/CalendarTypes';
import { PositionUtils } from '../utils/PositionUtils';
export declare class DragDropManager {
private eventBus;
private mouseDownPosition;
private currentMousePosition;
private mouseOffset;
private originalElement;
private draggedClone;
private currentColumn;
private previousColumn;
private originalSourceColumn;
private isDragStarted;
private readonly dragThreshold;
private scrollableContent;
private scrollDeltaY;
private lastScrollTop;
private isScrollCompensating;
private dragAnimationId;
private targetY;
private currentY;
private targetColumn;
private positionUtils;
constructor(eventBus: IEventBus, positionUtils: PositionUtils);
/**
* Initialize with optimized event listener setup
*/
private init;
private handleGridRendered;
private handleMouseDown;
private handleMouseMove;
/**
* Try to initialize drag based on movement threshold
* Returns true if drag was initialized, false if not enough movement
*/
private initializeDrag;
private continueDrag;
/**
* Detect column change and emit event
*/
private detectColumnChange;
/**
* Optimized mouse up handler with consolidated cleanup
*/
private handleMouseUp;
private cleanupAllClones;
/**
* Cancel drag operation when mouse leaves grid container
* Animates clone back to original position before cleanup
*/
private cancelDrag;
/**
* Optimized snap position calculation using PositionUtils
*/
private calculateSnapPosition;
/**
* Smooth drag animation using requestAnimationFrame
* Emits drag:move events with current draggedClone reference on each frame
*/
private animateDrag;
/**
* Handle scroll during drag - update scrollDeltaY and call continueDrag
*/
private handleScroll;
/**
* Stop drag animation
*/
private stopDragAnimation;
/**
* Clean up drag state
*/
private cleanupDragState;
/**
* Detect drop target - whether dropped in swp-day-column or swp-day-header
*/
private detectDropTarget;
/**
* Handle mouse enter on calendar header - simplified using native events
*/
private handleHeaderMouseEnter;
/**
* Handle mouse enter on day column - for converting all-day to timed events
*/
private handleColumnMouseEnter;
/**
* Handle mouse leave from calendar header - simplified using native events
*/
private handleHeaderMouseLeave;
}