diff --git a/src/factories/ManagerFactory.ts b/src/factories/ManagerFactory.ts index 8e08158..34fb15c 100644 --- a/src/factories/ManagerFactory.ts +++ b/src/factories/ManagerFactory.ts @@ -10,6 +10,7 @@ import { DragDropManager } from '../managers/DragDropManager'; import { AllDayManager } from '../managers/AllDayManager'; import { ResizeHandleManager } from '../managers/ResizeHandleManager'; import { EdgeScrollManager } from '../managers/EdgeScrollManager'; +import { DragHoverManager } from '../managers/DragHoverManager'; import { CalendarManagers } from '../types/ManagerTypes'; /** @@ -43,6 +44,7 @@ export class ManagerFactory { const allDayManager = new AllDayManager(eventManager); const resizeHandleManager = new ResizeHandleManager(); const edgeScrollManager = new EdgeScrollManager(eventBus); + const dragHoverManager = new DragHoverManager(eventBus); // CalendarManager depends on all other managers const calendarManager = new CalendarManager( @@ -65,7 +67,8 @@ export class ManagerFactory { dragDropManager, allDayManager, resizeHandleManager, - edgeScrollManager + edgeScrollManager, + dragHoverManager }; } diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 2a53d34..138ad59 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -18,7 +18,7 @@ import { DragColumnChangeEventPayload } from '../types/EventTypes'; import { MousePosition } from '../types/DragDropTypes'; -import { CoreEvents } from '../constants/CoreEvents'; +import { CoreEvents } from '../constants/CoreEvents'; export class DragDropManager { private eventBus: IEventBus; @@ -35,10 +35,6 @@ export class DragDropManager { private previousColumn: ColumnBounds | null = null; private isDragStarted = false; - // Hover state - private isHoverTrackingActive = false; - private currentHoveredEvent: HTMLElement | null = null; - // Movement threshold to distinguish click from drag private readonly dragThreshold = 5; // pixels @@ -47,8 +43,6 @@ export class DragDropManager { private scrollDeltaY = 0; // Current scroll delta to apply in continueDrag private lastScrollTop = 0; // Last scroll position for delta calculation private isScrollCompensating = false; // Track if scroll compensation is active - private hasScrolledDuringDrag = false; // Track if we have scrolled during this drag operation - private scrollListener: ((e: Event) => void) | null = null; // Smooth drag animation private dragAnimationId: number | null = null; @@ -58,10 +52,6 @@ export class DragDropManager { constructor(eventBus: IEventBus) { this.eventBus = eventBus; - // Get config values - const gridSettings = calendarConfig.getGridSettings(); - - this.init(); } @@ -91,8 +81,6 @@ export class DragDropManager { this.handleHeaderMouseEnter(e as MouseEvent); } else if (target.closest('swp-day-column')) { this.handleColumnMouseEnter(e as MouseEvent); - } else if (target.closest('swp-event')) { - this.handleEventMouseEnter(e as MouseEvent); } }, true); // Use capture phase @@ -128,13 +116,12 @@ export class DragDropManager { // Listen to edge-scroll events to control scroll compensation this.eventBus.on('edgescroll:started', () => { this.isScrollCompensating = true; - this.hasScrolledDuringDrag = true; - + // Gem nuværende scroll position for delta beregning if (this.scrollableContent) { this.lastScrollTop = this.scrollableContent.scrollTop; } - + console.log('🎬 DragDropManager: Edge-scroll started'); }); @@ -199,18 +186,7 @@ export class DragDropManager { } } - /** - * Optimized mouse move handler with consolidated position calculations - */ private handleMouseMove(event: MouseEvent): void { - - //this.currentMouseY = event.clientY; - // this.lastMousePosition = { x: event.clientX, y: event.clientY }; - - // Check for event hover (coordinate-based) - only when mouse button is up - if (this.isHoverTrackingActive && event.buttons === 0) { - this.checkEventHover(event); - } if (event.buttons === 1) { // Always update mouse position from event @@ -283,11 +259,11 @@ export class DragDropManager { if (column) { // Calculate raw Y position relative to column (accounting for mouse offset) const columnRect = column.boundingClientRect; - + // Beregn position fra mus + scroll delta kompensation const adjustedMouseY = currentPosition.y + this.scrollDeltaY; const eventTopY = adjustedMouseY - columnRect.top - this.mouseOffset.y; - + this.targetY = Math.max(0, eventTopY); this.targetColumn = column; @@ -400,32 +376,32 @@ export class DragDropManager { // Get current clone position const cloneRect = this.draggedClone.getBoundingClientRect(); - + // Get original element position const originalRect = this.originalElement.getBoundingClientRect(); - + // Calculate distance to animate const deltaX = originalRect.left - cloneRect.left; const deltaY = originalRect.top - cloneRect.top; - + // Add transition for smooth animation this.draggedClone.style.transition = 'transform 300ms ease-out'; this.draggedClone.style.transform = `translate(${deltaX}px, ${deltaY}px)`; - + // Wait for animation to complete, then cleanup setTimeout(() => { this.cleanupAllClones(); - + if (this.originalElement) { this.originalElement.style.opacity = ''; this.originalElement.style.cursor = ''; } - + this.eventBus.emit('drag:cancelled', { originalElement: this.originalElement, reason: 'mouse-left-grid' }); - + this.cleanupDragState(); this.stopDragAnimation(); }, 300); @@ -502,7 +478,7 @@ export class DragDropManager { const currentScrollTop = this.scrollableContent.scrollTop; const scrollDelta = currentScrollTop - this.lastScrollTop; - + // Gem scroll delta for continueDrag this.scrollDeltaY += scrollDelta; this.lastScrollTop = currentScrollTop; @@ -537,7 +513,6 @@ export class DragDropManager { this.draggedClone = null; this.currentColumn = null; this.isDragStarted = false; - this.hasScrolledDuringDrag = false; this.scrollDeltaY = 0; this.lastScrollTop = 0; } @@ -562,26 +537,6 @@ export class DragDropManager { return null; } - /** - * Handle mouse enter on swp-event - activate hover tracking - */ - private handleEventMouseEnter(event: MouseEvent): void { - const target = event.target as HTMLElement; - const eventElement = target.closest('swp-event'); - - // Only handle hover if mouse button is up - if (eventElement && !this.isDragStarted && event.buttons === 0) { - // Clear any previous hover first - if (this.currentHoveredEvent && this.currentHoveredEvent !== eventElement) { - this.currentHoveredEvent.classList.remove('hover'); - } - - this.isHoverTrackingActive = true; - this.currentHoveredEvent = eventElement; - eventElement.classList.add('hover'); - } - } - /** * Handle mouse enter on calendar header - simplified using native events */ @@ -681,33 +636,4 @@ export class DragDropManager { }; this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload); } - - 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 && - mouseY >= rect.top && mouseY <= rect.bottom; - - // 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) { - this.currentHoveredEvent.classList.remove('hover'); - this.currentHoveredEvent = null; - } - } } diff --git a/src/managers/DragHoverManager.ts b/src/managers/DragHoverManager.ts new file mode 100644 index 0000000..65263f5 --- /dev/null +++ b/src/managers/DragHoverManager.ts @@ -0,0 +1,116 @@ +/** + * DragHoverManager - Handles event hover tracking + * Fully autonomous - listens to mouse events and manages hover state independently + */ + +import { IEventBus } from '../types/CalendarTypes'; + +export class DragHoverManager { + private isHoverTrackingActive = false; + private currentHoveredEvent: HTMLElement | null = null; + private calendarContainer: HTMLElement | null = null; + + constructor(private eventBus: IEventBus) { + this.init(); + } + + private init(): void { + // Wait for DOM to be ready + setTimeout(() => { + this.calendarContainer = document.querySelector('swp-calendar-container'); + if (this.calendarContainer) { + this.setupEventListeners(); + } + }, 100); + + // Listen to drag start to deactivate hover tracking + this.eventBus.on('drag:start', () => { + this.deactivateTracking(); + }); + } + + private setupEventListeners(): void { + if (!this.calendarContainer) return; + + // Listen to mouseenter on events (using event delegation) + this.calendarContainer.addEventListener('mouseenter', (e) => { + const target = e.target as HTMLElement; + const eventElement = target.closest('swp-event'); + + if (eventElement) { + this.handleEventMouseEnter(e as MouseEvent, eventElement); + } + }, true); // Use capture phase + + // Listen to mousemove globally to track when mouse leaves event bounds + document.body.addEventListener('mousemove', (e: MouseEvent) => { + if (this.isHoverTrackingActive && e.buttons === 0) { + this.checkEventHover(e); + } + }); + } + + /** + * Handle mouse enter on swp-event - activate hover tracking + */ + private handleEventMouseEnter(event: MouseEvent, eventElement: HTMLElement): void { + // Only handle hover if mouse button is up + if (event.buttons === 0) { + // Clear any previous hover first + if (this.currentHoveredEvent && this.currentHoveredEvent !== eventElement) { + this.currentHoveredEvent.classList.remove('hover'); + } + + this.isHoverTrackingActive = true; + this.currentHoveredEvent = eventElement; + eventElement.classList.add('hover'); + + this.eventBus.emit('event:hover:start', { element: eventElement }); + } + } + + /** + * Check if mouse is still over the currently hovered event + */ + private checkEventHover(event: MouseEvent): void { + // Only track hover when active and mouse button is up + if (!this.isHoverTrackingActive || !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 && + mouseY >= rect.top && mouseY <= rect.bottom; + + // 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(); + } + } + } + + /** + * Clear hover state + */ + private clearEventHover(): void { + if (this.currentHoveredEvent) { + this.currentHoveredEvent.classList.remove('hover'); + this.eventBus.emit('event:hover:end', { element: this.currentHoveredEvent }); + this.currentHoveredEvent = null; + } + } + + /** + * Deactivate hover tracking and clear any current hover + * Called via event bus when drag starts + */ + private deactivateTracking(): void { + this.isHoverTrackingActive = false; + this.clearEventHover(); + } +} diff --git a/src/types/ManagerTypes.ts b/src/types/ManagerTypes.ts index e35768a..e58bf9f 100644 --- a/src/types/ManagerTypes.ts +++ b/src/types/ManagerTypes.ts @@ -15,6 +15,7 @@ export interface CalendarManagers { allDayManager: unknown; // Avoid interface conflicts resizeHandleManager: ResizeHandleManager; edgeScrollManager: unknown; // Avoid interface conflicts + dragHoverManager: unknown; // Avoid interface conflicts } /**