Refactors calendar event rendering and management
Improves code organization and maintainability by separating concerns related to all-day event rendering, header management, and event resizing. Moves all-day event rendering logic into a dedicated `AllDayEventRenderer` class, utilizing the factory pattern for event element creation. Refactors `AllDayManager` to handle all-day row height animations, separated from `HeaderManager`. Removes the `ResizeManager` and related functionality. These changes aim to reduce code duplication, improve testability, and enhance the overall architecture of the calendar component.
This commit is contained in:
parent
e0b83ebd70
commit
c07d83d86f
13 changed files with 599 additions and 1306 deletions
220
src/managers/AllDayManager.ts
Normal file
220
src/managers/AllDayManager.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
// All-day row height management and animations
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||
|
||||
/**
|
||||
* AllDayManager - Handles all-day row height animations and management
|
||||
* Separated from HeaderManager for clean responsibility separation
|
||||
*/
|
||||
export class AllDayManager {
|
||||
private cachedAllDayContainer: HTMLElement | null = null;
|
||||
private cachedCalendarHeader: HTMLElement | null = null;
|
||||
private cachedHeaderSpacer: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
// Bind methods for event listeners
|
||||
this.checkAndAnimateAllDayHeight = this.checkAndAnimateAllDayHeight.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached all-day container element
|
||||
*/
|
||||
private getAllDayContainer(): HTMLElement | null {
|
||||
if (!this.cachedAllDayContainer) {
|
||||
const calendarHeader = this.getCalendarHeader();
|
||||
if (calendarHeader) {
|
||||
this.cachedAllDayContainer = calendarHeader.querySelector('swp-allday-container');
|
||||
}
|
||||
}
|
||||
return this.cachedAllDayContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached calendar header element
|
||||
*/
|
||||
private getCalendarHeader(): HTMLElement | null {
|
||||
if (!this.cachedCalendarHeader) {
|
||||
this.cachedCalendarHeader = document.querySelector('swp-calendar-header');
|
||||
}
|
||||
return this.cachedCalendarHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached header spacer element
|
||||
*/
|
||||
private getHeaderSpacer(): HTMLElement | null {
|
||||
if (!this.cachedHeaderSpacer) {
|
||||
this.cachedHeaderSpacer = document.querySelector('swp-header-spacer');
|
||||
}
|
||||
return this.cachedHeaderSpacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate all-day height based on number of rows
|
||||
*/
|
||||
private calculateAllDayHeight(targetRows: number): {
|
||||
targetHeight: number;
|
||||
currentHeight: number;
|
||||
heightDifference: number;
|
||||
} {
|
||||
const root = document.documentElement;
|
||||
const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT;
|
||||
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
|
||||
const heightDifference = targetHeight - currentHeight;
|
||||
|
||||
return { targetHeight, currentHeight, heightDifference };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached DOM elements (call when DOM structure changes)
|
||||
*/
|
||||
private clearCache(): void {
|
||||
this.cachedCalendarHeader = null;
|
||||
this.cachedAllDayContainer = null;
|
||||
this.cachedHeaderSpacer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all-day row to show events
|
||||
*/
|
||||
public expandAllDayRow(): void {
|
||||
const { currentHeight } = this.calculateAllDayHeight(0);
|
||||
|
||||
if (currentHeight === 0) {
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse all-day row when no events
|
||||
*/
|
||||
public collapseAllDayRow(): void {
|
||||
this.animateToRows(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current all-day events and animate to correct height
|
||||
*/
|
||||
public checkAndAnimateAllDayHeight(): void {
|
||||
const container = this.getAllDayContainer();
|
||||
if (!container) return;
|
||||
|
||||
const allDayEvents = container.querySelectorAll('swp-allday-event');
|
||||
|
||||
// Calculate required rows - 0 if no events (will collapse)
|
||||
let maxRows = 0;
|
||||
|
||||
if (allDayEvents.length > 0) {
|
||||
// Expand events to all dates they span and group by date
|
||||
const expandedEventsByDate: Record<string, string[]> = {};
|
||||
|
||||
(Array.from(allDayEvents) as HTMLElement[]).forEach((event: HTMLElement) => {
|
||||
const startISO = event.dataset.start || '';
|
||||
const endISO = event.dataset.end || startISO;
|
||||
const eventId = event.dataset.eventId || '';
|
||||
|
||||
// Extract dates from ISO strings
|
||||
const startDate = startISO.split('T')[0]; // YYYY-MM-DD
|
||||
const endDate = endISO.split('T')[0]; // YYYY-MM-DD
|
||||
|
||||
// Loop through all dates from start to end
|
||||
let current = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
while (current <= end) {
|
||||
const dateStr = current.toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
|
||||
if (!expandedEventsByDate[dateStr]) {
|
||||
expandedEventsByDate[dateStr] = [];
|
||||
}
|
||||
expandedEventsByDate[dateStr].push(eventId);
|
||||
|
||||
// Move to next day
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Find max rows needed
|
||||
maxRows = Math.max(
|
||||
...Object.values(expandedEventsByDate).map(ids => ids?.length || 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// Animate to required rows (0 = collapse, >0 = expand)
|
||||
this.animateToRows(maxRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate all-day container to specific number of rows
|
||||
*/
|
||||
public animateToRows(targetRows: number): void {
|
||||
const { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows);
|
||||
|
||||
if (targetHeight === currentHeight) return; // No animation needed
|
||||
|
||||
console.log(`🎬 All-day height animation: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)} → ${targetRows} rows)`);
|
||||
|
||||
// Get cached elements
|
||||
const calendarHeader = this.getCalendarHeader();
|
||||
const headerSpacer = this.getHeaderSpacer();
|
||||
const allDayContainer = this.getAllDayContainer();
|
||||
|
||||
if (!calendarHeader || !allDayContainer) return;
|
||||
|
||||
// Get current parent height for animation
|
||||
const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height);
|
||||
const targetParentHeight = currentParentHeight + heightDifference;
|
||||
|
||||
const animations = [
|
||||
calendarHeader.animate([
|
||||
{ height: `${currentParentHeight}px` },
|
||||
{ height: `${targetParentHeight}px` }
|
||||
], {
|
||||
duration: 300,
|
||||
easing: 'ease-out',
|
||||
fill: 'forwards'
|
||||
})
|
||||
];
|
||||
|
||||
// Add spacer animation if spacer exists
|
||||
if (headerSpacer) {
|
||||
const root = document.documentElement;
|
||||
const currentSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + currentHeight;
|
||||
const targetSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + targetHeight;
|
||||
|
||||
animations.push(
|
||||
headerSpacer.animate([
|
||||
{ height: `${currentSpacerHeight}px` },
|
||||
{ height: `${targetSpacerHeight}px` }
|
||||
], {
|
||||
duration: 300,
|
||||
easing: 'ease-out',
|
||||
fill: 'forwards'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Update CSS variable after animation
|
||||
Promise.all(animations.map(anim => anim.finished)).then(() => {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--all-day-row-height', `${targetHeight}px`);
|
||||
eventBus.emit('header:height-changed');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update row height when all-day events change
|
||||
*/
|
||||
public updateRowHeight(): void {
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up cached elements and resources
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.clearCache();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue