Enhances drag and drop functionality
Improves drag and drop event handling, including conversion between all-day and timed events. Introduces HeaderManager to handle header-related event logic and centralizes header event handling for better code organization and separation of concerns. Optimizes event listeners and throttles events for improved performance. Removes redundant code and improves the overall drag and drop experience.
This commit is contained in:
parent
d087e333fe
commit
3bd74d6f4e
6 changed files with 418 additions and 170 deletions
|
|
@ -4,6 +4,7 @@ import { calendarConfig } from '../core/CalendarConfig.js';
|
||||||
import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js';
|
import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js';
|
||||||
import { EventManager } from './EventManager.js';
|
import { EventManager } from './EventManager.js';
|
||||||
import { GridManager } from './GridManager.js';
|
import { GridManager } from './GridManager.js';
|
||||||
|
import { HeaderManager } from './HeaderManager.js';
|
||||||
import { EventRenderingService } from '../renderers/EventRendererManager.js';
|
import { EventRenderingService } from '../renderers/EventRendererManager.js';
|
||||||
import { ScrollManager } from './ScrollManager.js';
|
import { ScrollManager } from './ScrollManager.js';
|
||||||
import { DateCalculator } from '../utils/DateCalculator.js';
|
import { DateCalculator } from '../utils/DateCalculator.js';
|
||||||
|
|
@ -17,6 +18,7 @@ export class CalendarManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private eventManager: EventManager;
|
private eventManager: EventManager;
|
||||||
private gridManager: GridManager;
|
private gridManager: GridManager;
|
||||||
|
private headerManager: HeaderManager;
|
||||||
private eventRenderer: EventRenderingService;
|
private eventRenderer: EventRenderingService;
|
||||||
private scrollManager: ScrollManager;
|
private scrollManager: ScrollManager;
|
||||||
private eventFilterManager: EventFilterManager;
|
private eventFilterManager: EventFilterManager;
|
||||||
|
|
@ -35,6 +37,7 @@ export class CalendarManager {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.eventManager = eventManager;
|
this.eventManager = eventManager;
|
||||||
this.gridManager = gridManager;
|
this.gridManager = gridManager;
|
||||||
|
this.headerManager = new HeaderManager();
|
||||||
this.eventRenderer = eventRenderer;
|
this.eventRenderer = eventRenderer;
|
||||||
this.scrollManager = scrollManager;
|
this.scrollManager = scrollManager;
|
||||||
this.eventFilterManager = new EventFilterManager();
|
this.eventFilterManager = new EventFilterManager();
|
||||||
|
|
@ -66,6 +69,9 @@ export class CalendarManager {
|
||||||
}
|
}
|
||||||
await this.gridManager.render();
|
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
|
// Step 2b: Trigger event rendering now that data is loaded
|
||||||
// Re-emit GRID_RENDERED to trigger EventRendererManager
|
// Re-emit GRID_RENDERED to trigger EventRendererManager
|
||||||
const gridContainer = document.querySelector('swp-calendar-container');
|
const gridContainer = document.querySelector('swp-calendar-container');
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ export class DragDropManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
|
|
||||||
// Mouse tracking with optimized state
|
// Mouse tracking with optimized state
|
||||||
private isMouseDown = false;
|
|
||||||
private lastMousePosition: Position = { x: 0, y: 0 };
|
private lastMousePosition: Position = { x: 0, y: 0 };
|
||||||
private lastLoggedPosition: Position = { x: 0, y: 0 };
|
private lastLoggedPosition: Position = { x: 0, y: 0 };
|
||||||
private currentMouseY = 0;
|
private currentMouseY = 0;
|
||||||
|
|
@ -96,7 +95,7 @@ export class DragDropManager {
|
||||||
this.eventBus.on('header:mouseover', (event) => {
|
this.eventBus.on('header:mouseover', (event) => {
|
||||||
const { element, targetDate, headerRenderer } = (event as CustomEvent).detail;
|
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
|
// Emit event to convert to all-day
|
||||||
this.eventBus.emit('drag:convert-to-allday', {
|
this.eventBus.emit('drag:convert-to-allday', {
|
||||||
eventId: this.draggedEventId,
|
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 {
|
private handleMouseDown(event: MouseEvent): void {
|
||||||
this.isMouseDown = true;
|
|
||||||
this.isDragStarted = false;
|
this.isDragStarted = false;
|
||||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
this.lastLoggedPosition = { 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 {
|
private handleMouseMove(event: MouseEvent): void {
|
||||||
this.currentMouseY = event.clientY;
|
this.currentMouseY = event.clientY;
|
||||||
|
|
||||||
if (this.isMouseDown && this.draggedEventId) {
|
if (event.buttons === 1 && this.draggedEventId) {
|
||||||
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Check if we need to start drag (movement threshold)
|
// Check if we need to start drag (movement threshold)
|
||||||
|
|
@ -230,23 +267,27 @@ export class DragDropManager {
|
||||||
* Optimized mouse up handler with consolidated cleanup
|
* Optimized mouse up handler with consolidated cleanup
|
||||||
*/
|
*/
|
||||||
private handleMouseUp(event: MouseEvent): void {
|
private handleMouseUp(event: MouseEvent): void {
|
||||||
if (!this.isMouseDown) return;
|
|
||||||
|
|
||||||
this.isMouseDown = false;
|
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
|
|
||||||
if (this.draggedEventId && this.originalElement) {
|
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
|
// Only emit drag:end if drag was actually started
|
||||||
if (this.isDragStarted) {
|
if (isDragStarted) {
|
||||||
const finalPosition: Position = { x: event.clientX, y: event.clientY };
|
const finalPosition: Position = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Use consolidated position calculation
|
// Use consolidated position calculation
|
||||||
const positionData = this.calculateDragPosition(finalPosition);
|
const positionData = this.calculateDragPosition(finalPosition);
|
||||||
|
|
||||||
// Emit drag end event
|
|
||||||
this.eventBus.emit('drag:end', {
|
this.eventBus.emit('drag:end', {
|
||||||
eventId: this.draggedEventId,
|
eventId: eventId,
|
||||||
originalElement: this.originalElement,
|
originalElement: originalElement,
|
||||||
finalPosition,
|
finalPosition,
|
||||||
finalColumn: positionData.column,
|
finalColumn: positionData.column,
|
||||||
finalY: positionData.snappedY
|
finalY: positionData.snappedY
|
||||||
|
|
@ -254,14 +295,11 @@ export class DragDropManager {
|
||||||
} else {
|
} else {
|
||||||
// This was just a click - emit click event instead
|
// This was just a click - emit click event instead
|
||||||
this.eventBus.emit('event:click', {
|
this.eventBus.emit('event:click', {
|
||||||
eventId: this.draggedEventId,
|
eventId: eventId,
|
||||||
originalElement: this.originalElement,
|
originalElement: originalElement,
|
||||||
mousePosition: { x: event.clientX, y: event.clientY }
|
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;
|
if (this.autoScrollAnimationId !== null) return;
|
||||||
|
|
||||||
const scroll = () => {
|
const scroll = () => {
|
||||||
if (!this.cachedElements.scrollContainer || !this.isMouseDown) {
|
if (!this.cachedElements.scrollContainer || !this.draggedEventId) {
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -466,6 +504,14 @@ export class DragDropManager {
|
||||||
this.cachedElements.lastColumnDate = null;
|
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
|
* Clean up all resources and event listeners
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
139
src/managers/HeaderManager.ts
Normal file
139
src/managers/HeaderManager.ts
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -140,6 +140,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
this.handleConvertToAllDay(eventId, targetDate, headerRenderer);
|
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)
|
// Handle navigation period change (when slide animation completes)
|
||||||
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
||||||
// Animate all-day height after navigation completes
|
// Animate all-day height after navigation completes
|
||||||
|
|
@ -183,6 +189,45 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
return 60;
|
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 `
|
||||||
|
<swp-event-time data-duration="${durationMinutes}">${startTime} - ${endTime}</swp-event-time>
|
||||||
|
<swp-event-title>${event.title}</swp-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
|
* Create a clone of an event for dragging
|
||||||
*/
|
*/
|
||||||
|
|
@ -199,18 +244,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
const originalDurationMinutes = this.getOriginalEventDuration(originalEvent);
|
const originalDurationMinutes = this.getOriginalEventDuration(originalEvent);
|
||||||
clone.dataset.originalDuration = originalDurationMinutes.toString();
|
clone.dataset.originalDuration = originalDurationMinutes.toString();
|
||||||
|
|
||||||
|
// Apply common drag styling
|
||||||
|
this.applyDragStyling(clone);
|
||||||
|
|
||||||
// Style for dragging
|
// Set height from original event
|
||||||
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 = '';
|
|
||||||
clone.style.height = originalEvent.style.height || `${originalEvent.getBoundingClientRect().height}px`;
|
clone.style.height = originalEvent.style.height || `${originalEvent.getBoundingClientRect().height}px`;
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
|
|
@ -220,6 +257,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
* Update clone timestamp based on new position
|
* Update clone timestamp based on new position
|
||||||
*/
|
*/
|
||||||
private updateCloneTimestamp(clone: HTMLElement, snappedY: number): void {
|
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 gridSettings = calendarConfig.getGridSettings();
|
||||||
const hourHeight = gridSettings.hourHeight;
|
const hourHeight = gridSettings.hourHeight;
|
||||||
const dayStartHour = gridSettings.dayStartHour;
|
const dayStartHour = gridSettings.dayStartHour;
|
||||||
|
|
@ -248,7 +289,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
|
|
||||||
clone.dataset.start = startDate.toISOString();
|
clone.dataset.start = startDate.toISOString();
|
||||||
clone.dataset.end = endDate.toISOString();
|
clone.dataset.end = endDate.toISOString();
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
const timeElement = clone.querySelector('swp-event-time');
|
const timeElement = clone.querySelector('swp-event-time');
|
||||||
if (timeElement) {
|
if (timeElement) {
|
||||||
|
|
@ -283,7 +323,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
* Handle drag start event
|
* Handle drag start event
|
||||||
*/
|
*/
|
||||||
private handleDragStart(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void {
|
private handleDragStart(originalElement: HTMLElement, eventId: string, mouseOffset: any, column: string): void {
|
||||||
console.log('handleDragStart:', eventId);
|
|
||||||
this.originalEvent = originalElement;
|
this.originalEvent = originalElement;
|
||||||
|
|
||||||
// Remove stacking styling during drag will be handled by new system
|
// 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
|
* Handle drag end event
|
||||||
*/
|
*/
|
||||||
private handleDragEnd(eventId: string, originalElement: HTMLElement, finalColumn: string, finalY: number): void {
|
private handleDragEnd(eventId: string, originalElement: HTMLElement, finalColumn: string, finalY: number): void {
|
||||||
console.log('handleDragEnd:', eventId);
|
|
||||||
|
|
||||||
if (!this.draggedClone || !this.originalEvent) {
|
if (!this.draggedClone || !this.originalEvent) {
|
||||||
console.log('Missing draggedClone or originalEvent');
|
console.warn('Missing draggedClone or originalEvent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,11 +481,13 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
this.draggedClone.style.userSelect = '';
|
this.draggedClone.style.userSelect = '';
|
||||||
// Behold z-index hvis det er et stacked event
|
// Behold z-index hvis det er et stacked event
|
||||||
|
|
||||||
// Update dataset with new times after successful drop
|
// Update dataset with new times after successful drop (only for timed events)
|
||||||
const newEvent = this.elementToCalendarEvent(this.draggedClone);
|
if (this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
|
||||||
if (newEvent) {
|
const newEvent = this.elementToCalendarEvent(this.draggedClone);
|
||||||
this.draggedClone.dataset.start = newEvent.start.toISOString();
|
if (newEvent) {
|
||||||
this.draggedClone.dataset.end = newEvent.end.toISOString();
|
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
|
// 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');
|
const allDayEvent = document.createElement('swp-allday-event');
|
||||||
allDayEvent.dataset.eventId = clone.dataset.eventId || '';
|
allDayEvent.dataset.eventId = clone.dataset.eventId || '';
|
||||||
allDayEvent.dataset.title = eventTitle;
|
allDayEvent.dataset.title = eventTitle;
|
||||||
allDayEvent.dataset.start = `${targetDate}T${eventTime.split(' - ')[0]}:00`;
|
allDayEvent.dataset.start = `${targetDate}T00:00:00`;
|
||||||
allDayEvent.dataset.end = `${targetDate}T${eventTime.split(' - ')[1]}:00`;
|
allDayEvent.dataset.end = `${targetDate}T23:59:59`;
|
||||||
allDayEvent.dataset.type = clone.dataset.type || 'work';
|
allDayEvent.dataset.type = clone.dataset.type || 'work';
|
||||||
allDayEvent.dataset.duration = eventDuration;
|
allDayEvent.dataset.duration = eventDuration;
|
||||||
|
allDayEvent.dataset.allDay = "true";
|
||||||
|
|
||||||
allDayEvent.textContent = eventTitle;
|
allDayEvent.textContent = eventTitle;
|
||||||
|
|
||||||
|
console.log("allDayEvent", allDayEvent.dataset);
|
||||||
// Position in grid
|
// Position in grid
|
||||||
(allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString();
|
(allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString();
|
||||||
// grid-row will be set by checkAndAnimateAllDayHeight() based on actual position
|
// 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
|
// Check if height animation is needed
|
||||||
this.triggerAllDayHeightAnimation();
|
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
|
* Fade out and remove element
|
||||||
|
|
@ -872,26 +1008,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
eventElement.dataset.type = event.type;
|
eventElement.dataset.type = event.type;
|
||||||
eventElement.dataset.duration = event.metadata?.duration?.toString() || '60';
|
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);
|
const position = this.calculateEventPosition(event);
|
||||||
eventElement.style.position = 'absolute';
|
this.applyEventPositioning(eventElement, position.top + 1, position.height - 3);
|
||||||
eventElement.style.top = `${position.top + 1}px`;
|
|
||||||
eventElement.style.height = `${position.height - 3}px`; //adjusted so bottom does not cover horizontal time lines.
|
|
||||||
|
|
||||||
// Color is now handled by CSS classes based on data-type attribute
|
// Color is now handled by CSS classes based on data-type attribute
|
||||||
|
|
||||||
// Format time for display using unified method
|
// Create event content using helper method
|
||||||
const startTime = this.formatTime(event.start);
|
eventElement.innerHTML = this.createEventInnerStructure(event);
|
||||||
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 = `
|
|
||||||
<swp-event-time data-duration="${durationMinutes}">${startTime} - ${endTime}</swp-event-time>
|
|
||||||
<swp-event-title>${event.title}</swp-event-title>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Setup resize handles on first mouseover only
|
// Setup resize handles on first mouseover only
|
||||||
eventElement.addEventListener('mouseover', () => {
|
eventElement.addEventListener('mouseover', () => {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { DateCalculator } from '../utils/DateCalculator';
|
||||||
* Optimized to reduce redundant DOM operations and improve performance
|
* Optimized to reduce redundant DOM operations and improve performance
|
||||||
*/
|
*/
|
||||||
export class GridRenderer {
|
export class GridRenderer {
|
||||||
private headerEventListener: ((event: Event) => void) | null = null;
|
|
||||||
private cachedGridContainer: HTMLElement | null = null;
|
private cachedGridContainer: HTMLElement | null = null;
|
||||||
private cachedCalendarHeader: HTMLElement | null = null;
|
private cachedCalendarHeader: HTMLElement | null = null;
|
||||||
private cachedTimeAxis: HTMLElement | null = null;
|
private cachedTimeAxis: HTMLElement | null = null;
|
||||||
|
|
@ -158,8 +157,8 @@ export class GridRenderer {
|
||||||
// Always ensure all-day containers exist for all days
|
// Always ensure all-day containers exist for all days
|
||||||
headerRenderer.ensureAllDayContainers(calendarHeader);
|
headerRenderer.ensureAllDayContainers(calendarHeader);
|
||||||
|
|
||||||
// Setup optimized event listener
|
// Setup only grid-related event listeners
|
||||||
this.setupOptimizedHeaderEventListener(calendarHeader);
|
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 {
|
private setupGridEventListeners(): void {
|
||||||
// Remove existing listener if any
|
// Setup grid body mouseover listener for all-day to timed conversion
|
||||||
if (this.headerEventListener) {
|
this.setupGridBodyMouseOver();
|
||||||
calendarHeader.removeEventListener('mouseover', this.headerEventListener);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
let lastEmitTime = 0;
|
||||||
const throttleDelay = 16; // ~60fps
|
const throttleDelay = 16; // ~60fps
|
||||||
|
|
||||||
this.headerEventListener = (event) => {
|
const gridBodyEventListener = (event: Event) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastEmitTime < throttleDelay) {
|
if (now - lastEmitTime < throttleDelay) {
|
||||||
return; // Throttle events for better performance
|
return;
|
||||||
}
|
}
|
||||||
lastEmitTime = now;
|
lastEmitTime = now;
|
||||||
|
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
|
const dayColumn = target.closest('swp-day-column');
|
||||||
|
|
||||||
// Optimized element detection
|
if (dayColumn) {
|
||||||
const dayHeader = target.closest('swp-day-header');
|
const targetColumn = (dayColumn as HTMLElement).dataset.date;
|
||||||
const allDayContainer = target.closest('swp-allday-container');
|
if (targetColumn) {
|
||||||
|
// Calculate Y position relative to the column
|
||||||
if (dayHeader || allDayContainer) {
|
const columnRect = dayColumn.getBoundingClientRect();
|
||||||
let hoveredElement: HTMLElement;
|
const mouseY = (event as MouseEvent).clientY;
|
||||||
let targetDate: string | undefined;
|
const targetY = mouseY - columnRect.top;
|
||||||
|
|
||||||
if (dayHeader) {
|
|
||||||
hoveredElement = dayHeader as HTMLElement;
|
|
||||||
targetDate = hoveredElement.dataset.date;
|
|
||||||
} else if (allDayContainer) {
|
|
||||||
hoveredElement = allDayContainer as HTMLElement;
|
|
||||||
|
|
||||||
// Optimized day calculation using cached header rect
|
eventBus.emit('column:mouseover', {
|
||||||
const headerRect = calendarHeader.getBoundingClientRect();
|
targetColumn,
|
||||||
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
targetY
|
||||||
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 once and cache
|
|
||||||
const calendarType = calendarConfig.getCalendarMode();
|
|
||||||
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
|
|
||||||
|
|
||||||
eventBus.emit('header:mouseover', {
|
|
||||||
element: hoveredElement,
|
|
||||||
targetDate,
|
|
||||||
headerRenderer
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the optimized listener
|
columnContainer.addEventListener('mouseover', gridBodyEventListener);
|
||||||
calendarHeader.addEventListener('mouseover', this.headerEventListener);
|
|
||||||
|
// Store reference for cleanup
|
||||||
|
(this as any).gridBodyEventListener = gridBodyEventListener;
|
||||||
|
(this as any).cachedColumnContainer = columnContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up cached elements and event listeners
|
* Clean up cached elements and event listeners
|
||||||
*/
|
*/
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
// Clean up event listeners
|
// Clean up grid-only event listeners
|
||||||
if (this.headerEventListener && this.cachedCalendarHeader) {
|
if ((this as any).gridBodyEventListener && (this as any).cachedColumnContainer) {
|
||||||
this.cachedCalendarHeader.removeEventListener('mouseover', this.headerEventListener);
|
(this as any).cachedColumnContainer.removeEventListener('mouseover', (this as any).gridBodyEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear cached references
|
// Clear cached references
|
||||||
this.cachedGridContainer = null;
|
this.cachedGridContainer = null;
|
||||||
this.cachedCalendarHeader = null;
|
this.cachedCalendarHeader = null;
|
||||||
this.cachedTimeAxis = null;
|
this.cachedTimeAxis = null;
|
||||||
this.headerEventListener = null;
|
(this as any).gridBodyEventListener = null;
|
||||||
|
(this as any).cachedColumnContainer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { DateCalculator } from '../utils/DateCalculator';
|
import { DateCalculator } from '../utils/DateCalculator';
|
||||||
import { EventRenderingService } from './EventRendererManager';
|
import { EventRenderingService } from './EventRendererManager';
|
||||||
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
||||||
import { eventBus } from '../core/EventBus';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NavigationRenderer - Handles DOM rendering for navigation containers
|
* NavigationRenderer - Handles DOM rendering for navigation containers
|
||||||
|
|
@ -12,8 +11,6 @@ import { eventBus } from '../core/EventBus';
|
||||||
*/
|
*/
|
||||||
export class NavigationRenderer {
|
export class NavigationRenderer {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private dateCalculator: DateCalculator;
|
|
||||||
private eventRenderer: EventRenderingService;
|
|
||||||
|
|
||||||
// Cached DOM elements to avoid redundant queries
|
// Cached DOM elements to avoid redundant queries
|
||||||
private cachedWeekNumberElement: HTMLElement | null = null;
|
private cachedWeekNumberElement: HTMLElement | null = null;
|
||||||
|
|
@ -21,9 +18,7 @@ export class NavigationRenderer {
|
||||||
|
|
||||||
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) {
|
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.eventRenderer = eventRenderer;
|
|
||||||
DateCalculator.initialize(calendarConfig);
|
DateCalculator.initialize(calendarConfig);
|
||||||
this.dateCalculator = new DateCalculator();
|
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,9 +197,6 @@ export class NavigationRenderer {
|
||||||
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarConfig.getCalendarMode());
|
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarConfig.getCalendarMode());
|
||||||
headerRenderer.ensureAllDayContainers(header as HTMLElement);
|
headerRenderer.ensureAllDayContainers(header as HTMLElement);
|
||||||
|
|
||||||
// Add event delegation listener for drag & drop functionality
|
|
||||||
this.setupHeaderEventListener(header as HTMLElement);
|
|
||||||
|
|
||||||
// Render day columns for target week
|
// Render day columns for target week
|
||||||
dates.forEach(date => {
|
dates.forEach(date => {
|
||||||
const column = document.createElement('swp-day-column');
|
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
|
* Public cleanup method for cached elements
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue