// Custom scroll management for calendar week container import { eventBus } from '../core/EventBus'; import { calendarConfig } from '../core/CalendarConfig'; import { EventTypes } from '../constants/EventTypes'; /** * Manages scrolling functionality for the calendar using native scrollbars */ export class ScrollManager { private scrollableContent: HTMLElement | null = null; private calendarContainer: HTMLElement | null = null; private timeAxis: HTMLElement | null = null; private weekHeader: HTMLElement | null = null; private resizeObserver: ResizeObserver | null = null; constructor() { this.init(); } private init(): void { this.subscribeToEvents(); } private subscribeToEvents(): void { // Initialize scroll when grid is rendered eventBus.on(EventTypes.GRID_RENDERED, () => { console.log('ScrollManager: Received GRID_RENDERED event'); this.setupScrolling(); }); // Handle window resize window.addEventListener('resize', () => { this.updateScrollableHeight(); }); // Handle config updates for scrollbar styling eventBus.on(EventTypes.CONFIG_UPDATE, (event: CustomEvent) => { const { key } = event.detail; if (key.startsWith('scrollbar')) { this.applyScrollbarStyling(); } }); } /** * Setup scrolling functionality after grid is rendered */ private setupScrolling(): void { this.findElements(); if (this.scrollableContent && this.calendarContainer) { this.setupResizeObserver(); this.updateScrollableHeight(); this.setupScrollSynchronization(); this.applyScrollbarStyling(); } // Setup horizontal scrolling synchronization if (this.scrollableContent && this.weekHeader) { this.setupHorizontalScrollSynchronization(); } } /** * Apply scrollbar styling from configuration */ private applyScrollbarStyling(): void { if (!this.scrollableContent) return; // Get scrollbar configuration const scrollbarWidth = calendarConfig.get('scrollbarWidth'); const scrollbarColor = calendarConfig.get('scrollbarColor'); const scrollbarTrackColor = calendarConfig.get('scrollbarTrackColor'); const scrollbarHoverColor = calendarConfig.get('scrollbarHoverColor'); const scrollbarBorderRadius = calendarConfig.get('scrollbarBorderRadius'); // Apply CSS custom properties to both the element and document root const root = document.documentElement; // Set on scrollable content this.scrollableContent.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`); this.scrollableContent.style.setProperty('--scrollbar-color', scrollbarColor); this.scrollableContent.style.setProperty('--scrollbar-track-color', scrollbarTrackColor); this.scrollableContent.style.setProperty('--scrollbar-hover-color', scrollbarHoverColor); this.scrollableContent.style.setProperty('--scrollbar-border-radius', `${scrollbarBorderRadius}px`); // Also set on root for global access root.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`); root.style.setProperty('--scrollbar-color', scrollbarColor); root.style.setProperty('--scrollbar-track-color', scrollbarTrackColor); root.style.setProperty('--scrollbar-hover-color', scrollbarHoverColor); root.style.setProperty('--scrollbar-border-radius', `${scrollbarBorderRadius}px`); console.log('ScrollManager: Applied scrollbar styling', { width: `${scrollbarWidth}px`, color: scrollbarColor, trackColor: scrollbarTrackColor, hoverColor: scrollbarHoverColor, borderRadius: `${scrollbarBorderRadius}px` }); } /** * Find DOM elements needed for scrolling */ private findElements(): void { this.scrollableContent = document.querySelector('swp-scrollable-content'); this.calendarContainer = document.querySelector('swp-calendar-container'); this.timeAxis = document.querySelector('swp-time-axis'); this.weekHeader = document.querySelector('swp-week-header'); console.log('ScrollManager: Found elements:', { scrollableContent: !!this.scrollableContent, calendarContainer: !!this.calendarContainer, timeAxis: !!this.timeAxis, weekHeader: !!this.weekHeader }); } /** * Scroll to specific position */ scrollTo(scrollTop: number): void { if (!this.scrollableContent) return; this.scrollableContent.scrollTop = scrollTop; } /** * Scroll to specific hour */ scrollToHour(hour: number): void { const hourHeight = calendarConfig.get('hourHeight'); const dayStartHour = calendarConfig.get('dayStartHour'); const scrollTop = (hour - dayStartHour) * hourHeight; this.scrollTo(scrollTop); } /** * Setup ResizeObserver to monitor container size changes */ private setupResizeObserver(): void { if (!this.calendarContainer) return; // Clean up existing observer if (this.resizeObserver) { this.resizeObserver.disconnect(); } this.resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { console.log('ScrollManager: Container resized', entry.contentRect); this.updateScrollableHeight(); } }); this.resizeObserver.observe(this.calendarContainer); } /** * Calculate and update scrollable content height dynamically */ private updateScrollableHeight(): void { if (!this.scrollableContent || !this.calendarContainer) return; // Get calendar container height const containerRect = this.calendarContainer.getBoundingClientRect(); // Find navigation height const navigation = document.querySelector('swp-calendar-nav'); const navHeight = navigation ? navigation.getBoundingClientRect().height : 0; // Find week header height const weekHeader = document.querySelector('swp-week-header'); const headerHeight = weekHeader ? weekHeader.getBoundingClientRect().height : 80; // Calculate available height for scrollable content const availableHeight = containerRect.height - headerHeight; // Calculate available width (container width minus time-axis) const availableWidth = containerRect.width - 60; // 60px time-axis console.log('ScrollManager: Dynamic height calculation'); console.log('- Container height:', containerRect.height); console.log('- Navigation height:', navHeight); console.log('- Header height:', headerHeight); console.log('- Available height:', availableHeight); console.log('- Available width:', availableWidth); // Set the height and width on scrollable content if (availableHeight > 0) { this.scrollableContent.style.height = `${availableHeight}px`; } if (availableWidth > 0) { this.scrollableContent.style.width = `${availableWidth}px`; } } /** * Setup scroll synchronization between scrollable content and time axis */ private setupScrollSynchronization(): void { if (!this.scrollableContent || !this.timeAxis) return; console.log('ScrollManager: Setting up scroll synchronization'); // Throttle scroll events for better performance let scrollTimeout: number | null = null; this.scrollableContent.addEventListener('scroll', () => { if (scrollTimeout) { cancelAnimationFrame(scrollTimeout); } scrollTimeout = requestAnimationFrame(() => { this.syncTimeAxisPosition(); }); }); } /** * Synchronize time axis position with scrollable content */ private syncTimeAxisPosition(): void { if (!this.scrollableContent || !this.timeAxis) return; const scrollTop = this.scrollableContent.scrollTop; const timeAxisContent = this.timeAxis.querySelector('swp-time-axis-content'); if (timeAxisContent) { // Use transform for smooth performance (timeAxisContent as HTMLElement).style.transform = `translateY(-${scrollTop}px)`; // Debug logging (can be removed later) if (scrollTop % 100 === 0) { // Only log every 100px to avoid spam console.log(`ScrollManager: Synced time-axis to scrollTop: ${scrollTop}px`); } } } /** * Setup horizontal scroll synchronization between scrollable content and week header */ private setupHorizontalScrollSynchronization(): void { if (!this.scrollableContent || !this.weekHeader) return; console.log('ScrollManager: Setting up horizontal scroll synchronization'); // Listen to horizontal scroll events this.scrollableContent.addEventListener('scroll', () => { this.syncWeekHeaderPosition(); }); } /** * Synchronize week header position with scrollable content horizontal scroll */ private syncWeekHeaderPosition(): void { if (!this.scrollableContent || !this.weekHeader) return; const scrollLeft = this.scrollableContent.scrollLeft; // Use transform for smooth performance this.weekHeader.style.transform = `translateX(-${scrollLeft}px)`; // Debug logging (can be removed later) if (scrollLeft % 100 === 0) { // Only log every 100px to avoid spam console.log(`ScrollManager: Synced week-header to scrollLeft: ${scrollLeft}px`); } } /** * Cleanup resources */ destroy(): void { if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } } }