2025-09-10 22:07:40 +02:00
|
|
|
import { eventBus } from '../core/EventBus';
|
|
|
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
|
|
|
|
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
2025-09-17 23:39:29 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
|
|
|
|
import { HeaderRenderContext } from '../renderers/HeaderRenderer';
|
|
|
|
|
import { ResourceCalendarData } from '../types/CalendarTypes';
|
2025-09-10 22:07:40 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
2025-09-17 23:39:29 +02:00
|
|
|
|
|
|
|
|
// Listen for navigation events to update header
|
|
|
|
|
this.setupNavigationListener();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialize header with initial date
|
|
|
|
|
*/
|
|
|
|
|
public initializeHeader(currentDate: Date, resourceData: ResourceCalendarData | null = null): void {
|
|
|
|
|
this.updateHeader(currentDate, resourceData);
|
2025-09-10 22:07:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
|
2025-09-17 22:08:27 +02:00
|
|
|
// Optimized element detection - handle day headers and all-day columns
|
2025-09-10 22:07:40 +02:00
|
|
|
const dayHeader = target.closest('swp-day-header');
|
2025-09-17 22:08:27 +02:00
|
|
|
const allDayColumn = target.closest('swp-allday-column');
|
2025-09-10 22:07:40 +02:00
|
|
|
|
2025-09-17 22:08:27 +02:00
|
|
|
if (dayHeader || allDayColumn) {
|
|
|
|
|
const hoveredElement = (dayHeader || allDayColumn) as HTMLElement;
|
2025-09-12 00:36:02 +02:00
|
|
|
const targetDate = hoveredElement.dataset.date;
|
2025-09-10 22:07:40 +02:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 23:39:29 +02:00
|
|
|
/**
|
|
|
|
|
* Setup navigation event listener
|
|
|
|
|
*/
|
|
|
|
|
private setupNavigationListener(): void {
|
|
|
|
|
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (event) => {
|
|
|
|
|
const { currentDate, resourceData } = (event as CustomEvent).detail;
|
|
|
|
|
this.updateHeader(currentDate, resourceData);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Also listen for date changes (including initial setup)
|
|
|
|
|
eventBus.on(CoreEvents.DATE_CHANGED, (event) => {
|
|
|
|
|
const { currentDate } = (event as CustomEvent).detail;
|
|
|
|
|
this.updateHeader(currentDate, null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Listen for workweek header updates after grid rebuild
|
|
|
|
|
eventBus.on('workweek:header-update', (event) => {
|
|
|
|
|
const { currentDate } = (event as CustomEvent).detail;
|
|
|
|
|
this.clearCache(); // Clear cache since DOM was cleared
|
|
|
|
|
this.updateHeader(currentDate, null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update header content for navigation
|
|
|
|
|
*/
|
|
|
|
|
private updateHeader(currentDate: Date, resourceData: ResourceCalendarData | null = null): void {
|
|
|
|
|
const calendarHeader = this.getOrCreateCalendarHeader();
|
|
|
|
|
if (!calendarHeader) return;
|
|
|
|
|
|
|
|
|
|
// Clear existing content
|
|
|
|
|
calendarHeader.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
// Render new header content
|
|
|
|
|
const calendarType = calendarConfig.getCalendarMode();
|
|
|
|
|
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
|
|
|
|
|
|
|
|
|
|
const context: HeaderRenderContext = {
|
|
|
|
|
currentWeek: currentDate,
|
|
|
|
|
config: calendarConfig,
|
|
|
|
|
resourceData: resourceData
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
headerRenderer.render(calendarHeader, context);
|
|
|
|
|
|
|
|
|
|
// Re-setup event listeners
|
|
|
|
|
this.setupHeaderDragListeners();
|
|
|
|
|
|
|
|
|
|
// Notify other managers that header was rebuilt
|
|
|
|
|
eventBus.emit('header:rebuilt', {
|
|
|
|
|
headerElement: calendarHeader
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get or create calendar header element
|
|
|
|
|
*/
|
|
|
|
|
private getOrCreateCalendarHeader(): HTMLElement | null {
|
|
|
|
|
let calendarHeader = this.getCalendarHeader();
|
|
|
|
|
|
|
|
|
|
if (!calendarHeader) {
|
|
|
|
|
// Find grid container and create header
|
|
|
|
|
const gridContainer = document.querySelector('swp-grid-container');
|
|
|
|
|
if (gridContainer) {
|
|
|
|
|
calendarHeader = document.createElement('swp-calendar-header');
|
|
|
|
|
// Insert header as first child
|
|
|
|
|
gridContainer.insertBefore(calendarHeader, gridContainer.firstChild);
|
|
|
|
|
this.cachedCalendarHeader = calendarHeader;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return calendarHeader;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-10 22:07:40 +02:00
|
|
|
/**
|
|
|
|
|
* 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();
|
|
|
|
|
}
|
|
|
|
|
}
|