Separates all-day event rendering; handles header lifecycle

Event rendering strategies now exclusively handle timed events, while all-day events are managed by a dedicated renderer.

Centralizes calendar header creation within `GridRenderer`, ensuring the header element is always present from initial DOM construction. `HeaderManager` and `ScrollManager` now react to a `header:ready` event, which signifies the header is fully initialized.

Synchronizes all-day event rendering with header readiness, temporarily queuing events until the header is prepared. Emits an `allday:checkHeight` event to prompt all-day container height adjustments after rendering.
This commit is contained in:
Janus C. H. Knudsen 2025-09-22 21:53:18 +02:00
parent 996459f226
commit f5e9909935
6 changed files with 128 additions and 18 deletions

View file

@ -542,11 +542,20 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events.filter(event => !event.allDay);
console.log('🎯 EventRenderer: Filtering events', {
totalEvents: events.length,
timedEvents: timedEvents.length,
filteredOutAllDay: events.length - timedEvents.length
});
// Find columns in the specific container for regular events
const columns = this.getColumns(container);
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, events);
const columnEvents = this.getEventsForColumn(column, timedEvents);
const eventsLayer = column.querySelector('swp-events-layer');
if (eventsLayer) {
@ -659,6 +668,14 @@ export class DateEventRenderer extends BaseEventRenderer {
this.setupDragEventListeners();
}
/**
* Setup drag event listeners - placeholder method
*/
private setupDragEventListeners(): void {
// Drag event listeners are handled by EventRendererManager
// This method exists for compatibility
}
protected getColumns(container: HTMLElement): HTMLElement[] {
const columns = container.querySelectorAll('swp-day-column');
return Array.from(columns) as HTMLElement[];

View file

@ -6,6 +6,7 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
import { EventManager } from '../managers/EventManager';
import { EventRendererStrategy } from './EventRenderer';
import { SwpEventElement } from '../elements/SwpEventElement';
import { AllDayEventRenderer } from './AllDayEventRenderer';
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload } from '../types/EventTypes';
/**
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
@ -15,6 +16,11 @@ export class EventRenderingService {
private eventBus: IEventBus;
private eventManager: EventManager;
private strategy: EventRendererStrategy;
private allDayEventRenderer: AllDayEventRenderer;
// Store all-day events until header is ready with dates
private pendingAllDayEvents: CalendarEvent[] = [];
private isHeaderReady: boolean = false;
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
@ -25,6 +31,9 @@ export class EventRenderingService {
// Cache strategy at initialization
const calendarType = calendarConfig.getCalendarMode();
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
// Initialize all-day event renderer
this.allDayEventRenderer = new AllDayEventRenderer();
this.setupEventListeners();
}
@ -43,13 +52,40 @@ export class EventRenderingService {
context.endDate
);
if (events.length === 0) {
return;
}
// Use cached strategy to render events in the specific container
this.strategy.renderEvents(events, context.container);
// Filter events by type
const timedEvents = events.filter(event => !event.allDay);
const allDayEvents = events.filter(event => event.allDay);
console.log('🎯 EventRenderingService: Event filtering', {
totalEvents: events.length,
timedEvents: timedEvents.length,
allDayEvents: allDayEvents.length,
allDayEventIds: allDayEvents.map(e => e.id)
});
// Render timed events using existing strategy
if (timedEvents.length > 0) {
this.strategy.renderEvents(timedEvents, context.container);
}
// Render all-day events - wait for header if not ready
if (allDayEvents.length > 0) {
if (this.isHeaderReady) {
this.renderAllDayEvents(allDayEvents);
// Check and adjust all-day container height after rendering
this.eventBus.emit('allday:checkHeight');
} else {
console.log('🕐 EventRendererManager: Header not ready, storing all-day events for later');
// Only store if we don't already have pending events to avoid duplicates
if (this.pendingAllDayEvents.length === 0) {
this.pendingAllDayEvents = [...allDayEvents];
}
}
}
// Emit EVENTS_RENDERED event for filtering system
this.eventBus.emit(CoreEvents.EVENTS_RENDERED, {
@ -68,6 +104,20 @@ export class EventRenderingService {
this.handleViewChanged(event as CustomEvent);
});
// Listen for header ready - when dates are populated
this.eventBus.on('header:ready', () => {
console.log('🎯 EventRendererManager: Header ready, rendering pending all-day events');
this.isHeaderReady = true;
if (this.pendingAllDayEvents.length > 0) {
this.renderAllDayEvents(this.pendingAllDayEvents);
this.pendingAllDayEvents = []; // Clear after rendering
// Check and adjust all-day container height after rendering
this.eventBus.emit('allday:checkHeight');
}
});
// Handle all drag events and delegate to appropriate renderer
this.setupDragEventListeners();
@ -299,8 +349,45 @@ export class EventRenderingService {
});
}
/**
* Render all-day events using AllDayEventRenderer
*/
private renderAllDayEvents(allDayEvents: CalendarEvent[]): void {
console.log('🏗️ EventRenderingService: Rendering all-day events', {
count: allDayEvents.length,
events: allDayEvents.map(e => ({ id: e.id, title: e.title }))
});
// Header always exists now, so we can render directly
allDayEvents.forEach(event => {
const renderedElement = this.allDayEventRenderer.renderAllDayEvent(event);
if (renderedElement) {
console.log('✅ EventRenderingService: Rendered all-day event', {
id: event.id,
title: event.title,
element: renderedElement.tagName
});
} else {
console.warn('❌ EventRenderingService: Failed to render all-day event', {
id: event.id,
title: event.title
});
}
});
}
private clearEvents(container?: HTMLElement): void {
this.strategy.clearEvents(container);
// Also clear all-day events
const allDayContainer = document.querySelector('swp-allday-container');
if (allDayContainer) {
allDayContainer.querySelectorAll('swp-event').forEach(event => event.remove());
}
// Clear pending all-day events
this.pendingAllDayEvents = [];
this.isHeaderReady = false;
}
public refresh(container?: HTMLElement): void {

View file

@ -108,6 +108,10 @@ export class GridRenderer {
): HTMLElement {
const gridContainer = document.createElement('swp-grid-container');
// Create calendar header as first child - always exists now!
const calendarHeader = document.createElement('swp-calendar-header');
gridContainer.appendChild(calendarHeader);
// Create scrollable content structure
const scrollableContent = document.createElement('swp-scrollable-content');
const timeGrid = document.createElement('swp-time-grid');
@ -124,6 +128,8 @@ export class GridRenderer {
scrollableContent.appendChild(timeGrid);
gridContainer.appendChild(scrollableContent);
console.log('✅ GridRenderer: Created grid container with header');
return gridContainer;
}