222 lines
7.7 KiB
TypeScript
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;
|
|
}
|