Improves all-day event rendering and handling

Refactors all-day event container handling to improve rendering and event delegation.

Ensures all-day containers are consistently created and managed,
preventing issues with event display and drag-and-drop functionality.

Moves event listener setup into dedicated methods for better
organization and reusability.
This commit is contained in:
Janus Knudsen 2025-08-25 22:05:57 +02:00
parent f2763ad826
commit 07402a07d9
6 changed files with 142 additions and 44 deletions

View file

@ -80,9 +80,11 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
return;
}
// Clear any existing all-day containers first
// Clear content of existing all-day containers (but keep the containers)
const existingContainers = calendarHeader.querySelectorAll('swp-allday-container');
existingContainers.forEach(container => container.remove());
existingContainers.forEach(container => {
container.innerHTML = ''; // Clear content, keep container
});
// Track maximum number of stacked events to calculate row height
let maxStackHeight = 0;
@ -142,21 +144,32 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
}
const columnSpan = endColumn - startColumn + 1;
let allDayContainer: Element | null = null;
// Create or find container for this column span
if (columnSpan === 1) {
// Single day event - use existing single-day container
const containerKey = `${startColumn}-1`;
allDayContainer = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
} else {
// Multi-day event - create or find spanning container
const containerKey = `${startColumn}-${columnSpan}`;
let allDayContainer = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
allDayContainer = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
if (!allDayContainer) {
// Create container that spans the appropriate columns
allDayContainer = document.createElement('swp-allday-container');
allDayContainer.setAttribute('data-container-key', containerKey);
(allDayContainer as HTMLElement).style.gridColumn = columnSpan > 1
? `${startColumn} / span ${columnSpan}`
: `${startColumn}`;
allDayContainer.setAttribute('data-date', this.dateCalculator.formatISODate(startDate));
(allDayContainer as HTMLElement).style.gridColumn = `${startColumn} / span ${columnSpan}`;
(allDayContainer as HTMLElement).style.gridRow = '2';
calendarHeader.appendChild(allDayContainer);
}
}
if (!allDayContainer) {
console.warn(`BaseEventRenderer: No container found for event "${event.title}"`);
return;
}
// Create the all-day event element inside container
const allDayEvent = document.createElement('swp-allday-event');

View file

