// Core EventBus using pure DOM CustomEvents import { IEventLogEntry, IListenerEntry, IEventBus } from '../types/CalendarTypes'; /** * Central event dispatcher for calendar using DOM CustomEvents * Provides logging and debugging capabilities */ export class EventBus implements IEventBus { private eventLog: IEventLogEntry[] = []; private debug: boolean = false; private listeners: Set = new Set(); // Log configuration for different categories private logConfig: { [key: string]: boolean } = { calendar: true, grid: true, event: true, scroll: true, navigation: true, view: true, default: true }; /** * Subscribe to an event via DOM addEventListener */ on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void { document.addEventListener(eventType, handler, options); // Track for cleanup this.listeners.add({ eventType, handler, options }); // Return unsubscribe function return () => this.off(eventType, handler); } /** * Subscribe to an event once */ once(eventType: string, handler: EventListener): () => void { return this.on(eventType, handler, { once: true }); } /** * Unsubscribe from an event */ off(eventType: string, handler: EventListener): void { document.removeEventListener(eventType, handler); // Remove from tracking for (const listener of this.listeners) { if (listener.eventType === eventType && listener.handler === handler) { this.listeners.delete(listener); break; } } } /** * Emit an event via DOM CustomEvent */ emit(eventType: string, detail: unknown = {}): boolean { // Validate eventType if (!eventType) { return false; } const event = new CustomEvent(eventType, { detail: detail ?? {}, bubbles: true, cancelable: true }); // Log event with grouping if (this.debug) { this.logEventWithGrouping(eventType, detail); } this.eventLog.push({ type: eventType, detail: detail ?? {}, timestamp: Date.now() }); // Emit on document (only DOM events now) return !document.dispatchEvent(event); } /** * Log event with console grouping */ private logEventWithGrouping(eventType: string, detail: unknown): void { // Extract category from event type (e.g., 'calendar:datechanged' → 'calendar') const category = this.extractCategory(eventType); // Only log if category is enabled if (!this.logConfig[category]) { return; } // Get category emoji and color const { emoji, color } = this.getCategoryStyle(category); // Use collapsed group to reduce visual noise } /** * Extract category from event type */ private extractCategory(eventType: string): string { if (!eventType) { return 'unknown'; } if (eventType.includes(':')) { return eventType.split(':')[0]; } // Fallback: try to detect category from event name patterns const lowerType = eventType.toLowerCase(); if (lowerType.includes('grid') || lowerType.includes('rendered')) return 'grid'; if (lowerType.includes('event') || lowerType.includes('sync')) return 'event'; if (lowerType.includes('scroll')) return 'scroll'; if (lowerType.includes('nav') || lowerType.includes('date')) return 'navigation'; if (lowerType.includes('view')) return 'view'; return 'default'; } /** * Get styling for different categories */ private getCategoryStyle(category: string): { emoji: string; color: string } { const styles: { [key: string]: { emoji: string; color: string } } = { calendar: { emoji: '🗓️', color: '#2196F3' }, grid: { emoji: '📊', color: '#4CAF50' }, event: { emoji: '📅', color: '#FF9800' }, scroll: { emoji: '📜', color: '#9C27B0' }, navigation: { emoji: '🧭', color: '#F44336' }, view: { emoji: '👁️', color: '#00BCD4' }, default: { emoji: '📢', color: '#607D8B' } }; return styles[category] || styles.default; } /** * Configure logging for specific categories */ setLogConfig(config: { [key: string]: boolean }): void { this.logConfig = { ...this.logConfig, ...config }; } /** * Get current log configuration */ getLogConfig(): { [key: string]: boolean } { return { ...this.logConfig }; } /** * Get event history */ getEventLog(eventType?: string): IEventLogEntry[] { if (eventType) { return this.eventLog.filter(e => e.type === eventType); } return this.eventLog; } /** * Enable/disable debug mode */ setDebug(enabled: boolean): void { this.debug = enabled; } } // Create singleton instance export const eventBus = new EventBus();