diff --git a/src/slices/calendar-navigation/CalendarRenderer.ts b/src/slices/calendar-navigation/CalendarRenderer.ts index 6a49953..28b69fa 100644 --- a/src/slices/calendar-navigation/CalendarRenderer.ts +++ b/src/slices/calendar-navigation/CalendarRenderer.ts @@ -15,7 +15,7 @@ export class CalendarRenderer extends Component { private dateNavigationService: DateNavigationService; private dateStateManager: DateStateManager; private currentMonthView: Date; - private onDateSelected: (day: number) => Promise; + private onDateSelected: (day: number, monthView: Date) => Promise; private onDropdownClose?: () => void; private eventRegistry: EventHandlerRegistry; @@ -24,7 +24,7 @@ export class CalendarRenderer extends Component { contentIndicatorService: ContentIndicatorService, dateNavigationService: DateNavigationService, currentMonthView: Date, - onDateSelected: (day: number) => Promise, + onDateSelected: (day: number, monthView: Date) => Promise, onDropdownClose?: () => void ) { super(); @@ -43,24 +43,24 @@ export class CalendarRenderer extends Component { */ async updateCalendarGrid(): Promise { const endTiming = performanceMonitor.startTiming('calendar-grid-update'); - + try { if (this.grid.children.length > 0) { await this.updateGridContent(); return; } - + // Use DocumentFragment for batch DOM operations const fragment = document.createDocumentFragment(); - + const state = this.dateStateManager.getState(); const currentDate = this.currentMonthView; const daysInMonth = this.dateNavigationService.getDaysInMonth( - currentDate.getFullYear(), + currentDate.getFullYear(), currentDate.getMonth() ); const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getDay(); - + // Create day headers for (let i = 0; i < 7; i++) { const dayHeader = document.createElement('div'); @@ -68,79 +68,79 @@ export class CalendarRenderer extends Component { dayHeader.textContent = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][i]; fragment.appendChild(dayHeader); } - + // Create empty day placeholders for (let i = 0; i < firstDayOfMonth; i++) { const emptyDay = document.createElement('div'); emptyDay.className = 'streams-bar-day empty'; fragment.appendChild(emptyDay); } - + // Batch create all day elements and prepare content indicators const dayElements: HTMLElement[] = []; const contentPromises: Promise[] = []; - + for (let day = 1; day <= daysInMonth; day++) { const dayEl = document.createElement('div'); dayEl.className = 'streams-bar-day'; dayEl.setAttribute('data-day', String(day)); - + const dateContainer = document.createElement('div'); dateContainer.className = 'streams-date-container'; dateContainer.textContent = String(day); dayEl.appendChild(dateContainer); - + const dotContainer = document.createElement('div'); dotContainer.className = 'streams-dot-container'; dayEl.appendChild(dotContainer); - + dayElements.push(dayEl); fragment.appendChild(dayEl); - + // Prepare content indicator promise const dayDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); contentPromises.push(this.contentIndicatorService.getContentIndicator(dayDate)); } - + // Clear grid and append all elements at once this.grid.empty(); this.grid.appendChild(fragment); - + // Process content indicators and apply styles in batch const contentIndicators = await Promise.all(contentPromises); - + // Batch apply styles and content this.applyDayStylesAndContent(dayElements, contentIndicators, currentDate, state); - + // Use event delegation for better performance this.setupCalendarEventDelegation(); - + } finally { endTiming(); } } - + /** * Update existing calendar grid content */ async updateGridContent(): Promise { const endTiming = performanceMonitor.startTiming('calendar-grid-content-update'); - + try { const dayElements = Array.from(this.grid.querySelectorAll('.streams-bar-day:not(.empty)')) as HTMLElement[]; const state = this.dateStateManager.getState(); const currentDate = this.currentMonthView; - + // Batch prepare all content indicators const contentPromises = dayElements.map((dayEl, i) => { const day = i + 1; const dayDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); return this.contentIndicatorService.getContentIndicator(dayDate); }); - + // Wait for all content indicators to load const contentIndicators = await Promise.all(contentPromises); - + // Batch apply all updates this.applyDayStylesAndContent(dayElements, contentIndicators, currentDate, state); } finally { @@ -166,9 +166,9 @@ export class CalendarRenderer extends Component { * Batch apply styles and content to day elements for optimal performance */ private applyDayStylesAndContent( - dayElements: HTMLElement[], - contentIndicators: ContentIndicator[], - currentDate: Date, + dayElements: HTMLElement[], + contentIndicators: ContentIndicator[], + currentDate: Date, state: DateState ): void { // Use requestAnimationFrame to batch DOM updates @@ -178,29 +178,29 @@ export class CalendarRenderer extends Component { const content = contentIndicators[i]; const dayDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); const dateString = this.dateNavigationService.formatDateString(dayDate); - + // Get dot container once const dotContainer = dayEl.querySelector('.streams-dot-container') as HTMLElement; - + // Clear existing dots if (dotContainer) { while (dotContainer.firstChild) { dotContainer.removeChild(dotContainer.firstChild); } } - + // Apply classes efficiently const classList = dayEl.classList; classList.remove('viewed', 'today'); - + if (dateString === state.currentViewedDate) { classList.add('viewed'); } - + if (this.dateNavigationService.isToday(dayDate)) { classList.add('today'); } - + // Add content dots if needed if (content.exists && dotContainer) { const dots = content.size === 'small' ? 1 : content.size === 'medium' ? 2 : 3; @@ -214,7 +214,7 @@ export class CalendarRenderer extends Component { if (content.isEncrypted) { const encryptionIcon = document.createElement('div'); encryptionIcon.className = 'streams-encryption-icon'; - + // Set the appropriate icon based on lock status if (content.isLocked) { setIcon(encryptionIcon, 'lock'); @@ -223,7 +223,7 @@ export class CalendarRenderer extends Component { setIcon(encryptionIcon, 'unlock'); encryptionIcon.setAttribute('title', 'Encrypted file (unlocked)'); } - + dotContainer.appendChild(encryptionIcon); } } @@ -238,25 +238,25 @@ export class CalendarRenderer extends Component { // Create single event handler for all day clicks const calendarClickHandler = (e: Event) => { const target = e.target as HTMLElement; - + // Close dropdown if it's open when clicking anywhere in calendar if (this.onDropdownClose) { this.onDropdownClose(); } - + const dayEl = target.closest('.streams-bar-day:not(.empty)') as HTMLElement; - + if (dayEl) { e.preventDefault(); e.stopPropagation(); - + const day = parseInt(dayEl.getAttribute('data-day') || '0', 10); if (day > 0) { - this.onDateSelected(day); + this.onDateSelected(day, this.currentMonthView); } } }; - + // Register event listeners using EventHandlerRegistry for automatic cleanup this.eventRegistry.register(this.grid, 'click', calendarClickHandler); this.eventRegistry.register(this.grid, 'touchend', calendarClickHandler, { passive: true } as AddEventListenerOptions); diff --git a/src/slices/calendar-navigation/ComponentUIBuilder.ts b/src/slices/calendar-navigation/ComponentUIBuilder.ts index ba0e7b6..01b110d 100644 --- a/src/slices/calendar-navigation/ComponentUIBuilder.ts +++ b/src/slices/calendar-navigation/ComponentUIBuilder.ts @@ -308,8 +308,8 @@ export class ComponentUIBuilder { this.contentIndicatorService, this.dateNavigationService, currentMonthView, - async (day: number) => { - await this.dateNavigationService.selectDate(currentMonthView, day); + async (day: number, monthView: Date) => { + await this.dateNavigationService.selectDate(monthView, day); if (this.callbacks.isExpanded()) { this.callbacks.toggleExpanded(); }