Refactors all-day event layout tracking logic

Removes redundant state tracking for all-day event layouts

Shifts from maintaining internal layout state to reading directly from DOM elements
Simplifies event handling and updates by using DOM as the source of truth
Improves performance by reducing unnecessary state management
This commit is contained in:
Janus C. H. Knudsen 2025-11-11 18:08:48 +01:00
parent 4cc110d9f2
commit 2656bae054
3 changed files with 83 additions and 67 deletions

View file

@ -33,11 +33,9 @@ export class AllDayManager {
private layoutEngine: AllDayLayoutEngine | null = null; private layoutEngine: AllDayLayoutEngine | null = null;
// State tracking for differential updates // State tracking for layout calculation
private currentLayouts: IEventLayout[] = [];
private currentAllDayEvents: ICalendarEvent[] = []; private currentAllDayEvents: ICalendarEvent[] = [];
private currentWeekDates: IColumnBounds[] = []; private currentWeekDates: IColumnBounds[] = [];
private newLayouts: IEventLayout[] = [];
// Expand/collapse state // Expand/collapse state
private isExpanded: boolean = false; private isExpanded: boolean = false;
@ -128,23 +126,12 @@ export class AllDayManager {
if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) { if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) {
const eventId = dragEndPayload.originalElement.dataset.eventId; const eventId = dragEndPayload.originalElement.dataset.eventId;
console.log('🔄 AllDayManager: All-day → timed conversion', { console.log('🔄 AllDayManager: All-day → timed conversion', { eventId });
eventId,
currentLayoutsCount: this.currentLayouts.length,
layoutsBeforeFilter: this.currentLayouts.map(l => l.calenderEvent.id)
});
// Remove event from currentLayouts since it's now a timed event // No need to filter currentLayouts - DOM is source of truth!
this.currentLayouts = this.currentLayouts.filter( // The element is already removed from all-day container by DragDropManager
layout => layout.calenderEvent.id !== eventId
);
console.log('📊 AllDayManager: After filter', { // Recalculate and animate header height based on remaining DOM elements
currentLayoutsCount: this.currentLayouts.length,
layoutsAfterFilter: this.currentLayouts.map(l => l.calenderEvent.id)
});
// Recalculate and animate header height
this.checkAndAnimateAllDayHeight(); this.checkAndAnimateAllDayHeight();
} }
}); });
@ -171,9 +158,9 @@ export class AllDayManager {
// Filter for all-day events // Filter for all-day events
const allDayEvents = events.filter(event => event.allDay); const allDayEvents = events.filter(event => event.allDay);
this.currentLayouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements) const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements);
this.allDayEventRenderer.renderAllDayEventsForPeriod(this.currentLayouts); this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts);
this.checkAndAnimateAllDayHeight(); this.checkAndAnimateAllDayHeight();
}); });
@ -194,6 +181,65 @@ export class AllDayManager {
return document.querySelector('swp-header-spacer'); return document.querySelector('swp-header-spacer');
} }
/**
* Read current max row from DOM elements
*/
private getMaxRowFromDOM(): number {
const container = this.getAllDayContainer();
if (!container) return 0;
let maxRow = 0;
const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)');
allDayEvents.forEach((element: Element) => {
const htmlElement = element as HTMLElement;
const row = parseInt(htmlElement.style.gridRow) || 1;
maxRow = Math.max(maxRow, row);
});
return maxRow;
}
/**
* Get current gridArea for an event from DOM
*/
private getGridAreaFromDOM(eventId: string): string | null {
const container = this.getAllDayContainer();
if (!container) return null;
const element = container.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement;
return element?.style.gridArea || null;
}
/**
* Count events in a specific column by reading DOM
*/
private countEventsInColumnFromDOM(columnIndex: number): number {
const container = this.getAllDayContainer();
if (!container) return 0;
let count = 0;
const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)');
allDayEvents.forEach((element: Element) => {
const htmlElement = element as HTMLElement;
const gridColumn = htmlElement.style.gridColumn;
// Parse "1 / 3" format
const match = gridColumn.match(/(\d+)\s*\/\s*(\d+)/);
if (match) {
const startCol = parseInt(match[1]);
const endCol = parseInt(match[2]) - 1; // End is exclusive in CSS
if (startCol <= columnIndex && endCol >= columnIndex) {
count++;
}
}
});
return count;
}
/** /**
* Calculate all-day height based on number of rows * Calculate all-day height based on number of rows
*/ */
@ -221,36 +267,14 @@ export class AllDayManager {
/** /**
* Check current all-day events and animate to correct height * Check current all-day events and animate to correct height
* Reads max row directly from DOM elements
*/ */
public checkAndAnimateAllDayHeight(): void { public checkAndAnimateAllDayHeight(): void {
console.log('📏 AllDayManager: checkAndAnimateAllDayHeight called', { // Read max row directly from DOM
currentLayoutsCount: this.currentLayouts.length, const maxRows = this.getMaxRowFromDOM();
layouts: this.currentLayouts.map(l => ({
id: l.calenderEvent.id,
row: l.row,
title: l.calenderEvent.title
}))
});
// Calculate required rows - 0 if no events (will collapse)
let maxRows = 0;
if (this.currentLayouts.length > 0) {
// Find the HIGHEST row number in use from currentLayouts
let highestRow = 0;
this.currentLayouts.forEach((layout) => {
highestRow = Math.max(highestRow, layout.row);
});
// Max rows = highest row number (e.g. if row 3 is used, height = 3 rows)
maxRows = highestRow;
}
console.log('📊 AllDayManager: Height calculation', { console.log('📊 AllDayManager: Height calculation', {
maxRows, maxRows,
currentLayoutsLength: this.currentLayouts.length,
isExpanded: this.isExpanded isExpanded: this.isExpanded
}); });
@ -508,17 +532,15 @@ export class AllDayManager {
]; ];
// 4. Calculate new layouts for ALL events // 4. Calculate new layouts for ALL events
this.newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates); const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
// 5. Apply differential updates - only update events that changed // 5. Apply differential updates - compare with DOM instead of currentLayouts
let changedCount = 0;
let container = this.getAllDayContainer(); let container = this.getAllDayContainer();
this.newLayouts.forEach((layout) => { newLayouts.forEach((layout) => {
// Find current layout for this event // Get current gridArea from DOM
let currentLayout = this.currentLayouts.find(old => old.calenderEvent.id === layout.calenderEvent.id); const currentGridArea = this.getGridAreaFromDOM(layout.calenderEvent.id);
if (currentLayout?.gridArea !== layout.gridArea) { if (currentGridArea !== layout.gridArea) {
changedCount++;
let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement; let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
if (element) { if (element) {
@ -542,9 +564,6 @@ export class AllDayManager {
} }
}); });
if (changedCount > 0)
this.currentLayouts = this.newLayouts;
// 6. Clean up drag styles from the dropped clone // 6. Clean up drag styles from the dropped clone
dragEndEvent.draggedClone.classList.remove('dragging'); dragEndEvent.draggedClone.classList.remove('dragging');
dragEndEvent.draggedClone.style.zIndex = ''; dragEndEvent.draggedClone.style.zIndex = '';
@ -625,18 +644,10 @@ export class AllDayManager {
} }
/** /**
* Count number of events in a specific column using IColumnBounds * Count number of events in a specific column using IColumnBounds
* Reads directly from DOM elements
*/ */
private countEventsInColumn(columnBounds: IColumnBounds): number { private countEventsInColumn(columnBounds: IColumnBounds): number {
let columnIndex = columnBounds.index; return this.countEventsInColumnFromDOM(columnBounds.index);
let count = 0;
this.currentLayouts.forEach((layout) => {
// Check if event spans this column
if (layout.startColumn <= columnIndex && layout.endColumn >= columnIndex) {
count++;
}
});
return count;
} }
/** /**

File diff suppressed because one or more lines are too long

View file

@ -197,6 +197,11 @@ swp-calendar-header {
gap: 2px 0px; gap: 2px 0px;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
/* Border only when events exist */
&:has(swp-allday-event) {
border-bottom: 1px solid var(--color-grid-line);
}
} }
} }