diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 7451a2a..9e0784c 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -16,6 +16,10 @@ export class NavigationManager { private currentWeek: Date; private targetWeek: Date; private animationQueue: number = 0; + + // Cached DOM elements to avoid redundant queries + private cachedCalendarContainer: HTMLElement | null = null; + private cachedCurrentGrid: HTMLElement | null = null; constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) { this.eventBus = eventBus; @@ -31,6 +35,37 @@ export class NavigationManager { // Don't update week info immediately - wait for DOM to be ready } + /** + * Get cached calendar container element + */ + private getCalendarContainer(): HTMLElement | null { + if (!this.cachedCalendarContainer) { + this.cachedCalendarContainer = document.querySelector('swp-calendar-container'); + } + return this.cachedCalendarContainer; + } + + /** + * Get cached current grid element + */ + private getCurrentGrid(): HTMLElement | null { + const container = this.getCalendarContainer(); + if (!container) return null; + + if (!this.cachedCurrentGrid) { + this.cachedCurrentGrid = container.querySelector('swp-grid-container:not([data-prerendered])'); + } + return this.cachedCurrentGrid; + } + + /** + * Clear cached DOM elements (call when DOM structure changes) + */ + private clearCache(): void { + this.cachedCalendarContainer = null; + this.cachedCurrentGrid = null; + } + private setupEventListeners(): void { // Initial DOM update when calendar is initialized this.eventBus.on(CoreEvents.INITIALIZED, () => { @@ -137,8 +172,8 @@ export class NavigationManager { * Animation transition using pre-rendered containers when available */ private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void { - const container = document.querySelector('swp-calendar-container'); - const currentGrid = container?.querySelector('swp-grid-container:not([data-prerendered])'); + const container = this.getCalendarContainer(); + const currentGrid = this.getCurrentGrid(); if (!container || !currentGrid) { return; @@ -148,7 +183,7 @@ export class NavigationManager { let newGrid: HTMLElement; // Always create a fresh container for consistent behavior - newGrid = this.navigationRenderer.renderContainer(container as HTMLElement, targetWeek); + newGrid = this.navigationRenderer.renderContainer(container, targetWeek); // Clear any existing transforms before animation @@ -187,6 +222,9 @@ export class NavigationManager { newGrid.style.position = 'relative'; newGrid.removeAttribute('data-prerendered'); + // Clear cache since DOM structure changed + this.clearCache(); + // Update state this.currentWeek = new Date(targetWeek); this.animationQueue--; diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index 1f907d7..2c00be1 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -26,9 +26,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { private draggedClone: HTMLElement | null = null; private originalEvent: HTMLElement | null = null; - constructor(config: CalendarConfig) { + constructor(config: CalendarConfig, dateCalculator?: DateCalculator) { this.config = config; - this.dateCalculator = new DateCalculator(config); + this.dateCalculator = dateCalculator || new DateCalculator(config); } /** @@ -68,12 +68,27 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { // Handle navigation period change (when slide animation completes) eventBus.on(CoreEvents.PERIOD_CHANGED, () => { // Animate all-day height after navigation completes - import('./HeaderRenderer').then(({ DateHeaderRenderer }) => { - const headerRenderer = new DateHeaderRenderer(); - headerRenderer.checkAndAnimateAllDayHeight(); - }); + this.triggerAllDayHeightAnimation(); }); } + + /** + * Trigger all-day height animation without creating new renderer instance + */ + private triggerAllDayHeightAnimation(): void { + import('./HeaderRenderer').then(({ DateHeaderRenderer }) => { + const headerRenderer = new DateHeaderRenderer(); + headerRenderer.checkAndAnimateAllDayHeight(); + }); + } + + /** + * Cleanup method for proper resource management + */ + public destroy(): void { + this.draggedClone = null; + this.originalEvent = null; + } /** * Get original event duration from data-duration attribute @@ -171,13 +186,24 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { } /** - * Format time from total minutes + * Unified time formatting method - handles both total minutes and Date objects */ - private formatTime(totalMinutes: number): string { - const hours = Math.floor(totalMinutes / 60) % 24; - const minutes = totalMinutes % 60; + private formatTime(input: number | Date | string): string { + let hours: number, minutes: number; + + if (typeof input === 'number') { + // Total minutes input + hours = Math.floor(input / 60) % 24; + minutes = input % 60; + } else { + // Date or ISO string input + const date = typeof input === 'string' ? new Date(input) : input; + hours = date.getHours(); + minutes = date.getMinutes(); + } + const period = hours >= 12 ? 'PM' : 'AM'; - const displayHours = hours % 12 || 12; + const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours); return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`; } @@ -331,10 +357,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { this.draggedClone = allDayEvent; // Check if height animation is needed - import('./HeaderRenderer').then(({ DateHeaderRenderer }) => { - const headerRenderer = new DateHeaderRenderer(); - headerRenderer.checkAndAnimateAllDayHeight(); - }); + this.triggerAllDayHeightAnimation(); } @@ -556,9 +579,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { // Color is now handled by CSS classes based on data-type attribute - // Format time for display - const startTime = this.formatTimeFromISOString(event.start); - const endTime = this.formatTimeFromISOString(event.end); + // Format time for display using unified method + const startTime = this.formatTime(event.start); + const endTime = this.formatTime(event.end); // Calculate duration in minutes const startDate = new Date(event.start); @@ -599,17 +622,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { return { top, height }; } - protected formatTimeFromISOString(isoString: string): string { - const date = new Date(isoString); - const hours = date.getHours(); - const minutes = date.getMinutes(); - const period = hours >= 12 ? 'PM' : 'AM'; - const displayHour = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours); - - return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`; - } - - /** * Calculate grid column span for event */ @@ -666,8 +678,8 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { * Date-based event renderer */ export class DateEventRenderer extends BaseEventRenderer { - constructor(config: CalendarConfig) { - super(config); + constructor(config: CalendarConfig, dateCalculator?: DateCalculator) { + super(config, dateCalculator); this.setupDragEventListeners(); } diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index 4616965..60efdfd 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -15,6 +15,10 @@ export class NavigationRenderer { private config: CalendarConfig; private dateCalculator: DateCalculator; private eventRenderer: EventRenderingService; + + // Cached DOM elements to avoid redundant queries + private cachedWeekNumberElement: HTMLElement | null = null; + private cachedDateRangeElement: HTMLElement | null = null; constructor(eventBus: IEventBus, config: CalendarConfig, eventRenderer: EventRenderingService) { this.eventBus = eventBus; @@ -24,6 +28,34 @@ export class NavigationRenderer { 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 */ @@ -36,11 +68,11 @@ export class NavigationRenderer { } /** - * Update week info in DOM elements + * Update week info in DOM elements using cached references */ private updateWeekInfoInDOM(weekNumber: number, dateRange: string): void { - const weekNumberElement = document.querySelector('swp-week-number'); - const dateRangeElement = document.querySelector('swp-date-range'); + const weekNumberElement = this.getWeekNumberElement(); + const dateRangeElement = this.getDateRangeElement(); if (weekNumberElement) { weekNumberElement.textContent = `Week ${weekNumber}`; @@ -235,4 +267,11 @@ export class NavigationRenderer { }); } + /** + * Public cleanup method for cached elements + */ + public destroy(): void { + this.clearCache(); + } + } \ No newline at end of file