2025-07-29 00:52:01 +02:00
|
|
|
// Custom scroll management for calendar week container
|
|
|
|
|
|
|
|
|
|
import { eventBus } from '../core/EventBus';
|
|
|
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
|
|
|
|
import { EventTypes } from '../constants/EventTypes';
|
2025-08-09 00:31:44 +02:00
|
|
|
import { StateEvents } from '../types/CalendarState';
|
2025-07-29 00:52:01 +02:00
|
|
|
|
|
|
|
|
/**
|
2025-08-05 23:03:08 +02:00
|
|
|
* Manages scrolling functionality for the calendar using native scrollbars
|
2025-07-29 00:52:01 +02:00
|
|
|
*/
|
|
|
|
|
export class ScrollManager {
|
|
|
|
|
private scrollableContent: HTMLElement | null = null;
|
|
|
|
|
private calendarContainer: HTMLElement | null = null;
|
|
|
|
|
private timeAxis: HTMLElement | null = null;
|
2025-08-07 00:26:33 +02:00
|
|
|
private calendarHeader: HTMLElement | null = null;
|
2025-08-05 23:03:08 +02:00
|
|
|
private resizeObserver: ResizeObserver | null = null;
|
2025-07-29 21:22:13 +02:00
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
constructor() {
|
2025-08-09 00:31:44 +02:00
|
|
|
console.log('📜 ScrollManager: Constructor called');
|
2025-07-29 00:52:01 +02:00
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private init(): void {
|
|
|
|
|
this.subscribeToEvents();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
/**
|
|
|
|
|
* Public method to initialize scroll after grid is rendered
|
|
|
|
|
*/
|
|
|
|
|
public initialize(): void {
|
|
|
|
|
console.log('ScrollManager: Initialize called, setting up scrolling');
|
|
|
|
|
this.setupScrolling();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
private subscribeToEvents(): void {
|
|
|
|
|
|
2025-08-13 23:05:58 +02:00
|
|
|
// Handle new period shown
|
|
|
|
|
//we need to subscribe to appropriate event and then call setupScrolling() again
|
|
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
// Handle window resize
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
this.updateScrollableHeight();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Setup scrolling functionality after grid is rendered
|
|
|
|
|
*/
|
|
|
|
|
private setupScrolling(): void {
|
|
|
|
|
this.findElements();
|
|
|
|
|
|
2025-08-05 23:03:08 +02:00
|
|
|
if (this.scrollableContent && this.calendarContainer) {
|
2025-07-29 00:52:01 +02:00
|
|
|
this.setupResizeObserver();
|
|
|
|
|
this.updateScrollableHeight();
|
|
|
|
|
this.setupScrollSynchronization();
|
|
|
|
|
}
|
2025-07-29 21:22:13 +02:00
|
|
|
|
2025-08-05 23:03:08 +02:00
|
|
|
// Setup horizontal scrolling synchronization
|
2025-08-07 00:26:33 +02:00
|
|
|
if (this.scrollableContent && this.calendarHeader) {
|
2025-07-29 21:22:13 +02:00
|
|
|
this.setupHorizontalScrollSynchronization();
|
|
|
|
|
}
|
2025-07-29 00:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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');
|
2025-08-07 00:26:33 +02:00
|
|
|
this.calendarHeader = document.querySelector('swp-calendar-header');
|
2025-08-13 23:05:58 +02:00
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scroll to specific position
|
|
|
|
|
*/
|
|
|
|
|
scrollTo(scrollTop: number): void {
|
|
|
|
|
if (!this.scrollableContent) return;
|
|
|
|
|
|
2025-08-05 23:03:08 +02:00
|
|
|
this.scrollableContent.scrollTop = scrollTop;
|
2025-07-29 00:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scroll to specific hour
|
|
|
|
|
*/
|
|
|
|
|
scrollToHour(hour: number): void {
|
2025-08-09 00:31:44 +02:00
|
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
|
|
|
const hourHeight = gridSettings.hourHeight;
|
|
|
|
|
const dayStartHour = gridSettings.dayStartHour;
|
2025-07-29 00:52:01 +02:00
|
|
|
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;
|
|
|
|
|
|
2025-08-07 00:26:33 +02:00
|
|
|
// Find calendar header height
|
|
|
|
|
const calendarHeaderElement = document.querySelector('swp-calendar-header');
|
|
|
|
|
const headerHeight = calendarHeaderElement ? calendarHeaderElement.getBoundingClientRect().height : 80;
|
2025-07-29 00:52:01 +02:00
|
|
|
|
|
|
|
|
// Calculate available height for scrollable content
|
|
|
|
|
const availableHeight = containerRect.height - headerHeight;
|
|
|
|
|
|
2025-08-05 23:03:08 +02:00
|
|
|
// Calculate available width (container width minus time-axis)
|
|
|
|
|
const availableWidth = containerRect.width - 60; // 60px time-axis
|
2025-07-29 23:17:52 +02:00
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
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);
|
2025-07-29 23:17:52 +02:00
|
|
|
console.log('- Available width:', availableWidth);
|
2025-07-29 00:52:01 +02:00
|
|
|
|
2025-07-29 23:17:52 +02:00
|
|
|
// Set the height and width on scrollable content
|
2025-07-29 00:52:01 +02:00
|
|
|
if (availableHeight > 0) {
|
|
|
|
|
this.scrollableContent.style.height = `${availableHeight}px`;
|
|
|
|
|
}
|
2025-07-29 23:17:52 +02:00
|
|
|
if (availableWidth > 0) {
|
|
|
|
|
this.scrollableContent.style.width = `${availableWidth}px`;
|
|
|
|
|
}
|
2025-07-29 00:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
2025-08-01 23:45:13 +02:00
|
|
|
const timeAxisContent = this.timeAxis.querySelector('swp-time-axis-content');
|
2025-07-29 00:52:01 +02:00
|
|
|
|
2025-08-01 23:45:13 +02:00
|
|
|
if (timeAxisContent) {
|
|
|
|
|
// Use transform for smooth performance
|
2025-08-05 21:56:06 +02:00
|
|
|
(timeAxisContent as HTMLElement).style.transform = `translateY(-${scrollTop}px)`;
|
2025-08-01 23:45:13 +02:00
|
|
|
|
|
|
|
|
// 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`);
|
|
|
|
|
}
|
2025-07-29 00:52:01 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 21:22:13 +02:00
|
|
|
/**
|
2025-08-07 00:26:33 +02:00
|
|
|
* Setup horizontal scroll synchronization between scrollable content and calendar header
|
2025-07-29 21:22:13 +02:00
|
|
|
*/
|
|
|
|
|
private setupHorizontalScrollSynchronization(): void {
|
2025-08-07 00:26:33 +02:00
|
|
|
if (!this.scrollableContent || !this.calendarHeader) return;
|
2025-07-29 21:22:13 +02:00
|
|
|
|
|
|
|
|
console.log('ScrollManager: Setting up horizontal scroll synchronization');
|
|
|
|
|
|
|
|
|
|
// Listen to horizontal scroll events
|
|
|
|
|
this.scrollableContent.addEventListener('scroll', () => {
|
2025-08-07 00:26:33 +02:00
|
|
|
this.syncCalendarHeaderPosition();
|
2025-07-29 21:22:13 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-07 00:26:33 +02:00
|
|
|
* Synchronize calendar header position with scrollable content horizontal scroll
|
2025-07-29 21:22:13 +02:00
|
|
|
*/
|
2025-08-07 00:26:33 +02:00
|
|
|
private syncCalendarHeaderPosition(): void {
|
|
|
|
|
if (!this.scrollableContent || !this.calendarHeader) return;
|
2025-07-29 21:22:13 +02:00
|
|
|
|
|
|
|
|
const scrollLeft = this.scrollableContent.scrollLeft;
|
|
|
|
|
|
|
|
|
|
// Use transform for smooth performance
|
2025-08-07 00:26:33 +02:00
|
|
|
this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`;
|
2025-07-29 21:22:13 +02:00
|
|
|
|
|
|
|
|
// Debug logging (can be removed later)
|
|
|
|
|
if (scrollLeft % 100 === 0) { // Only log every 100px to avoid spam
|
2025-08-07 00:26:33 +02:00
|
|
|
console.log(`ScrollManager: Synced calendar-header to scrollLeft: ${scrollLeft}px`);
|
2025-07-29 21:22:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 00:52:01 +02:00
|
|
|
/**
|
|
|
|
|
* Cleanup resources
|
|
|
|
|
*/
|
|
|
|
|
destroy(): void {
|
|
|
|
|
if (this.resizeObserver) {
|
|
|
|
|
this.resizeObserver.disconnect();
|
|
|
|
|
this.resizeObserver = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|