@ -51,7 +51,6 @@ export class EventRenderingService {
// Use cached strategy to render events in the specific container
this.strategy.renderEvents(events, context.container, calendarConfig);
console.log(` ✅ Rendered ${events.length} events successfully`);
// Emit EVENTS_RENDERED event for filtering system

View file

@ -10,6 +10,7 @@ import { eventBus } from '../core/EventBus';
*/
export class GridRenderer {
private config: CalendarConfig;
private headerEventListener: ((event: Event) => void) | null = null;
constructor(config: CalendarConfig) {
this.config = config;
@ -135,31 +136,11 @@ export class GridRenderer {
headerRenderer.render(calendarHeader, context);
// Use event delegation for mouseover detection on entire header
calendarHeader.addEventListener('mouseover', (event) => {
const target = event.target as HTMLElement;
// Always ensure all-day containers exist for all days
headerRenderer.ensureAllDayContainers(calendarHeader);
// Check what was hovered - could be day-header OR all-day-container
const dayHeader = target.closest('swp-day-header');
const allDayContainer = target.closest('swp-allday-container');
if (dayHeader || allDayContainer) {
const hoveredElement = dayHeader || allDayContainer;
const targetDate = (hoveredElement as HTMLElement).dataset.date;
console.log('GridRenderer: Detected hover over:', {
elementType: dayHeader ? 'day-header' : 'all-day-container',
targetDate,
element: hoveredElement
});
eventBus.emit('header:mouseover', {
element: hoveredElement,
targetDate,
headerRenderer
});
}
});
// Setup event listener for mouseover detection
this.setupHeaderEventListener(calendarHeader);
}
/**
@ -200,4 +181,47 @@ export class GridRenderer {
// Re-render headers using Strategy Pattern - this will also re-attach the event listener
this.renderCalendarHeader(calendarHeader as HTMLElement, currentWeek, resourceData);
}
/**
* Setup or re-setup event delegation listener on calendar header
*/
private setupHeaderEventListener(calendarHeader: HTMLElement): void {
// Remove existing listener if any (stored reference approach)
if (this.headerEventListener) {
calendarHeader.removeEventListener('mouseover', this.headerEventListener);
}
// Create new listener function
this.headerEventListener = (event) => {
const target = event.target as HTMLElement;
// Check what was hovered - could be day-header OR all-day-container
const dayHeader = target.closest('swp-day-header');
const allDayContainer = target.closest('swp-allday-container');
if (dayHeader || allDayContainer) {
const hoveredElement = dayHeader || allDayContainer;
const targetDate = (hoveredElement as HTMLElement).dataset.date;
console.log('GridRenderer: Detected hover over:', {
elementType: dayHeader ? 'day-header' : 'all-day-container',
targetDate,
element: hoveredElement
});
// Get the header renderer for addToAllDay functionality
const calendarType = this.config.getCalendarMode();
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
eventBus.emit('header:mouseover', {
element: hoveredElement,
targetDate,
headerRenderer
});
}
};
// Add the new listener
calendarHeader.addEventListener('mouseover', this.headerEventListener);
}
}

View file

@ -11,6 +11,7 @@ import { DateCalculator } from '../utils/DateCalculator';
export interface HeaderRenderer {
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
addToAllDay(dayHeader: HTMLElement): void;
ensureAllDayContainers(calendarHeader: HTMLElement): void;
}
/**
@ -36,6 +37,24 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
}
}
/**
* Ensure all-day containers exist for all days (called automatically after header render)
*/
ensureAllDayContainers(calendarHeader: HTMLElement): void {
const root = document.documentElement;
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
// If all-day row doesn't exist yet, set it up without animation
if (currentHeight === 0) {
root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`);
}
// Always ensure containers exist for all days
this.createEmptyAllDayContainers(calendarHeader);
console.log('BaseHeaderRenderer: Ensured all-day containers exist for all days');
}
private animateHeaderExpansion(calendarHeader: HTMLElement): void {
const root = document.documentElement;
const currentHeaderHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height'));
@ -126,7 +145,7 @@ export class DateHeaderRenderer extends BaseHeaderRenderer {
private dateCalculator!: DateCalculator;
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void {
const { currentWeek, config, allDayEvents = [] } = context;
const { currentWeek, config } = context;
// Initialize date calculator with config
this.dateCalculator = new DateCalculator(config);

View file

@ -3,6 +3,8 @@ import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
import { DateCalculator } from '../utils/DateCalculator';
import { EventRenderingService } from './EventRendererManager';
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
import { eventBus } from '../core/EventBus';
/**
* NavigationRenderer - Handles DOM rendering for navigation containers
@ -171,6 +173,13 @@ export class NavigationRenderer {
header.appendChild(headerElement);
});
// Always ensure all-day containers exist for all days
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(this.config.getCalendarMode());
headerRenderer.ensureAllDayContainers(header as HTMLElement);
// Add event delegation listener for drag & drop functionality
this.setupHeaderEventListener(header as HTMLElement);
// Render day columns for target week
dates.forEach(date => {
const column = document.createElement('swp-day-column');
@ -183,4 +192,38 @@ export class NavigationRenderer {
});
}
/**
* Setup event delegation listener for header mouseover (same logic as GridRenderer)
*/
private setupHeaderEventListener(calendarHeader: HTMLElement): void {
calendarHeader.addEventListener('mouseover', (event) => {
const target = event.target as HTMLElement;
// Check what was hovered - could be day-header OR all-day-container
const dayHeader = target.closest('swp-day-header');
const allDayContainer = target.closest('swp-allday-container');
if (dayHeader || allDayContainer) {
const hoveredElement = dayHeader || allDayContainer;
const targetDate = (hoveredElement as HTMLElement).dataset.date;
console.log('NavigationRenderer: Detected hover over:', {
elementType: dayHeader ? 'day-header' : 'all-day-container',
targetDate,
element: hoveredElement
});
// Get the header renderer for addToAllDay functionality
const calendarType = this.config.getCalendarMode();
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
eventBus.emit('header:mouseover', {
element: hoveredElement,
targetDate,
headerRenderer
});
}
});
}
}

View file

@ -50,8 +50,8 @@ export class MonthViewStrategy implements ViewStrategy {
// Add 6 weeks of day cells
this.createDayCells(monthGrid, context.currentDate);
// Render events in day cells
this.renderMonthEvents(monthGrid, context.allDayEvents);
// Render events in day cells (will be handled by EventRendererManager)
// this.renderMonthEvents(monthGrid, context.allDayEvents);
context.container.appendChild(monthGrid);
}