diff --git a/docs/EventSystem-Analysis.md b/docs/EventSystem-Analysis.md new file mode 100644 index 0000000..5d70f46 --- /dev/null +++ b/docs/EventSystem-Analysis.md @@ -0,0 +1,161 @@ +# Calendar Event System Analysis + +## Overview +Analysis of all events used in the Calendar Plantempus system, categorized by type and usage. + +## Core Events (25 events) +*Defined in `src/constants/CoreEvents.ts`* + +### Lifecycle Events (3) +- `core:initialized` - Calendar initialization complete +- `core:ready` - Calendar ready for use +- `core:destroyed` - Calendar cleanup complete + +### View Events (3) +- `view:changed` - Calendar view changed (day/week/month) +- `view:rendered` - View rendering complete +- `workweek:changed` - Work week configuration changed + +### Navigation Events (4) +- `nav:date-changed` - Current date changed +- `nav:navigation-completed` - Navigation animation/transition complete +- `nav:period-info-update` - Week/period information updated +- `nav:navigate-to-event` - Request to navigate to specific event + +### Data Events (4) +- `data:loading` - Data fetch started +- `data:loaded` - Data fetch completed +- `data:error` - Data fetch error +- `data:events-filtered` - Events filtered + +### Grid Events (3) +- `grid:rendered` - Grid rendering complete +- `grid:clicked` - Grid cell clicked +- `grid:cell-selected` - Grid cell selected + +### Event Management (4) +- `event:created` - New event created +- `event:updated` - Event updated +- `event:deleted` - Event deleted +- `event:selected` - Event selected + +### System Events (2) +- `system:error` - System error occurred +- `system:refresh` - Refresh requested + +### Filter Events (1) +- `filter:changed` - Event filter changed + +### Rendering Events (1) +- `events:rendered` - Events rendering complete + +## Custom Events (22 events) +*Used throughout the system for specific functionality* + +### Drag & Drop Events (12) +- `drag:start` - Drag operation started +- `drag:move` - Drag operation in progress +- `drag:end` - Drag operation ended +- `drag:auto-scroll` - Auto-scroll during drag +- `drag:column-change` - Dragged to different column +- `drag:mouseenter-header` - Mouse entered header during drag +- `drag:mouseleave-header` - Mouse left header during drag +- `drag:convert-to-time_event` - Convert all-day to timed event + +### Event Interaction (2) +- `event:click` - Event clicked (no drag) +- `event:clicked` - Event clicked (legacy) + +### Header Events (3) +- `header:mouseleave` - Mouse left header area +- `header:height-changed` - Header height changed +- `header:rebuilt` - Header DOM rebuilt + +### All-Day Events (1) +- `allday:ensure-container` - Ensure all-day container exists + +### Column Events (1) +- `column:mouseover` - Mouse over column + +### Scroll Events (1) +- `scroll:to-event-time` - Scroll to specific event time + +### Workweek Events (1) +- `workweek:header-update` - Update header after workweek change + +### Navigation Events (1) +- `navigation:completed` - Navigation completed (different from core event) + +## Event Payload Analysis + +### Type Safety Issues Found + +#### 1. AllDayManager Event Mismatch +**File:** `src/managers/AllDayManager.ts:33-34` +```typescript +// Expected payload: +const { targetDate, originalElement } = (event as CustomEvent).detail; + +// Actual payload from DragDropManager: +{ + targetDate: string, + mousePosition: { x: number, y: number }, + originalElement: HTMLElement, + cloneElement: HTMLElement | null +} +``` + +#### 2. Inconsistent Event Signatures +Multiple events have different payload structures across different emitters/listeners. + +#### 3. No Type Safety +All events use `(event as CustomEvent).detail` without proper TypeScript interfaces. + +## Event Usage Statistics + +### Most Used Events +1. **Drag Events** - 12 different types, used heavily in drag-drop system +2. **Core Navigation** - 4 types, used across all managers +3. **Grid Events** - 3 types, fundamental to calendar rendering +4. **Header Events** - 3 types, critical for all-day functionality + +### Critical Events (High Impact) +- `drag:mouseenter-header` / `drag:mouseleave-header` - Core drag functionality +- `nav:navigation-completed` - Synchronizes multiple managers +- `grid:rendered` - Triggers event rendering +- `events:rendered` - Triggers filtering system + +### Simple Events (Low Impact) +- `header:height-changed` - Simple notification +- `allday:ensure-container` - Simple request +- `system:refresh` - Simple trigger + +## Recommendations + +### Priority 1: Fix Critical Issues +1. Fix AllDayManager event signature mismatch +2. Standardize drag event payloads +3. Document current event contracts + +### Priority 2: Type Safety Implementation +1. Create TypeScript interfaces for all event payloads +2. Implement type-safe EventBus +3. Migrate drag events first (highest complexity) + +### Priority 3: System Cleanup +1. Consolidate duplicate events (`event:click` vs `event:clicked`) +2. Standardize event naming conventions +3. Remove unused events + +## Total Event Count +- **Core Events:** 25 +- **Custom Events:** 22 +- **Total:** 47 unique event types + +## Files Analyzed +- `src/constants/CoreEvents.ts` +- `src/managers/*.ts` (8 files) +- `src/renderers/*.ts` (4 files) +- `src/core/CalendarConfig.ts` + +*Analysis completed: 2025-09-20* \ No newline at end of file diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts index e4d0d78..cd04daa 100644 --- a/src/managers/AllDayManager.ts +++ b/src/managers/AllDayManager.ts @@ -4,6 +4,12 @@ import { eventBus } from '../core/EventBus'; import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig'; import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer'; import { CalendarEvent } from '../types/CalendarTypes'; +import { + DragMouseEnterHeaderEventPayload, + DragStartEventPayload, + DragMoveEventPayload, + DragEndEventPayload +} from '../types/EventTypes'; /** * AllDayManager - Handles all-day row height animations and management @@ -28,14 +34,20 @@ export class AllDayManager { * Setup event listeners for drag conversions */ private setupEventListeners(): void { - eventBus.on('drag:convert-to-allday_event', (event) => { - const { targetDate, originalElement } = (event as CustomEvent).detail; - console.log('๐Ÿ”„ AllDayManager: Received drag:convert-to-allday_event', { + + + eventBus.on('drag:mouseenter-header', (event) => { + const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent).detail; + + console.log('๐Ÿ”„ AllDayManager: Received drag:mouseenter-header', { targetDate, originalElementId: originalElement?.dataset?.eventId, originalElementTag: originalElement?.tagName }); - this.handleConvertToAllDay(targetDate, originalElement); + + if (targetDate && cloneElement) { + this.handleConvertToAllDay(targetDate, cloneElement); + } }); @@ -53,20 +65,25 @@ export class AllDayManager { // Listen for drag operations on all-day events eventBus.on('drag:start', (event) => { - const { eventId, mouseOffset } = (event as CustomEvent).detail; - - // Check if this is an all-day event - const originalElement = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`); - if (!originalElement) return; // Not an all-day event + const { draggedElement, mouseOffset } = (event as CustomEvent).detail; + + // Check if this is an all-day event by checking if it's in all-day container + const isAllDayEvent = draggedElement.closest('swp-allday-container'); + if (!isAllDayEvent) return; // Not an all-day event + const eventId = draggedElement.dataset.eventId; console.log('๐ŸŽฏ AllDayManager: Starting drag for all-day event', { eventId }); - this.handleDragStart(originalElement as HTMLElement, eventId, mouseOffset); + this.handleDragStart(draggedElement, eventId || '', mouseOffset); }); eventBus.on('drag:move', (event) => { - const { eventId, mousePosition } = (event as CustomEvent).detail; + const { draggedElement, mousePosition } = (event as CustomEvent).detail; - // Only handle for all-day events + // Only handle for all-day events - check if original element is all-day + const isAllDayEvent = draggedElement.closest('swp-allday-container'); + if (!isAllDayEvent) return; + + const eventId = draggedElement.dataset.eventId; const dragClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${eventId}"]`); if (dragClone) { this.handleDragMove(dragClone as HTMLElement, mousePosition); @@ -74,26 +91,21 @@ export class AllDayManager { }); eventBus.on('drag:end', (event) => { + const { draggedElement, mousePosition, finalPosition, target } = (event as CustomEvent).detail; - const { eventId, finalColumn, finalY, dropTarget } = (event as CustomEvent).detail; - - if (dropTarget != 'SWP-DAY-HEADER')//we are not inside the swp-day-header, so just ignore. + if (target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore. return; + const eventId = draggedElement.dataset.eventId; console.log('๐ŸŽฌ AllDayManager: Received drag:end', { eventId: eventId, - finalColumn: finalColumn, - finalY: finalY + finalPosition }); - // Check if this was an all-day event - const originalElement = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`); const dragClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${eventId}"]`); - - console.log('๐ŸŽฏ AllDayManager: Ending drag for all-day event', { eventId }); - this.handleDragEnd(originalElement as HTMLElement, dragClone as HTMLElement, finalColumn); + this.handleDragEnd(draggedElement, dragClone as HTMLElement, finalPosition.column); }); } @@ -287,18 +299,20 @@ export class AllDayManager { /** * Handle conversion of timed event to all-day event */ - private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void { + private handleConvertToAllDay(targetDate: string, cloneElement: HTMLElement): void { // Extract event data from original element - const eventId = originalElement.dataset.eventId; - const title = originalElement.dataset.title || originalElement.textContent || 'Untitled'; - const type = originalElement.dataset.type || 'work'; - const startStr = originalElement.dataset.start; - const endStr = originalElement.dataset.end; + const eventId = cloneElement.dataset.eventId; + const title = cloneElement.dataset.title || cloneElement.textContent || 'Untitled'; + const type = cloneElement.dataset.type || 'work'; + const startStr = cloneElement.dataset.start; + const endStr = cloneElement.dataset.end; if (!eventId || !startStr || !endStr) { console.error('Original element missing required data (eventId, start, end)'); return; } + //we just hide it, it will only be removed on mouse up + cloneElement.style.display = 'none'; // Create CalendarEvent for all-day conversion - preserve original times const originalStart = new Date(startStr); @@ -312,7 +326,7 @@ export class AllDayManager { targetEnd.setHours(originalEnd.getHours(), originalEnd.getMinutes(), originalEnd.getSeconds(), originalEnd.getMilliseconds()); const calendarEvent: CalendarEvent = { - id: `clone-${eventId}`, + id: eventId, title: title, start: targetStart, end: targetEnd, @@ -320,12 +334,12 @@ export class AllDayManager { allDay: true, syncStatus: 'synced', metadata: { - duration: originalElement.dataset.duration || '60' + duration: cloneElement.dataset.duration || '60' } }; // Check if all-day clone already exists for this event ID - const existingAllDayEvent = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${eventId}"]`); + const existingAllDayEvent = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`); if (existingAllDayEvent) { // All-day event already exists, just ensure clone is hidden const dragClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`); diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index da059b6..49c1c01 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -5,8 +5,14 @@ import { IEventBus } from '../types/CalendarTypes'; import { calendarConfig } from '../core/CalendarConfig'; -import { DateCalculator } from '../utils/DateCalculator'; import { PositionUtils } from '../utils/PositionUtils'; +import { + DragStartEventPayload, + DragMoveEventPayload, + DragEndEventPayload, + DragMouseEnterHeaderEventPayload, + DragMouseLeaveHeaderEventPayload +} from '../types/EventTypes'; interface CachedElements { scrollContainer: HTMLElement | null; @@ -27,71 +33,72 @@ interface ColumnBounds { export class DragDropManager { private eventBus: IEventBus; - + // Mouse tracking with optimized state private lastMousePosition: Position = { x: 0, y: 0 }; private lastLoggedPosition: Position = { x: 0, y: 0 }; private currentMouseY = 0; private mouseOffset: Position = { x: 0, y: 0 }; private initialMousePosition: Position = { x: 0, y: 0 }; - + // Drag state - private draggedEventId: string | null = null; - private originalElement: HTMLElement | null = null; + private draggedElement: HTMLElement | null = null ; private currentColumn: string | null = null; private isDragStarted = false; - + + // Header tracking state + private isInHeader = false; + // Movement threshold to distinguish click from drag private readonly dragThreshold = 5; // pixels - + // Cached DOM elements for performance private cachedElements: CachedElements = { scrollContainer: null, currentColumn: null, lastColumnDate: null }; - + // Column bounds cache for coordinate-based column detection private columnBoundsCache: ColumnBounds[] = []; - + // Auto-scroll properties private autoScrollAnimationId: number | null = null; private readonly scrollSpeed = 10; // pixels per frame private readonly scrollThreshold = 30; // pixels from edge - + // Snap configuration private snapIntervalMinutes = 15; // Default 15 minutes private hourHeightPx: number; // Will be set from config - + // Event listener references for proper cleanup private boundHandlers = { mouseMove: this.handleMouseMove.bind(this), mouseDown: this.handleMouseDown.bind(this), mouseUp: this.handleMouseUp.bind(this) }; - + private get snapDistancePx(): number { return (this.snapIntervalMinutes / 60) * this.hourHeightPx; } - + constructor(eventBus: IEventBus) { this.eventBus = eventBus; - // Get config values const gridSettings = calendarConfig.getGridSettings(); this.hourHeightPx = gridSettings.hourHeight; this.snapIntervalMinutes = gridSettings.snapInterval; - + this.init(); } - + /** * Configure snap interval */ public setSnapInterval(minutes: number): void { this.snapIntervalMinutes = minutes; } - + /** * Initialize with optimized event listener setup */ @@ -100,100 +107,32 @@ export class DragDropManager { document.body.addEventListener('mousemove', this.boundHandlers.mouseMove); document.body.addEventListener('mousedown', this.boundHandlers.mouseDown); document.body.addEventListener('mouseup', this.boundHandlers.mouseUp); - + // Initialize column bounds cache this.updateColumnBoundsCache(); - + // Listen to resize events to update cache window.addEventListener('resize', () => { this.updateColumnBoundsCache(); }); - + // Listen to navigation events to update cache this.eventBus.on('navigation:completed', () => { this.updateColumnBoundsCache(); }); - - // Listen for header mouseover events - this.eventBus.on('header:mouseover', (event) => { - const { targetDate, headerRenderer } = (event as CustomEvent).detail; - - console.log('๐ŸŽฏ DragDropManager: Received header:mouseover', { - targetDate, - draggedEventId: this.draggedEventId, - isDragging: !!this.draggedEventId - }); - - if (this.draggedEventId && targetDate) { - // Find dragget element dynamisk - const draggedElement = document.querySelector(`swp-event[data-event-id="${this.draggedEventId}"]`); - - console.log('๐Ÿ” DragDropManager: Looking for dragged element', { - eventId: this.draggedEventId, - found: !!draggedElement, - tagName: draggedElement?.tagName - }); - - if (draggedElement) { - console.log('โœ… DragDropManager: Converting to all-day for date:', targetDate); - - // Element findes stadig som day-event, sรฅ konverter - this.eventBus.emit('drag:convert-to-allday_event', { - targetDate, - originalElement: draggedElement, - headerRenderer - }); - } else { - console.log('โŒ DragDropManager: Dragged element not found'); - } - } else { - console.log('โญ๏ธ DragDropManager: Skipping conversion - no drag or no target date'); - } - }); - // Listen for column mouseover events (for all-day to timed conversion) - this.eventBus.on('column:mouseover', (event) => { - const { targetColumn, targetY } = (event as CustomEvent).detail; - - if (this.draggedEventId && this.isAllDayEventBeingDragged()) { - // Emit event to convert to timed - this.eventBus.emit('drag:convert-to-timed', { - eventId: this.draggedEventId, - targetColumn, - targetY - }); - } - }); - - // Listen for header mouseleave events (convert from all-day back to day) - this.eventBus.on('header:mouseleave', (event) => { - // Check if we're dragging ANY event - if (this.draggedEventId) { - const mousePosition = { x: this.lastMousePosition.x, y: this.lastMousePosition.y }; - const column = this.getColumnDateFromX(mousePosition.x); - - // Find the actual dragged element - const draggedElement = document.querySelector(`[data-event-id="${this.draggedEventId}"]`) as HTMLElement; - - this.eventBus.emit('drag:convert-to-time_event', { - draggedElement: draggedElement, - mousePosition: mousePosition, - column: column - }); - } - }); } - + private handleMouseDown(event: MouseEvent): void { this.isDragStarted = false; this.lastMousePosition = { x: event.clientX, y: event.clientY }; this.lastLoggedPosition = { x: event.clientX, y: event.clientY }; this.initialMousePosition = { x: event.clientX, y: event.clientY }; - + // Check if mousedown is on an event const target = event.target as HTMLElement; let eventElement = target; - + while (eventElement && eventElement.tagName !== 'SWP-EVENTS-LAYER') { if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') { break; @@ -201,96 +140,104 @@ export class DragDropManager { eventElement = eventElement.parentElement as HTMLElement; if (!eventElement) return; } - + // If we reached SWP-EVENTS-LAYER without finding an event, return if (!eventElement || eventElement.tagName === 'SWP-EVENTS-LAYER') { return; } - + // Found an event - prepare for potential dragging if (eventElement) { - this.originalElement = eventElement; - this.draggedEventId = eventElement.dataset.eventId || null; - + this.draggedElement = eventElement; + // Calculate mouse offset within event const eventRect = eventElement.getBoundingClientRect(); this.mouseOffset = { x: event.clientX - eventRect.left, y: event.clientY - eventRect.top }; - + // Detect current column const column = this.detectColumn(event.clientX, event.clientY); if (column) { this.currentColumn = column; } - + // Don't emit drag:start yet - wait for movement threshold } } - + /** * Optimized mouse move handler with consolidated position calculations */ private handleMouseMove(event: MouseEvent): void { this.currentMouseY = event.clientY; - - if (event.buttons === 1 && this.draggedEventId) { + this.lastMousePosition = { x: event.clientX, y: event.clientY }; + + // Check for header enter/leave during drag + if (this.draggedElement) { + this.checkHeaderEnterLeave(event); + } + + if (event.buttons === 1 && this.draggedElement) { const currentPosition: Position = { x: event.clientX, y: event.clientY }; - + // Check if we need to start drag (movement threshold) if (!this.isDragStarted) { 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) { // Start drag - emit drag:start event this.isDragStarted = true; - this.eventBus.emit('drag:start', { - eventId: this.draggedEventId, + + const dragStartPayload: DragStartEventPayload = { + draggedElement: this.draggedElement, mousePosition: this.initialMousePosition, mouseOffset: this.mouseOffset, column: this.currentColumn - }); + }; + this.eventBus.emit('drag:start', dragStartPayload); } else { // Not enough movement yet - don't start drag return; } } - + // Continue with normal drag behavior only if drag has started if (this.isDragStarted) { const deltaY = Math.abs(currentPosition.y - this.lastLoggedPosition.y); - + // Check for snap interval vertical movement (normal drag behavior) if (deltaY >= this.snapDistancePx) { this.lastLoggedPosition = currentPosition; - + // Consolidated position calculations with snapping for normal drag const positionData = this.calculateDragPosition(currentPosition); - + // Emit drag move event with snapped position (normal behavior) - this.eventBus.emit('drag:move', { - eventId: this.draggedEventId, + const dragMovePayload: DragMoveEventPayload = { + draggedElement: this.draggedElement, mousePosition: currentPosition, snappedY: positionData.snappedY, column: positionData.column, mouseOffset: this.mouseOffset - }); + }; + this.eventBus.emit('drag:move', dragMovePayload); } - + // Check for auto-scroll this.checkAutoScroll(event); - + // Check for column change using cached data const newColumn = this.getColumnFromCache(currentPosition); if (newColumn && newColumn !== this.currentColumn) { const previousColumn = this.currentColumn; this.currentColumn = newColumn; - + this.eventBus.emit('drag:column-change', { - eventId: this.draggedEventId, + draggedElement: this.draggedElement, previousColumn, newColumn, mousePosition: currentPosition @@ -299,81 +246,64 @@ export class DragDropManager { } } } - + /** * Optimized mouse up handler with consolidated cleanup */ private handleMouseUp(event: MouseEvent): void { this.stopAutoScroll(); - - if (this.draggedEventId && this.originalElement) { + + if (this.draggedElement) { // Store variables locally before cleanup - const eventId = this.draggedEventId; - const originalElement = this.originalElement; + const draggedElement = this.draggedElement; const isDragStarted = this.isDragStarted; - + // Clean up drag state first this.cleanupDragState(); - + // Only emit drag:end if drag was actually started if (isDragStarted) { - const finalPosition: Position = { x: event.clientX, y: event.clientY }; - + const mousePosition: Position = { x: event.clientX, y: event.clientY }; + // Use consolidated position calculation - const positionData = this.calculateDragPosition(finalPosition); - + const positionData = this.calculateDragPosition(mousePosition); + // Detect drop target (swp-day-column or swp-day-header) - const dropTarget = this.detectDropTarget(finalPosition); - + const dropTarget = this.detectDropTarget(mousePosition); + console.log('๐ŸŽฏ DragDropManager: Emitting drag:end', { - eventId: eventId, + draggedElement: draggedElement.dataset.eventId, finalColumn: positionData.column, finalY: positionData.snappedY, dropTarget: dropTarget, isDragStarted: isDragStarted }); - - this.eventBus.emit('drag:end', { - eventId: eventId, - finalPosition, - finalColumn: positionData.column, - finalY: positionData.snappedY, + + const dragEndPayload: DragEndEventPayload = { + draggedElement: draggedElement, + mousePosition, + finalPosition: positionData, target: dropTarget - }); + }; + this.eventBus.emit('drag:end', dragEndPayload); } else { // This was just a click - emit click event instead this.eventBus.emit('event:click', { - eventId: eventId, + draggedElement: draggedElement, mousePosition: { x: event.clientX, y: event.clientY } }); } } } - + /** * Consolidated position calculation method using PositionUtils */ private calculateDragPosition(mousePosition: Position): { column: string | null; snappedY: number } { const column = this.detectColumn(mousePosition.x, mousePosition.y); const snappedY = this.calculateSnapPosition(mousePosition.y, column); - - return { column, snappedY }; - } - /** - * Calculate free position (follows mouse exactly) - */ - private calculateFreePosition(mouseY: number, column: string | null = null): number { - const targetColumn = column || this.currentColumn; - - // Use cached column element if available - const columnElement = this.getCachedColumnElement(targetColumn); - if (!columnElement) return mouseY; - - const relativeY = PositionUtils.getPositionFromCoordinate(mouseY, columnElement); - - // Return free position (no snapping) - return Math.max(0, relativeY); + return { column, snappedY }; } /** @@ -381,32 +311,32 @@ export class DragDropManager { */ private calculateSnapPosition(mouseY: number, column: string | null = null): number { const targetColumn = column || this.currentColumn; - + // Use cached column element if available const columnElement = this.getCachedColumnElement(targetColumn); if (!columnElement) return mouseY; - + // Use PositionUtils for consistent snapping behavior const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, columnElement); - + return Math.max(0, snappedY); } - + /** * Update column bounds cache for coordinate-based column detection */ private updateColumnBoundsCache(): void { // Reset cache this.columnBoundsCache = []; - + // Find alle kolonner const columns = document.querySelectorAll('swp-day-column'); - + // Cache hver kolonnes x-grรฆnser columns.forEach(column => { const rect = column.getBoundingClientRect(); const date = (column as HTMLElement).dataset.date; - + if (date) { this.columnBoundsCache.push({ date, @@ -415,10 +345,10 @@ export class DragDropManager { }); } }); - + // Sorter efter x-position (fra venstre til hรธjre) this.columnBoundsCache.sort((a, b) => a.left - b.left); - + } /** @@ -429,12 +359,12 @@ export class DragDropManager { if (this.columnBoundsCache.length === 0) { this.updateColumnBoundsCache(); } - + // Find den kolonne hvor x-koordinaten er indenfor grรฆnserne const column = this.columnBoundsCache.find(col => x >= col.left && x <= col.right ); - + return column ? column.date : null; } @@ -444,7 +374,7 @@ export class DragDropManager { private detectColumn(mouseX: number, mouseY: number): string | null { // Brug den koordinatbaserede metode direkte const columnDate = this.getColumnDateFromX(mouseX); - + // Opdater stadig den eksisterende cache hvis vi finder en kolonne if (columnDate && columnDate !== this.cachedElements.lastColumnDate) { const columnElement = document.querySelector(`swp-day-column[data-date="${columnDate}"]`) as HTMLElement; @@ -453,7 +383,7 @@ export class DragDropManager { this.cachedElements.lastColumnDate = columnDate; } } - + return columnDate; } @@ -468,7 +398,7 @@ export class DragDropManager { return this.cachedElements.lastColumnDate; } } - + // Cache miss - detect new column return this.detectColumn(mousePosition.x, mousePosition.y); } @@ -478,22 +408,22 @@ export class DragDropManager { */ private getCachedColumnElement(columnDate: string | null): HTMLElement | null { if (!columnDate) return null; - + // Return cached element if it matches if (this.cachedElements.lastColumnDate === columnDate && this.cachedElements.currentColumn) { return this.cachedElements.currentColumn; } - + // Query for new element and cache it const element = document.querySelector(`swp-day-column[data-date="${columnDate}"]`) as HTMLElement; if (element) { this.cachedElements.currentColumn = element; this.cachedElements.lastColumnDate = columnDate; } - + return element; } - + /** * Optimized auto-scroll check with cached container */ @@ -505,14 +435,14 @@ export class DragDropManager { return; } } - + const containerRect = this.cachedElements.scrollContainer.getBoundingClientRect(); const mouseY = event.clientY; - + // Calculate distances from edges const distanceFromTop = mouseY - containerRect.top; const distanceFromBottom = containerRect.bottom - mouseY; - + // Check if we need to scroll if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) { this.startAutoScroll('up'); @@ -522,24 +452,24 @@ export class DragDropManager { this.stopAutoScroll(); } } - + /** * Optimized auto-scroll with cached container reference */ private startAutoScroll(direction: 'up' | 'down'): void { if (this.autoScrollAnimationId !== null) return; - + const scroll = () => { - if (!this.cachedElements.scrollContainer || !this.draggedEventId) { + if (!this.cachedElements.scrollContainer || !this.draggedElement) { this.stopAutoScroll(); return; } - + const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed; this.cachedElements.scrollContainer.scrollTop += scrollAmount; - + // Emit updated position during scroll - adjust for scroll movement - if (this.draggedEventId) { + if (this.draggedElement) { // During autoscroll, we need to calculate position relative to the scrolled content // The mouse hasn't moved, but the content has scrolled const columnElement = this.getCachedColumnElement(this.currentColumn); @@ -548,21 +478,21 @@ export class DragDropManager { // Calculate free position relative to column, accounting for scroll movement (no snapping during scroll) const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y; const freeY = Math.max(0, relativeY); - + this.eventBus.emit('drag:auto-scroll', { - eventId: this.draggedEventId, + draggedElement: this.draggedElement, snappedY: freeY, // Actually free position during scroll scrollTop: this.cachedElements.scrollContainer.scrollTop }); } } - + this.autoScrollAnimationId = requestAnimationFrame(scroll); }; - + this.autoScrollAnimationId = requestAnimationFrame(scroll); } - + /** * Stop auto-scroll animation */ @@ -572,31 +502,21 @@ export class DragDropManager { this.autoScrollAnimationId = null; } } - + /** * Clean up drag state */ private cleanupDragState(): void { - this.draggedEventId = null; - this.originalElement = null; + this.draggedElement = null; this.currentColumn = null; this.isDragStarted = false; - + this.isInHeader = false; + // Clear cached elements this.cachedElements.currentColumn = null; this.cachedElements.lastColumnDate = null; } - /** - * Check if an all-day event is currently being dragged - */ - private isAllDayEventBeingDragged(): boolean { - if (!this.draggedEventId) return false; - // Check if element exists as all-day event - const allDayElement = document.querySelector(`swp-allday-event[data-event-id="${this.draggedEventId}"]`); - return allDayElement !== null; - } - /** * Detect drop target - whether dropped in swp-day-column or swp-day-header */ @@ -619,23 +539,81 @@ export class DragDropManager { return null; } + /** + * Check for header enter/leave during drag operations + */ + private checkHeaderEnterLeave(event: MouseEvent): void { + const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY); + if (!elementAtPosition) return; + + // Check if we're in a header area + const headerElement = elementAtPosition.closest('swp-day-header, swp-calendar-header'); + const isCurrentlyInHeader = !!headerElement; + + // Detect header enter + if (!this.isInHeader && isCurrentlyInHeader) { + this.isInHeader = true; + + // Calculate target date using existing method + const targetDate = this.getColumnDateFromX(event.clientX); + + if (targetDate) { + console.log('๐ŸŽฏ DragDropManager: Emitting drag:mouseenter-header', { targetDate }); + + // Find clone element (if it exists) + const eventId = this.draggedElement?.dataset.eventId; + const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement; + + const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = { + targetDate, + mousePosition: { x: event.clientX, y: event.clientY }, + originalElement: this.draggedElement, + cloneElement: cloneElement + }; + this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload); + } + } + + // Detect header leave + if (this.isInHeader && !isCurrentlyInHeader) { + this.isInHeader = false; + + console.log('๐Ÿšช DragDropManager: Emitting drag:mouseleave-header'); + + // Calculate target date using existing method + const targetDate = this.getColumnDateFromX(event.clientX); + + // Find clone element (if it exists) + const eventId = this.draggedElement?.dataset.eventId; + const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement; + + const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = { + targetDate, + mousePosition: { x: event.clientX, y: event.clientY }, + originalElement: this.draggedElement, + cloneElement: cloneElement + }; + this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload); + } + } + /** * Clean up all resources and event listeners */ public destroy(): void { this.stopAutoScroll(); - + // Remove event listeners using bound references document.body.removeEventListener('mousemove', this.boundHandlers.mouseMove); document.body.removeEventListener('mousedown', this.boundHandlers.mouseDown); document.body.removeEventListener('mouseup', this.boundHandlers.mouseUp); - + // Clear all cached elements this.cachedElements.scrollContainer = null; this.cachedElements.currentColumn = null; this.cachedElements.lastColumnDate = null; - + // Clean up drag state this.cleanupDragState(); } -} \ No newline at end of file +} diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index 427b66a..0a4e051 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -4,15 +4,18 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { CoreEvents } from '../constants/CoreEvents'; import { HeaderRenderContext } from '../renderers/HeaderRenderer'; import { ResourceCalendarData } from '../types/CalendarTypes'; +import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload } from '../types/EventTypes'; /** * HeaderManager - Handles all header-related event logic * Separates event handling from rendering concerns */ export class HeaderManager { - private headerEventListener: ((event: Event) => void) | null = null; - private headerMouseLeaveListener: ((event: Event) => void) | null = null; private cachedCalendarHeader: HTMLElement | null = null; + + // Event listeners for drag events + private dragMouseEnterHeaderListener: ((event: Event) => void) | null = null; + private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null; constructor() { // Bind methods for event listeners @@ -41,132 +44,72 @@ export class HeaderManager { } /** - * Setup header drag event listeners - REFACTORED to use mouseenter + * Setup header drag event listeners - REFACTORED to listen to DragDropManager events */ public setupHeaderDragListeners(): void { - if (!this.getCalendarHeader()) return; + console.log('๐ŸŽฏ HeaderManager: Setting up drag event listeners'); - console.log('๐ŸŽฏ HeaderManager: Setting up drag listeners with mouseenter'); - - - // Use mouseenter instead of mouseover to avoid continuous firing - this.headerEventListener = (event: Event) => { + // Create and store event listeners + this.dragMouseEnterHeaderListener = (event: Event) => { + const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent).detail; - if (!document.querySelector('.dragging') !== null) { - return; - } + console.log('๐ŸŽฏ HeaderManager: Received drag:mouseenter-header', { + targetDate, + originalElement: !!originalElement, + cloneElement: !!cloneElement + }); - const target = event.target as HTMLElement; - - console.log('๐Ÿ–ฑ๏ธ HeaderManager: mouseenter detected on:', target.tagName, target.className); - - // Check if we're entering the all-day container OR the header area where container should be - let allDayContainer = target.closest('swp-allday-container'); - - // If no container exists, check if we're in the header and should create one via AllDayManager - if (!allDayContainer && target.closest('swp-calendar-header')) { - console.log('๐Ÿ“ HeaderManager: In header area but no all-day container exists, requesting creation...'); + if (targetDate) { + // Ensure all-day container exists + this.ensureAllDayContainer(); - // Emit event to AllDayManager to create container - eventBus.emit('allday:ensure-container'); + const calendarType = calendarConfig.getCalendarMode(); + const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); - // Try to find it again after creation - allDayContainer = target.closest('swp-calendar-header')?.querySelector('swp-allday-container') as HTMLElement; - } - - if (allDayContainer) { - console.log('๐Ÿ“ HeaderManager: Active drag detected, calculating target date...'); - - // Calculate target date from mouse X coordinate - const targetDate = this.calculateTargetDateFromMouseX(event as MouseEvent); - - console.log('๐ŸŽฏ HeaderManager: Calculated target date:', targetDate); - - if (targetDate) { - const calendarType = calendarConfig.getCalendarMode(); - const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); - - console.log('โœ… HeaderManager: Emitting header:mouseover with targetDate:', targetDate); - - eventBus.emit('header:mouseover', { - element: allDayContainer, - targetDate, - headerRenderer - }); - } else { - console.log('โŒ HeaderManager: Could not calculate target date from mouse position'); - } + } }; - // Header mouseleave listener - this.headerMouseLeaveListener = (event: Event) => { - console.log('๐Ÿšช HeaderManager: mouseleave detected'); + this.dragMouseLeaveHeaderListener = (event: Event) => { + const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent).detail; + + console.log('๐Ÿšช HeaderManager: Received drag:mouseleave-header', { + targetDate, + originalElement: !!originalElement, + cloneElement: !!cloneElement + }); + eventBus.emit('header:mouseleave', { - element: event.target as HTMLElement + element: this.getCalendarHeader(), + targetDate, + originalElement, + cloneElement }); }; - this.getCalendarHeader()?.addEventListener('mouseenter', this.headerEventListener, true); - this.getCalendarHeader()?.addEventListener('mouseleave', this.headerMouseLeaveListener); + // Listen for drag events from DragDropManager + eventBus.on('drag:mouseenter-header', this.dragMouseEnterHeaderListener); + eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); - console.log('โœ… HeaderManager: Event listeners attached (mouseenter + mouseleave)'); + console.log('โœ… HeaderManager: Drag event listeners attached'); } /** - * Calculate target date from mouse X coordinate + * Ensure all-day container exists in header */ - private calculateTargetDateFromMouseX(event: MouseEvent): string | null { - const dayHeaders = document.querySelectorAll('swp-day-header'); - const mouseX = event.clientX; - - console.log('๐Ÿงฎ HeaderManager: Calculating target date from mouseX:', mouseX); - console.log('๐Ÿ“Š HeaderManager: Found', dayHeaders.length, 'day headers'); - - for (const header of dayHeaders) { - const headerElement = header as HTMLElement; - const rect = headerElement.getBoundingClientRect(); - const headerDate = headerElement.dataset.date; - - console.log('๐Ÿ“ HeaderManager: Checking header', headerDate, 'bounds:', { - left: rect.left, - right: rect.right, - mouseX: mouseX, - isWithin: mouseX >= rect.left && mouseX <= rect.right - }); - - // Check if mouse X is within this header's bounds - if (mouseX >= rect.left && mouseX <= rect.right) { - console.log('๐ŸŽฏ HeaderManager: Found matching header for date:', headerDate); - return headerDate || null; - } - } - - console.log('โŒ HeaderManager: No matching header found for mouseX:', mouseX); - return null; - } - - /** - * Remove event listeners from header - UPDATED for mouseenter - */ - private removeEventListeners(): void { + private ensureAllDayContainer(): void { const calendarHeader = this.getCalendarHeader(); if (!calendarHeader) return; - - console.log('๐Ÿงน HeaderManager: Removing event listeners'); - - if (this.headerEventListener) { - // Remove mouseenter listener with capture flag - calendarHeader.removeEventListener('mouseenter', this.headerEventListener, true); - console.log('โœ… HeaderManager: Removed mouseenter listener'); - } - if (this.headerMouseLeaveListener) { - calendarHeader.removeEventListener('mouseleave', this.headerMouseLeaveListener); - console.log('โœ… HeaderManager: Removed mouseleave listener'); + let allDayContainer = calendarHeader.querySelector('swp-allday-container'); + + if (!allDayContainer) { + console.log('๐Ÿ“ HeaderManager: All-day container missing, requesting creation...'); + eventBus.emit('allday:ensure-container'); } } + /** * Setup navigation event listener */ @@ -198,10 +141,7 @@ export class HeaderManager { const calendarHeader = this.getOrCreateCalendarHeader(); if (!calendarHeader) return; - // Remove existing event listeners BEFORE clearing content - this.removeEventListeners(); - - // Clear existing content + // Clear existing content calendarHeader.innerHTML = ''; // Render new header content @@ -257,11 +197,18 @@ export class HeaderManager { * Clean up resources and event listeners */ public destroy(): void { - this.removeEventListeners(); + + // Remove eventBus listeners + if (this.dragMouseEnterHeaderListener) { + eventBus.off('drag:mouseenter-header', this.dragMouseEnterHeaderListener); + } + if (this.dragMouseLeaveHeaderListener) { + eventBus.off('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); + } // Clear references - this.headerEventListener = null; - this.headerMouseLeaveListener = null; + this.dragMouseEnterHeaderListener = null; + this.dragMouseLeaveHeaderListener = null; this.clearCache(); } diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts index 9fb71b8..f567b35 100644 --- a/src/renderers/EventRendererManager.ts +++ b/src/renderers/EventRendererManager.ts @@ -6,7 +6,7 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { EventManager } from '../managers/EventManager'; import { EventRendererStrategy } from './EventRenderer'; import { SwpEventElement } from '../elements/SwpEventElement'; - +import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload } from '../types/EventTypes'; /** * EventRenderingService - Render events i DOM med positionering using Strategy Pattern * Hรฅndterer event positioning og overlap detection @@ -16,14 +16,16 @@ export class EventRenderingService { private eventManager: EventManager; private strategy: EventRendererStrategy; + private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null; + constructor(eventBus: IEventBus, eventManager: EventManager) { this.eventBus = eventBus; this.eventManager = eventManager; - + // Cache strategy at initialization const calendarType = calendarConfig.getCalendarMode(); this.strategy = CalendarTypeFactory.getEventRenderer(calendarType); - + this.setupEventListeners(); } @@ -31,24 +33,24 @@ export class EventRenderingService { * Render events in a specific container for a given period */ public renderEvents(context: RenderContext): void { - + // Clear existing events in the specific container first this.strategy.clearEvents(context.container); - + // Get events from EventManager for the period const events = this.eventManager.getEventsForPeriod( context.startDate, context.endDate ); - - + + if (events.length === 0) { return; } - + // Use cached strategy to render events in the specific container this.strategy.renderEvents(events, context.container); - + // Emit EVENTS_RENDERED event for filtering system this.eventBus.emit(CoreEvents.EVENTS_RENDERED, { events: events, @@ -57,16 +59,11 @@ export class EventRenderingService { } private setupEventListeners(): void { - // Event-driven rendering: React to grid and container events + this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => { this.handleGridRendered(event as CustomEvent); }); - // CONTAINER_READY_FOR_EVENTS removed - events are now pre-rendered synchronously - // this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => { - // this.handleContainerReady(event as CustomEvent); - // }); - this.eventBus.on(CoreEvents.VIEW_CHANGED, (event: Event) => { this.handleViewChanged(event as CustomEvent); }); @@ -93,7 +90,7 @@ export class EventRenderingService { */ private handleGridRendered(event: CustomEvent): void { const { container, startDate, endDate, currentDate } = event.detail; - + if (!container) { return; } @@ -110,7 +107,7 @@ export class EventRenderingService { } else { return; } - + this.renderEvents({ container: container, startDate: periodStart, @@ -123,7 +120,7 @@ export class EventRenderingService { */ private handleContainerReady(event: CustomEvent): void { const { container, startDate, endDate } = event.detail; - + if (!container || !startDate || !endDate) { return; } @@ -141,7 +138,7 @@ export class EventRenderingService { private handleViewChanged(event: CustomEvent): void { // Clear all existing events since view structure may have changed this.clearEvents(); - + // New rendering will be triggered by subsequent GRID_RENDERED event } @@ -152,45 +149,49 @@ export class EventRenderingService { private setupDragEventListeners(): void { // Handle drag start this.eventBus.on('drag:start', (event: Event) => { - const { eventId, mouseOffset, column } = (event as CustomEvent).detail; - // Find element dynamically - const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement; - if (originalElement && this.strategy.handleDragStart) { - this.strategy.handleDragStart(originalElement, eventId, mouseOffset, column); + const { draggedElement, mouseOffset, column } = (event as CustomEvent).detail; + // Use the draggedElement directly - no need for DOM query + if (draggedElement && this.strategy.handleDragStart && column) { + const eventId = draggedElement.dataset.eventId || ''; + this.strategy.handleDragStart(draggedElement, eventId, mouseOffset, column); } }); // Handle drag move this.eventBus.on('drag:move', (event: Event) => { - const { eventId, snappedY, column, mouseOffset } = (event as CustomEvent).detail; - if (this.strategy.handleDragMove) { + const { draggedElement, snappedY, column, mouseOffset } = (event as CustomEvent).detail; + if (this.strategy.handleDragMove && column) { + const eventId = draggedElement.dataset.eventId || ''; this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset); } }); // Handle drag auto-scroll this.eventBus.on('drag:auto-scroll', (event: Event) => { - const { eventId, snappedY } = (event as CustomEvent).detail; + const { draggedElement, snappedY } = (event as CustomEvent).detail; if (this.strategy.handleDragAutoScroll) { + const eventId = draggedElement.dataset.eventId || ''; this.strategy.handleDragAutoScroll(eventId, snappedY); } }); // Handle drag end events and delegate to appropriate renderer this.eventBus.on('drag:end', (event: Event) => { - const { eventId, finalColumn, finalY, target } = (event as CustomEvent).detail; - + const { draggedElement, finalPosition, target } = (event as CustomEvent).detail; + const finalColumn = finalPosition.column; + const finalY = finalPosition.snappedY; + const eventId = draggedElement.dataset.eventId || ''; + // Only handle day column drops for EventRenderer - if (target === 'swp-day-column') { - // Find both original element and dragged clone - const originalElement = document.querySelector(`swp-day-column swp-event[data-event-id="${eventId}"]`) as HTMLElement; + if (target === 'swp-day-column' && finalColumn) { + // Find dragged clone - use draggedElement as original const draggedClone = document.querySelector(`swp-day-column swp-event[data-event-id="clone-${eventId}"]`) as HTMLElement; - - if (originalElement && draggedClone && this.strategy.handleDragEnd) { - this.strategy.handleDragEnd(eventId, originalElement, draggedClone, finalColumn, finalY); + + if (draggedElement && draggedClone && this.strategy.handleDragEnd) { + this.strategy.handleDragEnd(eventId, draggedElement, draggedClone, finalColumn, finalY); } } - + // Clean up any remaining day event clones const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`); if (dayEventClone) { @@ -200,22 +201,40 @@ export class EventRenderingService { // Handle click (when drag threshold not reached) this.eventBus.on('event:click', (event: Event) => { - const { eventId } = (event as CustomEvent).detail; - // Find element dynamically - const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement; - if (originalElement && this.strategy.handleEventClick) { - this.strategy.handleEventClick(eventId, originalElement); + const { draggedElement } = (event as CustomEvent).detail; + // Use draggedElement directly - no need for DOM query + if (draggedElement && this.strategy.handleEventClick) { + const eventId = draggedElement.dataset.eventId || ''; + this.strategy.handleEventClick(eventId, draggedElement); } }); // Handle column change this.eventBus.on('drag:column-change', (event: Event) => { - const { eventId, newColumn } = (event as CustomEvent).detail; + const { draggedElement, newColumn } = (event as CustomEvent).detail; if (this.strategy.handleColumnChange) { + const eventId = draggedElement.dataset.eventId || ''; this.strategy.handleColumnChange(eventId, newColumn); } }); + + this.dragMouseLeaveHeaderListener = (event: Event) => { + const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent).detail; + + if (cloneElement) + cloneElement.style.display = ''; + + console.log('๐Ÿšช EventRendererManager: Received drag:mouseleave-header', { + targetDate, + originalElement: originalElement, + cloneElement: cloneElement + }); + + }; + + this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); + // Handle navigation period change this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => { // Delegate to strategy if it handles navigation @@ -232,46 +251,46 @@ export class EventRenderingService { // Use the provided draggedElement directly const allDayClone = draggedElement; const draggedEventId = draggedElement?.dataset.eventId?.replace('clone-', '') || ''; - - + + // Use SwpEventElement factory to create day event from all-day event const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement); const dayElement = dayEventElement.getElement(); - + // Remove the all-day clone - it's no longer needed since we're converting to day event allDayClone.remove(); - + // Set clone ID dayElement.dataset.eventId = `clone-${draggedEventId}`; - + // Find target column const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`); if (!columnElement) { console.warn('EventRendererManager: Target column not found', { column }); return; } - + // Find events layer in the column const eventsLayer = columnElement.querySelector('swp-events-layer'); if (!eventsLayer) { console.warn('EventRendererManager: Events layer not found in column'); return; } - + // Add to events layer eventsLayer.appendChild(dayElement); - + // Position based on mouse Y coordinate const columnRect = columnElement.getBoundingClientRect(); const relativeY = Math.max(0, mousePosition.y - columnRect.top); dayElement.style.top = `${relativeY}px`; - + // Set drag styling dayElement.style.zIndex = '1000'; dayElement.style.cursor = 'grabbing'; dayElement.style.opacity = ''; dayElement.style.transform = ''; - + console.log('โœ… EventRendererManager: Converted all-day event to time event', { draggedEventId, column, diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts index e14397c..bb3a68d 100644 --- a/src/renderers/GridRenderer.ts +++ b/src/renderers/GridRenderer.ts @@ -3,7 +3,6 @@ import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes'; import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { ColumnRenderContext } from './ColumnRenderer'; import { eventBus } from '../core/EventBus'; -import { DateCalculator } from '../utils/DateCalculator'; /** * GridRenderer - Centralized DOM rendering for calendar grid @@ -37,7 +36,7 @@ export class GridRenderer { if (grid.children.length === 0) { this.createCompleteGridStructure(grid, currentDate, resourceData, view); // Setup grid-related event listeners on first render - this.setupGridEventListeners(); + // this.setupGridEventListeners(); } else { // Optimized update - only refresh dynamic content this.updateGridContent(grid, currentDate, resourceData, view); @@ -169,15 +168,15 @@ export class GridRenderer { /** * Setup grid-only event listeners (column events) - */ + private setupGridEventListeners(): void { // Setup grid body mouseover listener for all-day to timed conversion this.setupGridBodyMouseOver(); } - + */ /** * Setup grid body mouseover listener for all-day to timed conversion - */ + private setupGridBodyMouseOver(): void { const grid = this.cachedGridContainer; if (!grid) return; @@ -221,15 +220,15 @@ export class GridRenderer { (this as any).gridBodyEventListener = gridBodyEventListener; (this as any).cachedColumnContainer = columnContainer; } - +*/ /** * Clean up cached elements and event listeners */ public destroy(): void { // Clean up grid-only event listeners - if ((this as any).gridBodyEventListener && (this as any).cachedColumnContainer) { - (this as any).cachedColumnContainer.removeEventListener('mouseover', (this as any).gridBodyEventListener); - } + // if ((this as any).gridBodyEventListener && (this as any).cachedColumnContainer) { + // (this as any).cachedColumnContainer.removeEventListener('mouseover', (this as any).gridBodyEventListener); + //} // Clear cached references this.cachedGridContainer = null; diff --git a/src/types/EventTypes.ts b/src/types/EventTypes.ts index 60d0ac5..3708def 100644 --- a/src/types/EventTypes.ts +++ b/src/types/EventTypes.ts @@ -30,4 +30,59 @@ export interface TimeEvent { }; } -export type CalendarEventData = AllDayEvent | TimeEvent; \ No newline at end of file +export type CalendarEventData = AllDayEvent | TimeEvent; + +/** + * Drag Event Payload Interfaces + * Type-safe interfaces for drag and drop events + */ + +// Common position interface +export interface MousePosition { + x: number; + y: number; +} + +// Drag start event payload +export interface DragStartEventPayload { + draggedElement: HTMLElement; + mousePosition: MousePosition; + mouseOffset: MousePosition; + column: string | null; +} + +// Drag move event payload +export interface DragMoveEventPayload { + draggedElement: HTMLElement; + mousePosition: MousePosition; + mouseOffset: MousePosition; + snappedY: number; + column: string | null; +} + +// Drag end event payload +export interface DragEndEventPayload { + draggedElement: HTMLElement; + mousePosition: MousePosition; + finalPosition: { + column: string | null; + snappedY: number; + }; + target: 'swp-day-column' | 'swp-day-header' | null; +} + +// Drag mouse enter header event payload +export interface DragMouseEnterHeaderEventPayload { + targetDate: string; + mousePosition: MousePosition; + originalElement: HTMLElement | null; + cloneElement: HTMLElement | null; +} + +// Drag mouse leave header event payload +export interface DragMouseLeaveHeaderEventPayload { + targetDate: string | null; + mousePosition: MousePosition; + originalElement: HTMLElement| null; + cloneElement: HTMLElement| null; +} \ No newline at end of file