diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts
index baec945..0afbd87 100644
--- a/src/managers/CalendarManager.ts
+++ b/src/managers/CalendarManager.ts
@@ -4,6 +4,7 @@ import { calendarConfig } from '../core/CalendarConfig.js';
import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js';
import { EventManager } from './EventManager.js';
import { GridManager } from './GridManager.js';
+import { HeaderManager } from './HeaderManager.js';
import { EventRenderingService } from '../renderers/EventRendererManager.js';
import { ScrollManager } from './ScrollManager.js';
import { DateCalculator } from '../utils/DateCalculator.js';
@@ -17,6 +18,7 @@ export class CalendarManager {
private eventBus: IEventBus;
private eventManager: EventManager;
private gridManager: GridManager;
+ private headerManager: HeaderManager;
private eventRenderer: EventRenderingService;
private scrollManager: ScrollManager;
private eventFilterManager: EventFilterManager;
@@ -35,6 +37,7 @@ export class CalendarManager {
this.eventBus = eventBus;
this.eventManager = eventManager;
this.gridManager = gridManager;
+ this.headerManager = new HeaderManager();
this.eventRenderer = eventRenderer;
this.scrollManager = scrollManager;
this.eventFilterManager = new EventFilterManager();
@@ -66,6 +69,9 @@ export class CalendarManager {
}
await this.gridManager.render();
+ // Step 2a: Setup header drag listeners after grid render (when DOM is available)
+ this.headerManager.setupHeaderDragListeners();
+
// Step 2b: Trigger event rendering now that data is loaded
// Re-emit GRID_RENDERED to trigger EventRendererManager
const gridContainer = document.querySelector('swp-calendar-container');
diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts
index a12736e..d48783d 100644
--- a/src/managers/DragDropManager.ts
+++ b/src/managers/DragDropManager.ts
@@ -22,7 +22,6 @@ export class DragDropManager {
private eventBus: IEventBus;
// Mouse tracking with optimized state
- private isMouseDown = false;
private lastMousePosition: Position = { x: 0, y: 0 };
private lastLoggedPosition: Position = { x: 0, y: 0 };
private currentMouseY = 0;
@@ -96,7 +95,7 @@ export class DragDropManager {
this.eventBus.on('header:mouseover', (event) => {
const { element, targetDate, headerRenderer } = (event as CustomEvent).detail;
- if (this.isMouseDown && this.draggedEventId && targetDate) {
+ if (this.draggedEventId && targetDate) {
// Emit event to convert to all-day
this.eventBus.emit('drag:convert-to-allday', {
eventId: this.draggedEventId,
@@ -106,10 +105,48 @@ export class DragDropManager {
});
}
});
+
+ // 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 ((event as any).buttons === 1 && 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 (for all-day to timed conversion when leaving header)
+ this.eventBus.on('header:mouseleave', (event) => {
+ // Check if we're dragging an all-day event
+ if ((event as any).buttons === 1 && this.draggedEventId && this.isAllDayEventBeingDragged()) {
+ // Get current mouse position to determine target column and Y
+ const currentColumn = this.detectColumn(this.lastMousePosition.x, this.lastMousePosition.y);
+
+ if (currentColumn) {
+ // Calculate Y position relative to the column
+ const columnElement = this.getCachedColumnElement(currentColumn);
+ if (columnElement) {
+ const columnRect = columnElement.getBoundingClientRect();
+ const targetY = this.lastMousePosition.y - columnRect.top - this.mouseOffset.y;
+
+ // Emit event to convert to timed
+ this.eventBus.emit('drag:convert-to-timed', {
+ eventId: this.draggedEventId,
+ targetColumn: currentColumn,
+ targetY: Math.max(0, targetY)
+ });
+ }
+ }
+ }
+ });
}
private handleMouseDown(event: MouseEvent): void {
- this.isMouseDown = true;
this.isDragStarted = false;
this.lastMousePosition = { x: event.clientX, y: event.clientY };
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
@@ -160,7 +197,7 @@ export class DragDropManager {
private handleMouseMove(event: MouseEvent): void {
this.currentMouseY = event.clientY;
- if (this.isMouseDown && this.draggedEventId) {
+ if (event.buttons === 1 && this.draggedEventId) {
const currentPosition: Position = { x: event.clientX, y: event.clientY };
// Check if we need to start drag (movement threshold)
@@ -230,23 +267,27 @@ export class DragDropManager {
* Optimized mouse up handler with consolidated cleanup
*/
private handleMouseUp(event: MouseEvent): void {
- if (!this.isMouseDown) return;
-
- this.isMouseDown = false;
this.stopAutoScroll();
if (this.draggedEventId && this.originalElement) {
+ // Store variables locally before cleanup
+ const eventId = this.draggedEventId;
+ const originalElement = this.originalElement;
+ const isDragStarted = this.isDragStarted;
+
+ // Clean up drag state first
+ this.cleanupDragState();
+
// Only emit drag:end if drag was actually started
- if (this.isDragStarted) {
+ if (isDragStarted) {
const finalPosition: Position = { x: event.clientX, y: event.clientY };
// Use consolidated position calculation
const positionData = this.calculateDragPosition(finalPosition);
- // Emit drag end event
this.eventBus.emit('drag:end', {
- eventId: this.draggedEventId,
- originalElement: this.originalElement,
+ eventId: eventId,
+ originalElement: originalElement,
finalPosition,
finalColumn: positionData.column,
finalY: positionData.snappedY
@@ -254,14 +295,11 @@ export class DragDropManager {
} else {
// This was just a click - emit click event instead
this.eventBus.emit('event:click', {
- eventId: this.draggedEventId,
- originalElement: this.originalElement,
+ eventId: eventId,
+ originalElement: originalElement,
mousePosition: { x: event.clientX, y: event.clientY }
});
}
-
- // Clean up drag state
- this.cleanupDragState();
}
}
@@ -409,7 +447,7 @@ export class DragDropManager {
if (this.autoScrollAnimationId !== null) return;
const scroll = () => {
- if (!this.cachedElements.scrollContainer || !this.isMouseDown) {
+ if (!this.cachedElements.scrollContainer || !this.draggedEventId) {
this.stopAutoScroll();
return;
}
@@ -466,6 +504,14 @@ export class DragDropManager {
this.cachedElements.lastColumnDate = null;
}
+ /**
+ * Check if an all-day event is currently being dragged
+ */
+ private isAllDayEventBeingDragged(): boolean {
+ if (!this.originalElement) return false;
+ return this.originalElement.tagName === 'SWP-ALLDAY-EVENT';
+ }
+
/**
* Clean up all resources and event listeners
*/
diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts
new file mode 100644
index 0000000..928edd1
--- /dev/null
+++ b/src/managers/HeaderManager.ts
@@ -0,0 +1,139 @@
+import { eventBus } from '../core/EventBus';
+import { calendarConfig } from '../core/CalendarConfig';
+import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
+
+/**
+ * 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;
+
+ constructor() {
+ // Bind methods for event listeners
+ this.setupHeaderDragListeners = this.setupHeaderDragListeners.bind(this);
+ this.destroy = this.destroy.bind(this);
+ }
+
+ /**
+ * Get cached calendar header element
+ */
+ private getCalendarHeader(): HTMLElement | null {
+ if (!this.cachedCalendarHeader) {
+ this.cachedCalendarHeader = document.querySelector('swp-calendar-header');
+ }
+ return this.cachedCalendarHeader;
+ }
+
+ /**
+ * Setup header drag event listeners
+ */
+ public setupHeaderDragListeners(): void {
+ const calendarHeader = this.getCalendarHeader();
+ if (!calendarHeader) return;
+
+ // Clean up existing listeners first
+ this.removeEventListeners();
+
+ // Throttle for better performance
+ let lastEmitTime = 0;
+ const throttleDelay = 16; // ~60fps
+
+ this.headerEventListener = (event: Event) => {
+ const now = Date.now();
+ if (now - lastEmitTime < throttleDelay) {
+ return; // Throttle events for better performance
+ }
+ lastEmitTime = now;
+
+ const target = event.target as HTMLElement;
+
+ // Optimized element detection
+ const dayHeader = target.closest('swp-day-header');
+ const allDayContainer = target.closest('swp-allday-container');
+
+ if (dayHeader || allDayContainer) {
+ let hoveredElement: HTMLElement;
+ let targetDate: string | undefined;
+
+ if (dayHeader) {
+ hoveredElement = dayHeader as HTMLElement;
+ targetDate = hoveredElement.dataset.date;
+ } else if (allDayContainer) {
+ hoveredElement = allDayContainer as HTMLElement;
+
+ // Optimized day calculation using cached header rect
+ const headerRect = calendarHeader.getBoundingClientRect();
+ const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
+ const mouseX = (event as MouseEvent).clientX - headerRect.left;
+ const dayWidth = headerRect.width / dayHeaders.length;
+ const dayIndex = Math.max(0, Math.min(dayHeaders.length - 1, Math.floor(mouseX / dayWidth)));
+
+ const targetDayHeader = dayHeaders[dayIndex] as HTMLElement;
+ targetDate = targetDayHeader?.dataset.date;
+ } else {
+ return;
+ }
+
+ // Get header renderer for coordination
+ const calendarType = calendarConfig.getCalendarMode();
+ const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
+
+ eventBus.emit('header:mouseover', {
+ element: hoveredElement,
+ targetDate,
+ headerRenderer
+ });
+ }
+ };
+
+ // Header mouseleave listener
+ this.headerMouseLeaveListener = (event: Event) => {
+ eventBus.emit('header:mouseleave', {
+ element: event.target as HTMLElement
+ });
+ };
+
+ // Add event listeners
+ calendarHeader.addEventListener('mouseover', this.headerEventListener);
+ calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener);
+ }
+
+ /**
+ * Remove event listeners from header
+ */
+ private removeEventListeners(): void {
+ const calendarHeader = this.getCalendarHeader();
+ if (!calendarHeader) return;
+
+ if (this.headerEventListener) {
+ calendarHeader.removeEventListener('mouseover', this.headerEventListener);
+ }
+
+ if (this.headerMouseLeaveListener) {
+ calendarHeader.removeEventListener('mouseleave', this.headerMouseLeaveListener);
+ }
+ }
+
+ /**
+ * Clear cached header reference
+ */
+ public clearCache(): void {
+ this.cachedCalendarHeader = null;
+ }
+
+ /**
+ * Clean up resources and event listeners
+ */
+ public destroy(): void {
+ this.removeEventListeners();
+
+ // Clear references
+ this.headerEventListener = null;
+ this.headerMouseLeaveListener = null;
+
+ this.clearCache();
+ }
+}
\ No newline at end of file
diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts
index f592955..d7b2770 100644
--- a/src/renderers/EventRenderer.ts
+++ b/src/renderers/EventRenderer.ts
@@ -140,6 +140,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
this.handleConvertToAllDay(eventId, targetDate, headerRenderer);
});
+ // Handle convert to timed event
+ eventBus.on('drag:convert-to-timed', (event) => {
+ const { eventId, targetColumn, targetY } = (event as CustomEvent).detail;
+ this.handleConvertToTimed(eventId, targetColumn, targetY);
+ });
+
// Handle navigation period change (when slide animation completes)
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
// Animate all-day height after navigation completes
@@ -183,6 +189,45 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
return 60;
}
+ /**
+ * Apply common drag styling to an element
+ */
+ private applyDragStyling(element: HTMLElement): void {
+ element.style.position = 'absolute';
+ element.style.zIndex = '999999';
+ element.style.pointerEvents = 'none';
+ element.style.opacity = '0.8';
+ element.style.left = '2px';
+ element.style.right = '2px';
+ element.style.marginLeft = '0px';
+ element.style.width = '';
+ }
+
+ /**
+ * Create event inner structure (swp-event-time and swp-event-title)
+ */
+ private createEventInnerStructure(event: CalendarEvent): string {
+ const startTime = this.formatTime(event.start);
+ const endTime = this.formatTime(event.end);
+ const durationMinutes = (event.end.getTime() - event.start.getTime()) / (1000 * 60);
+
+ return `
+ ${startTime} - ${endTime}
+ ${event.title}
+ `;
+ }
+
+ /**
+ * Apply standard event positioning
+ */
+ private applyEventPositioning(element: HTMLElement, top: number, height: number): void {
+ element.style.position = 'absolute';
+ element.style.top = `${top}px`;
+ element.style.height = `${height}px`;
+ element.style.left = '2px';
+ element.style.right = '2px';
+ }
+
/**
* Create a clone of an event for dragging
*/
@@ -199,18 +244,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
const originalDurationMinutes = this.getOriginalEventDuration(originalEvent);
clone.dataset.originalDuration = originalDurationMinutes.toString();
+ // Apply common drag styling
+ this.applyDragStyling(clone);
- // Style for dragging
- clone.style.position = 'absolute';
- clone.style.zIndex = '999999';
- clone.style.pointerEvents = 'none';
- clone.style.opacity = '0.8';
-
- // Dragged event skal have fuld kolonne bredde
- clone.style.left = '2px';
- clone.style.right = '2px';
- clone.style.marginLeft = '0px';
- clone.style.width = '';
+ // Set height from original event
clone.style.height = originalEvent.style.height || `${originalEvent.getBoundingClientRect().height}px`;
return clone;
@@ -220,6 +257,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
* Update clone timestamp based on new position
*/
private updateCloneTimestamp(clone: HTMLElement, snappedY: number): void {
+
+ //important as events can pile up, so they will still fire after event has been converted to another rendered type
+ if(clone.dataset.allDay == "true") return;
+
const gridSettings = calendarConfig.getGridSettings();
const hourHeight = gridSettings.hourHeight;
const dayStartHour = gridSettings.dayStartHour;
@@ -248,7 +289,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
clone.dataset.start = startDate.toISOString();
clone.dataset.end = endDate.toISOString();
-
// Update display
const timeElement = clone.querySelector('swp-event-time');
if (timeElement) {
@@ -283,7 +323,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
* Handle drag start event
*/
private handleDragStart(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void {
- console.log('handleDragStart:', eventId);
this.originalEvent = originalElement;
// Remove stacking styling during drag will be handled by new system
@@ -346,10 +385,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
* Handle drag end event
*/
private handleDragEnd(eventId: string, originalElement: HTMLElement, finalColumn: string, finalY: number): void {
- console.log('handleDragEnd:', eventId);
if (!this.draggedClone || !this.originalEvent) {
- console.log('Missing draggedClone or originalEvent');
+ console.warn('Missing draggedClone or originalEvent');
return;
}
@@ -443,11 +481,13 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
this.draggedClone.style.userSelect = '';
// Behold z-index hvis det er et stacked event
- // Update dataset with new times after successful drop
- const newEvent = this.elementToCalendarEvent(this.draggedClone);
- if (newEvent) {
- this.draggedClone.dataset.start = newEvent.start.toISOString();
- this.draggedClone.dataset.end = newEvent.end.toISOString();
+ // Update dataset with new times after successful drop (only for timed events)
+ if (this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
+ const newEvent = this.elementToCalendarEvent(this.draggedClone);
+ if (newEvent) {
+ this.draggedClone.dataset.start = newEvent.start.toISOString();
+ this.draggedClone.dataset.end = newEvent.end.toISOString();
+ }
}
// Detect overlaps with other events in the target column and reposition if needed
@@ -687,12 +727,15 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
const allDayEvent = document.createElement('swp-allday-event');
allDayEvent.dataset.eventId = clone.dataset.eventId || '';
allDayEvent.dataset.title = eventTitle;
- allDayEvent.dataset.start = `${targetDate}T${eventTime.split(' - ')[0]}:00`;
- allDayEvent.dataset.end = `${targetDate}T${eventTime.split(' - ')[1]}:00`;
+ allDayEvent.dataset.start = `${targetDate}T00:00:00`;
+ allDayEvent.dataset.end = `${targetDate}T23:59:59`;
allDayEvent.dataset.type = clone.dataset.type || 'work';
allDayEvent.dataset.duration = eventDuration;
+ allDayEvent.dataset.allDay = "true";
+
allDayEvent.textContent = eventTitle;
-
+
+ console.log("allDayEvent", allDayEvent.dataset);
// Position in grid
(allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString();
// grid-row will be set by checkAndAnimateAllDayHeight() based on actual position
@@ -711,8 +754,101 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// Check if height animation is needed
this.triggerAllDayHeightAnimation();
}
-
-
+
+ /**
+ * Handle conversion from all-day to timed event
+ */
+ private handleConvertToTimed(eventId: string, targetColumn: string, targetY: number): void {
+ if (!this.draggedClone) return;
+
+ // Only convert if it's an all-day event
+ if (this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') return;
+
+ // Transform clone to timed format
+ this.transformAllDayToTimed(this.draggedClone, targetColumn, targetY);
+ }
+
+ /**
+ * Transform clone from all-day to timed event
+ */
+ private transformAllDayToTimed(allDayClone: HTMLElement, targetColumn: string, targetY: number): void {
+ // Find target column element
+ const columnElement = document.querySelector(`swp-day-column[data-date="${targetColumn}"]`);
+ if (!columnElement) return;
+
+ const eventsLayer = columnElement.querySelector('swp-events-layer');
+ if (!eventsLayer) return;
+
+ // Extract event data from all-day element
+ const eventId = allDayClone.dataset.eventId || '';
+ const eventTitle = allDayClone.dataset.title || allDayClone.textContent || 'Untitled';
+ const eventType = allDayClone.dataset.type || 'work';
+
+ // Calculate time from Y position
+ const gridSettings = calendarConfig.getGridSettings();
+ const hourHeight = gridSettings.hourHeight;
+ const dayStartHour = gridSettings.dayStartHour;
+ const snapInterval = gridSettings.snapInterval;
+
+ // Calculate start time from position
+ const minutesFromGridStart = (targetY / hourHeight) * 60;
+ const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart;
+ const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval;
+
+ // Use default duration or extract from dataset
+ const duration = parseInt(allDayClone.dataset.duration || '60');
+ const endMinutes = snappedStartMinutes + duration;
+
+ // Create dates with target column date
+ const columnDate = new Date(targetColumn + 'T00:00:00');
+ const startDate = new Date(columnDate);
+ startDate.setMinutes(snappedStartMinutes);
+
+ const endDate = new Date(columnDate);
+ endDate.setMinutes(endMinutes);
+
+ // Create CalendarEvent object for helper methods
+ const tempEvent: CalendarEvent = {
+ id: eventId,
+ title: eventTitle,
+ start: startDate,
+ end: endDate,
+ type: eventType,
+ allDay: false,
+ syncStatus: 'synced',
+ metadata: {
+ duration: duration
+ }
+ };
+
+ // Create timed event element
+ const timedEvent = document.createElement('swp-event');
+ timedEvent.dataset.eventId = eventId;
+ timedEvent.dataset.title = eventTitle;
+ timedEvent.dataset.type = eventType;
+ timedEvent.dataset.start = startDate.toISOString();
+ timedEvent.dataset.end = endDate.toISOString();
+ timedEvent.dataset.duration = duration.toString();
+ timedEvent.dataset.originalDuration = duration.toString();
+
+ // Create inner structure using helper method
+ timedEvent.innerHTML = this.createEventInnerStructure(tempEvent);
+
+ // Apply drag styling and positioning
+ this.applyDragStyling(timedEvent);
+ const eventHeight = (duration / 60) * hourHeight - 3;
+ timedEvent.style.height = `${eventHeight}px`;
+ timedEvent.style.top = `${targetY}px`;
+
+ // Remove all-day element
+ allDayClone.remove();
+
+ // Add timed event to events layer
+ eventsLayer.appendChild(timedEvent);
+
+ // Update reference
+ this.draggedClone = timedEvent;
+ }
/**
* Fade out and remove element
@@ -872,26 +1008,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
eventElement.dataset.type = event.type;
eventElement.dataset.duration = event.metadata?.duration?.toString() || '60';
- // Calculate position based on time
+ // Calculate and apply position based on time
const position = this.calculateEventPosition(event);
- eventElement.style.position = 'absolute';
- eventElement.style.top = `${position.top + 1}px`;
- eventElement.style.height = `${position.height - 3}px`; //adjusted so bottom does not cover horizontal time lines.
+ this.applyEventPositioning(eventElement, position.top + 1, position.height - 3);
// Color is now handled by CSS classes based on data-type attribute
- // Format time for display using unified method
- const startTime = this.formatTime(event.start);
- const endTime = this.formatTime(event.end);
-
- // Calculate duration in minutes
- const durationMinutes = (event.end.getTime() - event.start.getTime()) / (1000 * 60);
-
- // Create event content
- eventElement.innerHTML = `
- ${startTime} - ${endTime}
- ${event.title}
- `;
+ // Create event content using helper method
+ eventElement.innerHTML = this.createEventInnerStructure(event);
// Setup resize handles on first mouseover only
eventElement.addEventListener('mouseover', () => {
diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts
index b3848b4..3a8c38a 100644
--- a/src/renderers/GridRenderer.ts
+++ b/src/renderers/GridRenderer.ts
@@ -11,7 +11,6 @@ import { DateCalculator } from '../utils/DateCalculator';
* Optimized to reduce redundant DOM operations and improve performance
*/
export class GridRenderer {
- private headerEventListener: ((event: Event) => void) | null = null;
private cachedGridContainer: HTMLElement | null = null;
private cachedCalendarHeader: HTMLElement | null = null;
private cachedTimeAxis: HTMLElement | null = null;
@@ -158,8 +157,8 @@ export class GridRenderer {
// Always ensure all-day containers exist for all days
headerRenderer.ensureAllDayContainers(calendarHeader);
- // Setup optimized event listener
- this.setupOptimizedHeaderEventListener(calendarHeader);
+ // Setup only grid-related event listeners
+ this.setupGridEventListeners();
}
/**
@@ -209,83 +208,74 @@ export class GridRenderer {
}
/**
- * Setup optimized event delegation listener with better performance
+ * Setup grid-only event listeners (column events)
*/
- private setupOptimizedHeaderEventListener(calendarHeader: HTMLElement): void {
- // Remove existing listener if any
- if (this.headerEventListener) {
- calendarHeader.removeEventListener('mouseover', this.headerEventListener);
- }
+ private setupGridEventListeners(): void {
+ // Setup grid body mouseover listener for all-day to timed conversion
+ this.setupGridBodyMouseOver();
+ }
- // Create optimized listener with throttling
+ /**
+ * Setup grid body mouseover listener for all-day to timed conversion
+ */
+ private setupGridBodyMouseOver(): void {
+ const grid = this.cachedGridContainer;
+ if (!grid) return;
+
+ const columnContainer = grid.querySelector('swp-day-columns');
+ if (!columnContainer) return;
+
+ // Throttle for better performance
let lastEmitTime = 0;
const throttleDelay = 16; // ~60fps
-
- this.headerEventListener = (event) => {
+
+ const gridBodyEventListener = (event: Event) => {
const now = Date.now();
if (now - lastEmitTime < throttleDelay) {
- return; // Throttle events for better performance
+ return;
}
lastEmitTime = now;
-
+
const target = event.target as HTMLElement;
+ const dayColumn = target.closest('swp-day-column');
- // Optimized element detection
- const dayHeader = target.closest('swp-day-header');
- const allDayContainer = target.closest('swp-allday-container');
-
- if (dayHeader || allDayContainer) {
- let hoveredElement: HTMLElement;
- let targetDate: string | undefined;
-
- if (dayHeader) {
- hoveredElement = dayHeader as HTMLElement;
- targetDate = hoveredElement.dataset.date;
- } else if (allDayContainer) {
- hoveredElement = allDayContainer as HTMLElement;
+ if (dayColumn) {
+ const targetColumn = (dayColumn as HTMLElement).dataset.date;
+ if (targetColumn) {
+ // Calculate Y position relative to the column
+ const columnRect = dayColumn.getBoundingClientRect();
+ const mouseY = (event as MouseEvent).clientY;
+ const targetY = mouseY - columnRect.top;
- // Optimized day calculation using cached header rect
- const headerRect = calendarHeader.getBoundingClientRect();
- const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
- const mouseX = (event as MouseEvent).clientX - headerRect.left;
- const dayWidth = headerRect.width / dayHeaders.length;
- const dayIndex = Math.max(0, Math.min(dayHeaders.length - 1, Math.floor(mouseX / dayWidth)));
-
- const targetDayHeader = dayHeaders[dayIndex] as HTMLElement;
- targetDate = targetDayHeader?.dataset.date;
- } else {
- return;
+ eventBus.emit('column:mouseover', {
+ targetColumn,
+ targetY
+ });
}
-
- // Get header renderer once and cache
- const calendarType = calendarConfig.getCalendarMode();
- const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
-
- eventBus.emit('header:mouseover', {
- element: hoveredElement,
- targetDate,
- headerRenderer
- });
}
};
-
- // Add the optimized listener
- calendarHeader.addEventListener('mouseover', this.headerEventListener);
+
+ columnContainer.addEventListener('mouseover', gridBodyEventListener);
+
+ // Store reference for cleanup
+ (this as any).gridBodyEventListener = gridBodyEventListener;
+ (this as any).cachedColumnContainer = columnContainer;
}
/**
* Clean up cached elements and event listeners
*/
public destroy(): void {
- // Clean up event listeners
- if (this.headerEventListener && this.cachedCalendarHeader) {
- this.cachedCalendarHeader.removeEventListener('mouseover', this.headerEventListener);
+ // 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);
}
// Clear cached references
this.cachedGridContainer = null;
this.cachedCalendarHeader = null;
this.cachedTimeAxis = null;
- this.headerEventListener = null;
+ (this as any).gridBodyEventListener = null;
+ (this as any).cachedColumnContainer = null;
}
}
\ No newline at end of file
diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts
index 300de31..1af5bfe 100644
--- a/src/renderers/NavigationRenderer.ts
+++ b/src/renderers/NavigationRenderer.ts
@@ -4,7 +4,6 @@ import { calendarConfig } from '../core/CalendarConfig';
import { DateCalculator } from '../utils/DateCalculator';
import { EventRenderingService } from './EventRendererManager';
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
-import { eventBus } from '../core/EventBus';
/**
* NavigationRenderer - Handles DOM rendering for navigation containers
@@ -12,8 +11,6 @@ import { eventBus } from '../core/EventBus';
*/
export class NavigationRenderer {
private eventBus: IEventBus;
- private dateCalculator: DateCalculator;
- private eventRenderer: EventRenderingService;
// Cached DOM elements to avoid redundant queries
private cachedWeekNumberElement: HTMLElement | null = null;
@@ -21,9 +18,7 @@ export class NavigationRenderer {
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) {
this.eventBus = eventBus;
- this.eventRenderer = eventRenderer;
DateCalculator.initialize(calendarConfig);
- this.dateCalculator = new DateCalculator();
this.setupEventListeners();
}
@@ -202,9 +197,6 @@ export class NavigationRenderer {
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarConfig.getCalendarMode());
headerRenderer.ensureAllDayContainers(header as HTMLElement);
- // Add event delegation listener for drag & drop functionality
- this.setupHeaderEventListener(header as HTMLElement);
-
// Render day columns for target week
dates.forEach(date => {
const column = document.createElement('swp-day-column');
@@ -217,55 +209,6 @@ export class NavigationRenderer {
});
}
- /**
- * Setup event delegation listener for header mouseover (same logic as GridRenderer)
- */
- private setupHeaderEventListener(calendarHeader: HTMLElement): void {
- calendarHeader.addEventListener('mouseover', (event) => {
- const target = event.target as HTMLElement;
-
- // Check what was hovered - could be day-header OR all-day-container
- const dayHeader = target.closest('swp-day-header');
- const allDayContainer = target.closest('swp-allday-container');
-
- if (dayHeader || allDayContainer) {
- let hoveredElement: HTMLElement;
- let targetDate: string | undefined;
-
- if (dayHeader) {
- hoveredElement = dayHeader as HTMLElement;
- targetDate = hoveredElement.dataset.date;
- } else if (allDayContainer) {
- // For all-day areas, we need to determine which day column we're over
- hoveredElement = allDayContainer as HTMLElement;
-
- // Calculate which day we're hovering over based on mouse position
- const headerRect = calendarHeader.getBoundingClientRect();
- const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
- const mouseX = (event as MouseEvent).clientX - headerRect.left;
- const dayWidth = headerRect.width / dayHeaders.length;
- const dayIndex = Math.floor(mouseX / dayWidth);
-
- const targetDayHeader = dayHeaders[dayIndex] as HTMLElement;
- targetDate = targetDayHeader?.dataset.date;
- } else {
- return; // No valid element found
- }
-
-
- // Get the header renderer for addToAllDay functionality
- const calendarType = calendarConfig.getCalendarMode();
- const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
-
- eventBus.emit('header:mouseover', {
- element: hoveredElement,
- targetDate,
- headerRenderer
- });
- }
- });
- }
-
/**
* Public cleanup method for cached elements
*/