diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 2298aa1..2e51f96 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -1,6 +1,135 @@ /** - * DragDropManager - Optimized drag and drop with consolidated position calculations - * Reduces redundant DOM queries and improves performance through caching + * DragDropManager - Advanced drag-and-drop system with smooth animations and event type conversion + * + * ARCHITECTURE OVERVIEW: + * ===================== + * DragDropManager provides a sophisticated drag-and-drop system for calendar events that supports: + * - Smooth animated dragging with requestAnimationFrame + * - Automatic event type conversion (timed events ↔ all-day events) + * - Scroll compensation during edge scrolling + * - Grid snapping for precise event placement + * - Column detection and change tracking + * + * KEY FEATURES: + * ============= + * 1. DRAG DETECTION + * - Movement threshold (5px) to distinguish clicks from drags + * - Immediate visual feedback with cloned element + * - Mouse offset tracking for natural drag feel + * + * 2. SMOOTH ANIMATION + * - Uses requestAnimationFrame for 60fps animations + * - Interpolated movement (30% per frame) for smooth transitions + * - Continuous drag:move events for real-time updates + * + * 3. EVENT TYPE CONVERSION + * - Timed → All-day: When dragging into calendar header + * - All-day → Timed: When dragging into day columns + * - Automatic clone replacement with appropriate element type + * + * 4. SCROLL COMPENSATION + * - Tracks scroll delta during edge-scrolling + * - Compensates dragged element position during scroll + * - Prevents visual "jumping" when scrolling while dragging + * + * 5. GRID SNAPPING + * - Snaps to time grid on mouse up + * - Uses PositionUtils for consistent positioning + * - Accounts for mouse offset within event + * + * STATE MANAGEMENT: + * ================= + * Mouse Tracking: + * - mouseDownPosition: Initial click position + * - currentMousePosition: Latest mouse position + * - mouseOffset: Click offset within event (for natural dragging) + * + * Drag State: + * - originalElement: Source event being dragged + * - draggedClone: Animated clone following mouse + * - currentColumn: Column mouse is currently over + * - previousColumn: Last column (for detecting changes) + * - isDragStarted: Whether drag threshold exceeded + * + * Scroll State: + * - scrollDeltaY: Accumulated scroll offset during drag + * - lastScrollTop: Previous scroll position + * - isScrollCompensating: Whether edge-scroll is active + * + * Animation State: + * - dragAnimationId: requestAnimationFrame ID + * - targetY: Desired position for smooth interpolation + * - currentY: Current interpolated position + * + * EVENT FLOW: + * =========== + * 1. Mouse Down (handleMouseDown) + * ├─ Store originalElement and mouse offset + * └─ Wait for movement + * + * 2. Mouse Move (handleMouseMove) + * ├─ Check movement threshold + * ├─ Initialize drag if threshold exceeded (initializeDrag) + * │ ├─ Create clone + * │ ├─ Emit drag:start + * │ └─ Start animation loop + * ├─ Continue drag (continueDrag) + * │ ├─ Calculate target position with scroll compensation + * │ └─ Update animation target + * └─ Detect column changes (detectColumnChange) + * └─ Emit drag:column-change + * + * 3. Animation Loop (animateDrag) + * ├─ Interpolate currentY toward targetY + * ├─ Emit drag:move on each frame + * └─ Schedule next frame until target reached + * + * 4. Event Type Conversion + * ├─ Entering header (handleHeaderMouseEnter) + * │ ├─ Emit drag:mouseenter-header + * │ └─ AllDayManager creates all-day clone + * └─ Entering column (handleColumnMouseEnter) + * ├─ Emit drag:mouseenter-column + * └─ EventRenderingService creates timed clone + * + * 5. Mouse Up (handleMouseUp) + * ├─ Stop animation + * ├─ Snap to grid + * ├─ Detect drop target (header or column) + * ├─ Emit drag:end with final position + * └─ Cleanup drag state + * + * SCROLL COMPENSATION SYSTEM: + * =========================== + * Problem: When EdgeScrollManager scrolls the grid during drag, the dragged element + * can appear to "jump" because the mouse position stays the same but the + * coordinate system (scrollable content) has moved. + * + * Solution: Track cumulative scroll delta and add it to mouse position calculations + * + * Flow: + * 1. EdgeScrollManager starts scrolling → emit edgescroll:started + * 2. DragDropManager sets isScrollCompensating = true + * 3. On each scroll event: + * ├─ Calculate scrollDelta = currentScrollTop - lastScrollTop + * ├─ Accumulate into scrollDeltaY + * └─ Call continueDrag with adjusted position + * 4. continueDrag adds scrollDeltaY to mouse Y coordinate + * 5. On event conversion, reset scrollDeltaY (new clone, new coordinate system) + * + * PERFORMANCE OPTIMIZATIONS: + * ========================== + * - Uses ColumnDetectionUtils cache for fast column lookups + * - Single requestAnimationFrame loop (not per-mousemove) + * - Interpolated animation reduces update frequency + * - Passive scroll listeners + * - Event delegation for header/column detection + * + * USAGE: + * ====== + * const dragDropManager = new DragDropManager(eventBus, positionUtils); + * // Automatically attaches event listeners and manages drag lifecycle + * // Other managers listen to drag:start, drag:move, drag:end, etc. */ import { IEventBus } from '../types/CalendarTypes'; @@ -122,24 +251,19 @@ export class DragDropManager { if (this.scrollableContent) { this.lastScrollTop = this.scrollableContent.scrollTop; } - - console.log('🎬 DragDropManager: Edge-scroll started'); }); this.eventBus.on('edgescroll:stopped', () => { this.isScrollCompensating = false; - console.log('🛑 DragDropManager: Edge-scroll stopped'); }); // Reset scrollDeltaY when event converts (new clone created) this.eventBus.on('drag:mouseenter-header', () => { - console.log('🔄 DragDropManager: Event converting to all-day - resetting scrollDeltaY'); this.scrollDeltaY = 0; this.lastScrollTop = 0; }); this.eventBus.on('drag:mouseenter-column', () => { - console.log('🔄 DragDropManager: Event converting to timed - resetting scrollDeltaY'); this.scrollDeltaY = 0; this.lastScrollTop = 0; }); @@ -340,8 +464,6 @@ export class DragDropManager { target: dropTarget }; - console.log('DragEndEventPayload', dragEndPayload); - this.eventBus.emit('drag:end', dragEndPayload); this.cleanupDragState(); @@ -361,7 +483,6 @@ export class DragDropManager { const allClones = document.querySelectorAll('[data-event-id^="clone"]'); if (allClones.length > 0) { - console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`); allClones.forEach(clone => clone.remove()); } } @@ -373,8 +494,6 @@ export class DragDropManager { private cancelDrag(): void { if (!this.originalElement || !this.draggedClone) return; - console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container'); - // Get current clone position const cloneRect = this.draggedClone.getBoundingClientRect(); @@ -486,13 +605,6 @@ export class DragDropManager { // Kald continueDrag med nuværende mus position this.continueDrag(this.currentMousePosition); - - console.log('📜 DragDropManager: Scroll compensation', { - currentScrollTop, - lastScrollTop: this.lastScrollTop - scrollDelta, - scrollDelta, - scrollDeltaY: this.scrollDeltaY - }); } /** @@ -564,7 +676,6 @@ export class DragDropManager { this.dragAnimationId === null; } }; - console.log('DragMouseEnterHeaderEventPayload', dragMouseEnterPayload); this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload); } } @@ -578,13 +689,10 @@ export class DragDropManager { return; } - console.log('🎯 DragDropManager: Mouse entered day column'); - const position: MousePosition = { x: event.clientX, y: event.clientY }; const targetColumn = ColumnDetectionUtils.getColumnBounds(position); if (!targetColumn) { - console.warn("No column detected when entering day column"); return; } @@ -619,13 +727,10 @@ export class DragDropManager { return; } - console.log('🚪 DragDropManager: Mouse left header'); - const position: MousePosition = { x: event.clientX, y: event.clientY }; const targetColumn = ColumnDetectionUtils.getColumnBounds(position); if (!targetColumn) { - console.warn("No column detected when leaving header"); return; } diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index d8240c6..6a463f6 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -15,15 +15,13 @@ interface RawEventData { } /** - * EventManager - Optimized event lifecycle and CRUD operations - * Handles data loading with improved performance and caching + * EventManager - Event lifecycle and CRUD operations + * Handles data loading and event management */ export class EventManager { private events: CalendarEvent[] = []; private rawData: RawEventData[] | null = null; - private eventCache = new Map(); // Cache for period queries - private lastCacheKey: string = ''; private dateService: DateService; private config: CalendarConfig; @@ -37,12 +35,11 @@ export class EventManager { } /** - * Optimized data loading with better error handling + * Load event data from JSON file */ public async loadData(): Promise { try { await this.loadMockData(); - this.clearCache(); // Clear cache when new data is loaded } catch (error) { console.error('Failed to load event data:', error); this.events = []; @@ -69,7 +66,7 @@ export class EventManager { } /** - * Optimized data processing with better type safety + * Process raw event data and convert to CalendarEvent objects */ private processCalendarData(data: RawEventData[]): CalendarEvent[] { return data.map((event): CalendarEvent => ({ @@ -82,14 +79,6 @@ export class EventManager { })); } - /** - * Clear event cache when data changes - */ - private clearCache(): void { - this.eventCache.clear(); - this.lastCacheKey = ''; - } - /** * Get events with optional copying for performance */ @@ -162,42 +151,26 @@ export class EventManager { } /** - * Optimized events for period with caching and DateService + * Get events that overlap with a given time period */ public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] { - // Create cache key using DateService for consistent formatting - const cacheKey = `${this.dateService.formatISODate(startDate)}_${this.dateService.formatISODate(endDate)}`; - - // Return cached result if available - if (this.lastCacheKey === cacheKey && this.eventCache.has(cacheKey)) { - return this.eventCache.get(cacheKey)!; - } - - // Filter events using optimized date operations - const filteredEvents = this.events.filter(event => { - // Event overlaps period if it starts before period ends AND ends after period starts + // Event overlaps period if it starts before period ends AND ends after period starts + return this.events.filter(event => { return event.start <= endDate && event.end >= startDate; }); - - // Cache the result - this.eventCache.set(cacheKey, filteredEvents); - this.lastCacheKey = cacheKey; - - return filteredEvents; } /** - * Optimized event creation with better ID generation + * Create a new event and add it to the calendar */ public addEvent(event: Omit): CalendarEvent { const newEvent: CalendarEvent = { ...event, id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` }; - + this.events.push(newEvent); - this.clearCache(); // Clear cache when data changes - + this.eventBus.emit(CoreEvents.EVENT_CREATED, { event: newEvent }); @@ -206,7 +179,7 @@ export class EventManager { } /** - * Optimized event update with validation + * Update an existing event */ public updateEvent(id: string, updates: Partial): CalendarEvent | null { const eventIndex = this.events.findIndex(event => event.id === id); @@ -214,39 +187,11 @@ export class EventManager { const updatedEvent = { ...this.events[eventIndex], ...updates }; this.events[eventIndex] = updatedEvent; - - this.clearCache(); // Clear cache when data changes - + this.eventBus.emit(CoreEvents.EVENT_UPDATED, { event: updatedEvent }); return updatedEvent; } - - /** - * Optimized event deletion with better error handling - */ - public deleteEvent(id: string): boolean { - const eventIndex = this.events.findIndex(event => event.id === id); - if (eventIndex === -1) return false; - - const deletedEvent = this.events[eventIndex]; - this.events.splice(eventIndex, 1); - - this.clearCache(); // Clear cache when data changes - - this.eventBus.emit(CoreEvents.EVENT_DELETED, { - event: deletedEvent - }); - - return true; - } - - /** - * Refresh data by reloading from source - */ - public async refresh(): Promise { - await this.loadData(); - } -} \ No newline at end of file +} diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index 8c6af1e..e7a0580 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -26,20 +26,6 @@ export class HeaderManager { this.setupNavigationListener(); } - /** - * Initialize header with initial date - */ - public initializeHeader(currentDate: Date): void { - this.updateHeader(currentDate); - } - - /** - * Get cached calendar header element - */ - private getCalendarHeader(): HTMLElement | null { - return document.querySelector('swp-calendar-header'); - } - /** * Setup header drag event listeners - Listen to DragDropManager events */ @@ -65,8 +51,6 @@ export class HeaderManager { originalElement: !!originalElement, cloneElement: !!cloneElement }); - - // Header renderer already injected - ready to use if needed } /** @@ -115,7 +99,7 @@ export class HeaderManager { rendererType: this.headerRenderer.constructor.name }); - const calendarHeader = this.getOrCreateCalendarHeader(); + const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement; if (!calendarHeader) { console.warn('❌ HeaderManager: No calendar header found!'); return; @@ -130,9 +114,7 @@ export class HeaderManager { config: this.config }; - console.log('🎨 HeaderManager: Calling renderer.render()', context); this.headerRenderer.render(calendarHeader, context); - console.log('✅ HeaderManager: Renderer completed'); // Setup event listeners on the new content this.setupHeaderDragListeners(); @@ -143,18 +125,4 @@ export class HeaderManager { }; eventBus.emit('header:ready', payload); } - - /** - * Get calendar header element - header always exists now - */ - private getOrCreateCalendarHeader(): HTMLElement | null { - const calendarHeader = this.getCalendarHeader(); - - if (!calendarHeader) { - console.warn('HeaderManager: Calendar header not found - should always exist now!'); - return null; - } - - return calendarHeader; - } } \ No newline at end of file diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index c7cdb56..b16174d 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -5,10 +5,6 @@ import { CoreEvents } from '../constants/CoreEvents'; import { NavigationRenderer } from '../renderers/NavigationRenderer'; import { GridRenderer } from '../renderers/GridRenderer'; -/** - * NavigationManager handles calendar navigation (prev/next/today buttons) - * with simplified CSS Grid approach - */ export class NavigationManager { private eventBus: IEventBus; private navigationRenderer: NavigationRenderer; @@ -49,20 +45,12 @@ export class NavigationManager { } - private getCalendarContainer(): HTMLElement | null { - return document.querySelector('swp-calendar-container'); - } - - private getCurrentGrid(): HTMLElement | null { - return document.querySelector('swp-calendar-container swp-grid-container:not([data-prerendered])'); - } - private setupEventListeners(): void { // Initial DOM update when calendar is initialized this.eventBus.on(CoreEvents.INITIALIZED, () => { this.updateWeekInfo(); }); - + // Listen for filter changes and apply to pre-rendered grids this.eventBus.on(CoreEvents.FILTER_CHANGED, (e: Event) => { const detail = (e as CustomEvent).detail; @@ -73,11 +61,11 @@ export class NavigationManager { document.addEventListener('click', (e) => { const target = e.target as HTMLElement; const navButton = target.closest('[data-action]') as HTMLElement; - + if (!navButton) return; - + const action = navButton.dataset.action; - + switch (action) { case 'prev': this.navigateToPreviousWeek(); @@ -118,12 +106,12 @@ export class NavigationManager { this.eventBus.on(CoreEvents.NAVIGATE_TO_EVENT, (event: Event) => { const customEvent = event as CustomEvent; const { eventDate, eventStartTime } = customEvent.detail; - + if (!eventDate || !eventStartTime) { console.warn('NavigationManager: Invalid event navigation data'); return; } - + this.navigateToEventDate(eventDate, eventStartTime); }); } @@ -134,10 +122,10 @@ export class NavigationManager { private navigateToEventDate(eventDate: Date, eventStartTime: string): void { const weekStart = this.getISOWeekStart(eventDate); this.targetWeek = new Date(weekStart); - + const currentTime = this.currentWeek.getTime(); const targetTime = weekStart.getTime(); - + // Store event start time for scrolling after navigation const scrollAfterNavigation = () => { // Emit scroll request after navigation is complete @@ -145,7 +133,7 @@ export class NavigationManager { eventStartTime }); }; - + if (currentTime < targetTime) { this.animationQueue++; this.animateTransition('next', weekStart); @@ -179,13 +167,13 @@ export class NavigationManager { private navigateToToday(): void { const today = new Date(); const todayWeekStart = this.getISOWeekStart(today); - + // Reset to today this.targetWeek = new Date(todayWeekStart); - + const currentTime = this.currentWeek.getTime(); const targetTime = todayWeekStart.getTime(); - + if (currentTime < targetTime) { this.animationQueue++; this.animateTransition('next', todayWeekStart); @@ -198,10 +186,10 @@ export class NavigationManager { private navigateToDate(date: Date): void { const weekStart = this.getISOWeekStart(date); this.targetWeek = new Date(weekStart); - + const currentTime = this.currentWeek.getTime(); const targetTime = weekStart.getTime(); - + if (currentTime < targetTime) { this.animationQueue++; this.animateTransition('next', weekStart); @@ -215,9 +203,10 @@ export class NavigationManager { * Animation transition using pre-rendered containers when available */ private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void { - const container = this.getCalendarContainer(); - const currentGrid = this.getCurrentGrid(); - + + const container = document.querySelector('swp-calendar-container') as HTMLElement; + const currentGrid = document.querySelector('swp-calendar-container swp-grid-container:not([data-prerendered])') as HTMLElement; + if (!container || !currentGrid) { return; } @@ -225,25 +214,25 @@ export class NavigationManager { // Reset all-day height BEFORE creating new grid to ensure base height const root = document.documentElement; root.style.setProperty('--all-day-row-height', '0px'); - + let newGrid: HTMLElement; console.group('🔧 NavigationManager.refactored'); console.log('Calling GridRenderer instead of NavigationRenderer'); console.log('Target week:', targetWeek); - + // Always create a fresh container for consistent behavior newGrid = this.gridRenderer.createNavigationGrid(container, targetWeek); - + console.groupEnd(); - + // Clear any existing transforms before animation newGrid.style.transform = ''; - (currentGrid as HTMLElement).style.transform = ''; + currentGrid.style.transform = ''; // Animate transition using Web Animations API - const slideOutAnimation = (currentGrid as HTMLElement).animate([ + const slideOutAnimation = currentGrid.animate([ { transform: 'translateX(0)', opacity: '1' }, { transform: direction === 'next' ? 'translateX(-100%)' : 'translateX(100%)', opacity: '0.5' } ], { @@ -263,7 +252,7 @@ export class NavigationManager { // Handle animation completion slideInAnimation.addEventListener('finish', () => { - + // Cleanup: Remove all old grids except the new one const allGrids = container.querySelectorAll('swp-grid-container'); for (let i = 0; i < allGrids.length - 1; i++) { @@ -273,16 +262,16 @@ export class NavigationManager { // Reset positioning newGrid.style.position = 'relative'; newGrid.removeAttribute('data-prerendered'); - + // Update state this.currentWeek = new Date(targetWeek); this.animationQueue--; - + // If this was the last queued animation, ensure we're in sync if (this.animationQueue === 0) { this.currentWeek = new Date(this.targetWeek); } - + // Update week info and notify other managers this.updateWeekInfo(); @@ -291,7 +280,7 @@ export class NavigationManager { direction, currentDate: this.currentWeek }); - + }); } @@ -299,7 +288,7 @@ export class NavigationManager { const weekNumber = this.dateService.getWeekNumber(this.currentWeek); const weekEnd = this.dateService.addDays(this.currentWeek, 6); const dateRange = this.dateService.formatDateRange(this.currentWeek, weekEnd); - + // Notify other managers about week info update - DOM manipulation should happen via events this.eventBus.emit(CoreEvents.PERIOD_INFO_UPDATE, { weekNumber, @@ -308,35 +297,4 @@ export class NavigationManager { weekEnd }); } - - /** - * Get current week start date - */ - getCurrentWeek(): Date { - return new Date(this.currentWeek); - } - - /** - * Get target week (where navigation is heading) - */ - getTargetWeek(): Date { - return new Date(this.targetWeek); - } - - /** - * Check if navigation animation is in progress - */ - isAnimating(): boolean { - return this.animationQueue > 0; - } - - /** - * Force navigation to specific week without animation - */ - setWeek(weekStart: Date): void { - this.currentWeek = new Date(weekStart); - this.targetWeek = new Date(weekStart); - this.updateWeekInfo(); - } - } \ No newline at end of file diff --git a/src/managers/ViewManager.ts b/src/managers/ViewManager.ts index 969032c..a90523c 100644 --- a/src/managers/ViewManager.ts +++ b/src/managers/ViewManager.ts @@ -1,77 +1,49 @@ -import { EventBus } from '../core/EventBus'; import { CalendarView, IEventBus } from '../types/CalendarTypes'; import { CalendarConfig } from '../core/CalendarConfig'; import { CoreEvents } from '../constants/CoreEvents'; -/** - * ViewManager - Optimized view switching with consolidated event handling - * Reduces redundant DOM queries and event listener setups - */ + export class ViewManager { private eventBus: IEventBus; private config: CalendarConfig; private currentView: CalendarView = 'week'; private buttonListeners: Map = new Map(); - // Cached DOM elements for performance - private cachedViewButtons: NodeListOf | null = null; - private cachedWorkweekButtons: NodeListOf | null = null; - private lastButtonCacheTime: number = 0; - private readonly CACHE_DURATION = 5000; // 5 seconds - constructor(eventBus: IEventBus, config: CalendarConfig) { this.eventBus = eventBus; this.config = config; this.setupEventListeners(); } - /** - * Consolidated event listener setup with better organization - */ private setupEventListeners(): void { - // Event bus listeners this.setupEventBusListeners(); - - // DOM button handlers with consolidated logic this.setupButtonHandlers(); } - /** - * Setup event bus listeners with proper cleanup tracking - */ + private setupEventBusListeners(): void { this.eventBus.on(CoreEvents.INITIALIZED, () => { this.initializeView(); }); - // Remove redundant VIEW_CHANGED listener that causes circular calls - // changeView is called directly from button handlers - this.eventBus.on(CoreEvents.DATE_CHANGED, () => { this.refreshCurrentView(); }); } - /** - * Consolidated button handler setup with shared logic - */ private setupButtonHandlers(): void { - // Setup view buttons with consolidated handler this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => { if (this.isValidView(value)) { this.changeView(value as CalendarView); } }); - // Setup workweek buttons with consolidated handler this.setupButtonGroup('swp-preset-button[data-workweek]', 'data-workweek', (value) => { this.changeWorkweek(value); }); } - /** - * Generic button group setup to eliminate duplicate code - */ + private setupButtonGroup(selector: string, attribute: string, handler: (value: string) => void): void { const buttons = document.querySelectorAll(selector); buttons.forEach(button => { @@ -87,42 +59,24 @@ export class ViewManager { }); } - /** - * Get cached view buttons with cache invalidation - */ private getViewButtons(): NodeListOf { - const now = Date.now(); - if (!this.cachedViewButtons || (now - this.lastButtonCacheTime) > this.CACHE_DURATION) { - this.cachedViewButtons = document.querySelectorAll('swp-view-button[data-view]'); - this.lastButtonCacheTime = now; - } - return this.cachedViewButtons; + + return document.querySelectorAll('swp-view-button[data-view]'); + } - /** - * Get cached workweek buttons with cache invalidation - */ private getWorkweekButtons(): NodeListOf { - const now = Date.now(); - if (!this.cachedWorkweekButtons || (now - this.lastButtonCacheTime) > this.CACHE_DURATION) { - this.cachedWorkweekButtons = document.querySelectorAll('swp-preset-button[data-workweek]'); - this.lastButtonCacheTime = now; - } - return this.cachedWorkweekButtons; + + return document.querySelectorAll('swp-preset-button[data-workweek]'); + } - /** - * Initialize view with consolidated button updates - */ private initializeView(): void { this.updateAllButtons(); this.emitViewRendered(); } - /** - * Optimized view change with debouncing - */ private changeView(newView: CalendarView): void { if (newView === this.currentView) return; @@ -130,34 +84,25 @@ export class ViewManager { this.currentView = newView; this.updateAllButtons(); - + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { previousView, currentView: newView }); } - /** - * Optimized workweek change with consolidated updates - */ private changeWorkweek(workweekId: string): void { - // Update the calendar config (does not emit events) + this.config.setWorkWeek(workweekId); - // Update button states using cached elements this.updateAllButtons(); - // Emit workweek change event with full payload const settings = this.config.getWorkWeekSettings(); this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, { workWeekId: workweekId, settings: settings }); } - - /** - * Consolidated button update method to eliminate duplicate code - */ private updateAllButtons(): void { this.updateButtonGroup( this.getViewButtons(), @@ -172,9 +117,6 @@ export class ViewManager { ); } - /** - * Generic button group update to eliminate duplicate logic - */ private updateButtonGroup(buttons: NodeListOf, attribute: string, activeValue: string): void { buttons.forEach(button => { const buttonValue = button.getAttribute(attribute); @@ -186,41 +128,19 @@ export class ViewManager { }); } - /** - * Emit view rendered event with current view - */ private emitViewRendered(): void { this.eventBus.emit(CoreEvents.VIEW_RENDERED, { view: this.currentView }); } - /** - * Refresh current view by emitting view rendered event - */ private refreshCurrentView(): void { this.emitViewRendered(); } - /** - * Validate if a string is a valid calendar view - */ private isValidView(view: string): view is CalendarView { return ['day', 'week', 'month'].includes(view); } - /** - * Get current active view - */ - public getCurrentView(): CalendarView { - return this.currentView; - } - - /** - * Public refresh method - */ - public refresh(): void { - this.refreshCurrentView(); - } } \ No newline at end of file diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index 0bf09a8..fa4ed7f 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -6,46 +6,17 @@ import { EventRenderingService } from './EventRendererManager'; * NavigationRenderer - Handles DOM rendering for navigation containers * Separated from NavigationManager to follow Single Responsibility Principle */ + export class NavigationRenderer { private eventBus: IEventBus; - - // Cached DOM elements to avoid redundant queries - private cachedWeekNumberElement: HTMLElement | null = null; - private cachedDateRangeElement: HTMLElement | null = null; constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) { this.eventBus = eventBus; this.setupEventListeners(); } + - /** - * Get cached week number element - */ - private getWeekNumberElement(): HTMLElement | null { - if (!this.cachedWeekNumberElement) { - this.cachedWeekNumberElement = document.querySelector('swp-week-number'); - } - return this.cachedWeekNumberElement; - } - - /** - * Get cached date range element - */ - private getDateRangeElement(): HTMLElement | null { - if (!this.cachedDateRangeElement) { - this.cachedDateRangeElement = document.querySelector('swp-date-range'); - } - return this.cachedDateRangeElement; - } - - /** - * Clear cached DOM elements (call when DOM structure changes) - */ - private clearCache(): void { - this.cachedWeekNumberElement = null; - this.cachedDateRangeElement = null; - } - + /** * Setup event listeners for DOM updates */ @@ -57,12 +28,11 @@ export class NavigationRenderer { }); } - /** - * Update week info in DOM elements using cached references - */ + private updateWeekInfoInDOM(weekNumber: number, dateRange: string): void { - const weekNumberElement = this.getWeekNumberElement(); - const dateRangeElement = this.getDateRangeElement(); + + const weekNumberElement = document.querySelector('swp-week-number'); + const dateRangeElement = document.querySelector('swp-date-range'); if (weekNumberElement) { weekNumberElement.textContent = `Week ${weekNumber}`;