Improves all-day event layout and drag behavior

Refactors all-day event layout calculation and rendering for improved accuracy and performance.

Improves drag-and-drop behavior for all-day events, ensuring correct event placement and column detection.

Addresses issues with event overflow display and provides a more responsive user experience.
This commit is contained in:
Janus C. H. Knudsen 2025-10-01 18:41:28 +02:00
parent 6a17bba343
commit ae3aab5dd0
5 changed files with 50 additions and 66 deletions

View file

@ -53,8 +53,7 @@ export class AllDayManager {
originalElementTag: payload.originalElement?.tagName originalElementTag: payload.originalElement?.tagName
}); });
this.handleConvertToAllDay(payload); this.handleConvertToAllDay(payload);
this.checkAndAnimateAllDayHeight();
}); });
eventBus.on('drag:mouseleave-header', (event) => { eventBus.on('drag:mouseleave-header', (event) => {
@ -64,7 +63,7 @@ export class AllDayManager {
originalElementId: originalElement?.dataset?.eventId originalElementId: originalElement?.dataset?.eventId
}); });
this.checkAndAnimateAllDayHeight(); //this.checkAndAnimateAllDayHeight();
}); });
// Listen for drag operations on all-day events // Listen for drag operations on all-day events
@ -117,15 +116,8 @@ export class AllDayManager {
reason reason
}); });
// Recalculate all-day height since clones may have been removed
this.checkAndAnimateAllDayHeight();
}); });
// Listen for height check requests from EventRendererManager
eventBus.on('allday:checkHeight', () => {
console.log('📏 AllDayManager: Received allday:checkHeight request');
this.checkAndAnimateAllDayHeight();
});
} }
private getAllDayContainer(): HTMLElement | null { private getAllDayContainer(): HTMLElement | null {
@ -212,7 +204,7 @@ export class AllDayManager {
} }
} else { } else {
// Hide chevron - not needed // Hide chevron - not needed
this.updateChevronButton(false); this.updateChevronButton(false);
this.clearOverflowIndicators(); this.clearOverflowIndicators();
@ -313,7 +305,19 @@ export class AllDayManager {
return layoutEngine.calculateLayout(events); return layoutEngine.calculateLayout(events);
} }
public initAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
// Store current state
this.currentAllDayEvents = events;
this.currentWeekDates = weekDates;
// Initialize layout engine with provided week dates
var layoutEngine = new AllDayLayoutEngine(weekDates);
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
this.currentLayouts = layoutEngine.calculateLayout(events);
return this.currentLayouts;
}
/** /**
* Handle conversion of timed event to all-day event - SIMPLIFIED * Handle conversion of timed event to all-day event - SIMPLIFIED
@ -412,12 +416,8 @@ export class AllDayManager {
if (dragEndEvent.draggedClone == null) if (dragEndEvent.draggedClone == null)
return; return;
// 2. Normalize clone ID // 2. Normalize clone ID
const cloneId = dragEndEvent.draggedClone?.dataset.eventId; dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
if (cloneId?.startsWith('clone-')) {
dragEndEvent.draggedClone.dataset.eventId = cloneId.replace('clone-', '');
}
// 3. Create temporary array with existing events + the dropped event // 3. Create temporary array with existing events + the dropped event
let eventId = dragEndEvent.draggedClone.dataset.eventId; let eventId = dragEndEvent.draggedClone.dataset.eventId;
@ -430,7 +430,7 @@ export class AllDayManager {
const droppedEvent: CalendarEvent = { const droppedEvent: CalendarEvent = {
id: eventId, id: eventId,
title: dragEndEvent.draggedClone.dataset.title || dragEndEvent.draggedClone.textContent || '', title: dragEndEvent.draggedClone.dataset.title|| '',
start: new Date(eventDate), start: new Date(eventDate),
end: new Date(eventDate), end: new Date(eventDate),
type: eventType, type: eventType,
@ -449,16 +449,14 @@ export class AllDayManager {
this.newLayouts.forEach((layout) => { this.newLayouts.forEach((layout) => {
// Find current layout for this event // Find current layout for this event
var currentLayout = this.currentLayouts.find(old => old.calenderEvent.id === layout.calenderEvent.id); var currentLayout = this.currentLayouts.find(old => old.calenderEvent.id === layout.calenderEvent.id);
var currentGridArea = currentLayout?.gridArea;
var newGridArea = layout.gridArea;
if (currentGridArea !== newGridArea) { if (currentLayout?.gridArea !== layout.gridArea) {
changedCount++; changedCount++;
const element = dragEndEvent.draggedClone; const element = dragEndEvent.draggedClone;
if (element) { if (element) {
// Add transition class for smooth animation // Add transition class for smooth animation
element.classList.add('transitioning'); element.classList.add('transitioning');
element.style.gridArea = newGridArea; element.style.gridArea = layout.gridArea;
element.style.gridRow = layout.row.toString(); element.style.gridRow = layout.row.toString();
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`; element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
@ -529,7 +527,7 @@ export class AllDayManager {
private countEventsInColumn(columnBounds: ColumnBounds): number { private countEventsInColumn(columnBounds: ColumnBounds): number {
var columnIndex = columnBounds.index; var columnIndex = columnBounds.index;
var count = 0; var count = 0;
this.currentLayouts.forEach((layout) => { this.currentLayouts.forEach((layout) => {
// Check if event spans this column // Check if event spans this column
if (layout.startColumn <= columnIndex && layout.endColumn >= columnIndex) { if (layout.startColumn <= columnIndex && layout.endColumn >= columnIndex) {
@ -546,23 +544,26 @@ export class AllDayManager {
const container = this.getAllDayContainer(); const container = this.getAllDayContainer();
if (!container) return; if (!container) return;
container.querySelectorAll('swp-event').forEach((element) => { // Create overflow indicators for each column that needs them
const event = element as HTMLElement; var columns = ColumnDetectionUtils.getColumns();
const gridRow = parseInt(event.style.gridRow) || 1;
if (gridRow === 4) { columns.forEach((columnBounds) => {
var totalEventsInColumn = this.countEventsInColumn(columnBounds);
// Convert row 4 events to indicators var overflowCount = Math.max(0, totalEventsInColumn - 3);
const overflowCount = this.actualRowCount - 3; // Total overflow rows
event.classList.add('max-event-overflow'); if (overflowCount > 0) {
event.innerHTML = `<span>+${overflowCount} more</span>`; // Create new overflow indicator element
event.onclick = (e) => { var overflowElement = document.createElement('swp-event');
overflowElement.className = 'max-event-overflow';
overflowElement.style.gridRow = '4';
overflowElement.style.gridColumn = columnBounds.index.toString();
overflowElement.innerHTML = `<span>+${overflowCount} more</span>`;
overflowElement.onclick = (e) => {
e.stopPropagation(); e.stopPropagation();
this.toggleExpanded(); this.toggleExpanded();
}; };
} else if (gridRow > 4) {
// Hide events beyond row 4 container.appendChild(overflowElement);
event.style.display = 'none';
} }
}); });
} }
@ -574,21 +575,12 @@ export class AllDayManager {
const container = this.getAllDayContainer(); const container = this.getAllDayContainer();
if (!container) return; if (!container) return;
container.querySelectorAll('.max-event-overflow').forEach((event) => { // Remove all overflow indicator elements
const htmlEvent = event as HTMLElement; container.querySelectorAll('.max-event-overflow').forEach((element) => {
htmlEvent.classList.remove('max-event-overflow'); element.remove();
htmlEvent.onclick = null;
// Restore original title from data-title
if (htmlEvent.dataset.title) {
htmlEvent.innerHTML = htmlEvent.dataset.title;
}
}); });
// Show all hidden events
container.querySelectorAll('swp-event[style*="display: none"]').forEach((event) => {
(event as HTMLElement).style.display = '';
});
} }
} }

View file

@ -243,8 +243,10 @@ export class DragDropManager {
// Check for column change using cached data // Check for column change using cached data
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition); const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
if (newColumn == null)
return;
if (newColumn && newColumn !== this.currentColumnBounds) { if (newColumn?.index !== this.currentColumnBounds?.index) {
const previousColumn = this.currentColumnBounds; const previousColumn = this.currentColumnBounds;
this.currentColumnBounds = newColumn; this.currentColumnBounds = newColumn;
@ -279,7 +281,7 @@ export class DragDropManager {
// Detect drop target (swp-day-column or swp-day-header) // Detect drop target (swp-day-column or swp-day-header)
const dropTarget = this.detectDropTarget(mousePosition); const dropTarget = this.detectDropTarget(mousePosition);
if(!dropTarget) if (!dropTarget)
throw "dropTarget is null"; throw "dropTarget is null";
console.log('🎯 DragDropManager: Emitting drag:end', { console.log('🎯 DragDropManager: Emitting drag:end', {
@ -298,7 +300,9 @@ export class DragDropManager {
target: dropTarget target: dropTarget
}; };
this.eventBus.emit('drag:end', dragEndPayload); this.eventBus.emit('drag:end', dragEndPayload);
this.draggedElement = null;
} else { } else {
// This was just a click - emit click event instead // This was just a click - emit click event instead

View file

@ -371,15 +371,13 @@ export class EventRenderingService {
// Pass current events to AllDayManager for state tracking // Pass current events to AllDayManager for state tracking
this.allDayManager.setCurrentEvents(allDayEvents, weekDates); this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates); const layouts = this.allDayManager.initAllDayEventsLayout(allDayEvents, weekDates);
// Render each all-day event with pre-calculated layout // Render each all-day event with pre-calculated layout
layouts.forEach(layout => { layouts.forEach(layout => {
this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout); this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout);
}); });
// Check and adjust all-day container height after rendering
this.eventBus.emit('allday:checkHeight');
} }
/** /**

View file

@ -135,7 +135,6 @@ export interface CalendarEventPayloadMap {
}; };
// All-day events // All-day events
'allday:checkHeight': undefined;
'allday:convert-to-allday': { 'allday:convert-to-allday': {
eventId: string; eventId: string;
element: HTMLElement; element: HTMLElement;

View file

@ -83,17 +83,8 @@ export class ColumnDetectionUtils {
return column || null; return column || null;
} }
/**
* Clear cache (useful for testing or when DOM structure changes) public static getColumns(): ColumnBounds[] {
*/
public static clearCache(): void {
this.columnBoundsCache = [];
}
/**
* Get current cache for debugging
*/
public static getCache(): ColumnBounds[] {
return [...this.columnBoundsCache]; return [...this.columnBoundsCache];
} }
} }