months are also selected by calendar

This commit is contained in:
Ben Floyd 2025-12-08 17:40:37 -07:00
parent 8c0bd33713
commit b0f2fd0ec4
2 changed files with 43 additions and 43 deletions

View file

@ -15,7 +15,7 @@ export class CalendarRenderer extends Component {
private dateNavigationService: DateNavigationService;
private dateStateManager: DateStateManager;
private currentMonthView: Date;
private onDateSelected: (day: number) => Promise<void>;
private onDateSelected: (day: number, monthView: Date) => Promise<void>;
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<void>,
onDateSelected: (day: number, monthView: Date) => Promise<void>,
onDropdownClose?: () => void
) {
super();
@ -43,24 +43,24 @@ export class CalendarRenderer extends Component {
*/
async updateCalendarGrid(): Promise<void> {
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<ContentIndicator>[] = [];
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<void> {
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);

View file

@ -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();
}