// Custom scroll management for calendar week container import { eventBus } from '../core/EventBus'; import { CoreEvents } from '../constants/CoreEvents'; /** * Manages scrolling functionality for the calendar using native scrollbars */ export class ScrollManager { constructor(positionUtils) { this.scrollableContent = null; this.calendarContainer = null; this.timeAxis = null; this.calendarHeader = null; this.resizeObserver = null; this.positionUtils = positionUtils; this.init(); } init() { this.subscribeToEvents(); } /** * Public method to initialize scroll after grid is rendered */ initialize() { this.setupScrolling(); } subscribeToEvents() { // Handle navigation animation completion - sync time axis position eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => { this.syncTimeAxisPosition(); this.setupScrolling(); }); // Handle all-day row height changes eventBus.on('header:height-changed', () => { this.updateScrollableHeight(); }); // Handle header ready - refresh header reference and re-sync eventBus.on('header:ready', () => { this.calendarHeader = document.querySelector('swp-calendar-header'); if (this.scrollableContent && this.calendarHeader) { this.setupHorizontalScrollSynchronization(); this.syncCalendarHeaderPosition(); // Immediately sync position } this.updateScrollableHeight(); // Update height calculations }); // Handle window resize window.addEventListener('resize', () => { this.updateScrollableHeight(); }); // Listen for scroll to event time requests eventBus.on('scroll:to-event-time', (event) => { const customEvent = event; const { eventStartTime } = customEvent.detail; if (eventStartTime) { this.scrollToEventTime(eventStartTime); } }); } /** * Setup scrolling functionality after grid is rendered */ setupScrolling() { this.findElements(); if (this.scrollableContent && this.calendarContainer) { this.setupResizeObserver(); this.updateScrollableHeight(); this.setupScrollSynchronization(); } // Setup horizontal scrolling synchronization if (this.scrollableContent && this.calendarHeader) { this.setupHorizontalScrollSynchronization(); } } /** * Find DOM elements needed for scrolling */ findElements() { this.scrollableContent = document.querySelector('swp-scrollable-content'); this.calendarContainer = document.querySelector('swp-calendar-container'); this.timeAxis = document.querySelector('swp-time-axis'); this.calendarHeader = document.querySelector('swp-calendar-header'); } /** * Scroll to specific position */ scrollTo(scrollTop) { if (!this.scrollableContent) return; this.scrollableContent.scrollTop = scrollTop; } /** * Scroll to specific hour using PositionUtils */ scrollToHour(hour) { // Create time string for the hour const timeString = `${hour.toString().padStart(2, '0')}:00`; const scrollTop = this.positionUtils.timeToPixels(timeString); this.scrollTo(scrollTop); } /** * Scroll to specific event time * @param eventStartTime ISO string of event start time */ scrollToEventTime(eventStartTime) { try { const eventDate = new Date(eventStartTime); const eventHour = eventDate.getHours(); const eventMinutes = eventDate.getMinutes(); // Convert to decimal hour (e.g., 14:30 becomes 14.5) const decimalHour = eventHour + (eventMinutes / 60); this.scrollToHour(decimalHour); } catch (error) { console.warn('ScrollManager: Failed to scroll to event time:', error); } } /** * Setup ResizeObserver to monitor container size changes */ setupResizeObserver() { if (!this.calendarContainer) return; // Clean up existing observer if (this.resizeObserver) { this.resizeObserver.disconnect(); } this.resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { this.updateScrollableHeight(); } }); this.resizeObserver.observe(this.calendarContainer); } /** * Calculate and update scrollable content height dynamically */ updateScrollableHeight() { 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 calendar header height const calendarHeaderElement = document.querySelector('swp-calendar-header'); const headerHeight = calendarHeaderElement ? calendarHeaderElement.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 // 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 */ setupScrollSynchronization() { if (!this.scrollableContent || !this.timeAxis) return; // Throttle scroll events for better performance let scrollTimeout = null; this.scrollableContent.addEventListener('scroll', () => { if (scrollTimeout) { cancelAnimationFrame(scrollTimeout); } scrollTimeout = requestAnimationFrame(() => { this.syncTimeAxisPosition(); }); }); } /** * Synchronize time axis position with scrollable content */ syncTimeAxisPosition() { 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.style.transform = `translateY(-${scrollTop}px)`; // Debug logging (can be removed later) if (scrollTop % 100 === 0) { // Only log every 100px to avoid spam } } } /** * Setup horizontal scroll synchronization between scrollable content and calendar header */ setupHorizontalScrollSynchronization() { if (!this.scrollableContent || !this.calendarHeader) return; // Listen to horizontal scroll events this.scrollableContent.addEventListener('scroll', () => { this.syncCalendarHeaderPosition(); }); } /** * Synchronize calendar header position with scrollable content horizontal scroll */ syncCalendarHeaderPosition() { if (!this.scrollableContent || !this.calendarHeader) return; const scrollLeft = this.scrollableContent.scrollLeft; // Use transform for smooth performance this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`; // Debug logging (can be removed later) if (scrollLeft % 100 === 0) { // Only log every 100px to avoid spam } } } //# sourceMappingURL=ScrollManager.js.map