import { eventBus } from '../core/EventBus'; import { calendarConfig } from '../core/CalendarConfig'; import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { CoreEvents } from '../constants/CoreEvents'; import { HeaderRenderContext } from '../renderers/HeaderRenderer'; import { ResourceCalendarData } from '../types/CalendarTypes'; /** * 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); // 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); } /** * 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 - handle day headers and all-day columns const dayHeader = target.closest('swp-day-header'); const allDayColumn = target.closest('swp-allday-column'); if (dayHeader || allDayColumn) { const hoveredElement = (dayHeader || allDayColumn) as HTMLElement; const targetDate = hoveredElement.dataset.date; // 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); } } /** * 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; } /** * 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(); } }