Refactors drag and drop column detection

Improves drag and drop functionality by refactoring column detection to use column bounds instead of dates.
This change enhances the accuracy and efficiency of determining the target column during drag operations.
It also removes redundant code and simplifies the logic in both the DragDropManager and AllDayManager.
This commit is contained in:
Janus C. H. Knudsen 2025-09-28 13:25:09 +02:00
parent 4141bffca4
commit 6ccc071587
8 changed files with 262 additions and 377 deletions

View file

@ -4,7 +4,7 @@ import { eventBus } from '../core/EventBus';
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { CalendarEvent } from '../types/CalendarTypes';
import {
DragMouseEnterHeaderEventPayload,
@ -22,7 +22,7 @@ import { DragOffset, MousePosition } from '../types/DragDropTypes';
export class AllDayManager {
private allDayEventRenderer: AllDayEventRenderer;
private layoutEngine: AllDayLayoutEngine | null = null;
// State tracking for differential updates
private currentLayouts: Map<string, string> = new Map();
private currentAllDayEvents: CalendarEvent[] = [];
@ -38,16 +38,16 @@ export class AllDayManager {
*/
private setupEventListeners(): void {
eventBus.on('drag:mouseenter-header', (event) => {
const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
const { targetColumn: targetColumnBounds, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
console.log('🔄 AllDayManager: Received drag:mouseenter-header', {
targetDate,
targetDate: targetColumnBounds,
originalElementId: originalElement?.dataset?.eventId,
originalElementTag: originalElement?.tagName
});
if (targetDate && cloneElement) {
this.handleConvertToAllDay(targetDate, cloneElement);
if (targetColumnBounds && cloneElement) {
this.handleConvertToAllDay(targetColumnBounds, cloneElement);
}
this.checkAndAnimateAllDayHeight();
@ -78,8 +78,8 @@ export class AllDayManager {
eventBus.on('drag:column-change', (event) => {
const { draggedElement, draggedClone, mousePosition } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
if(draggedClone == null)
if (draggedClone == null)
return;
// Filter: Only handle events where clone IS an all-day event
@ -88,27 +88,20 @@ export class AllDayManager {
}
console.log('🔄 AllDayManager: Handling drag:column-change for all-day event', {
eventId : draggedElement.dataset.eventId,
eventId: draggedElement.dataset.eventId,
cloneId: draggedClone.dataset.eventId
});
this.handleColumnChange(draggedClone, mousePosition);
});
eventBus.on('drag:end', (event) => {
const { draggedElement, mousePosition, finalPosition, target, draggedClone } = (event as CustomEvent<DragEndEventPayload>).detail;
let draggedElement: DragEndEventPayload = (event as CustomEvent<DragEndEventPayload>).detail;
if (target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
if (draggedElement.target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
return;
const eventId = draggedElement.dataset.eventId;
console.log('🎬 AllDayManager: Received drag:end', {
eventId: eventId,
finalPosition
});
console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId });
this.handleDragEnd(draggedElement, draggedClone as HTMLElement, { column: finalPosition.column || '', y: 0 });
this.handleDragEnd(draggedElement);
});
// Listen for drag cancellation to recalculate height
@ -273,7 +266,7 @@ export class AllDayManager {
this.currentLayouts.clear();
const container = this.getAllDayContainer();
if (!container) return;
container.querySelectorAll('swp-event').forEach(element => {
const htmlElement = element as HTMLElement;
const eventId = htmlElement.dataset.eventId;
@ -282,7 +275,7 @@ export class AllDayManager {
this.currentLayouts.set(eventId, gridArea);
}
});
console.log('📋 AllDayManager: Stored current layouts', {
count: this.currentLayouts.size,
layouts: Array.from(this.currentLayouts.entries())
@ -295,7 +288,7 @@ export class AllDayManager {
public setCurrentEvents(events: CalendarEvent[], weekDates: string[]): void {
this.currentAllDayEvents = events;
this.currentWeekDates = weekDates;
console.log('📝 AllDayManager: Set current events', {
eventCount: events.length,
weekDatesCount: weekDates.length
@ -307,17 +300,17 @@ export class AllDayManager {
* This is the correct method that processes all events together for proper overlap detection
*/
public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
// Store current state
this.currentAllDayEvents = events;
this.currentWeekDates = weekDates;
// Initialize layout engine with provided week dates
this.layoutEngine = new AllDayLayoutEngine(weekDates);
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
return this.layoutEngine.calculateLayout(events);
}
@ -325,22 +318,20 @@ export class AllDayManager {
* Handle conversion of timed event to all-day event - SIMPLIFIED
* During drag: Place in row 1 only, calculate column from targetDate
*/
private handleConvertToAllDay(targetDate: string, cloneElement: HTMLElement): void {
private handleConvertToAllDay(targetColumnBounds: ColumnBounds, cloneElement: HTMLElement): void {
console.log('🔄 AllDayManager: Converting to all-day (row 1 only during drag)', {
eventId: cloneElement.dataset.eventId,
targetDate
targetDate: targetColumnBounds
});
// Get all-day container, request creation if needed
let allDayContainer = this.getAllDayContainer();
// Calculate target column from targetDate using ColumnDetectionUtils
const targetColumn = ColumnDetectionUtils.getColumnIndexFromDate(targetDate);
cloneElement.removeAttribute('style');
cloneElement.classList.add('all-day-style');
cloneElement.style.gridRow = '1';
cloneElement.style.gridColumn = targetColumn.toString();
cloneElement.style.gridColumn = targetColumnBounds.index.toString();
cloneElement.dataset.allday = 'true'; // Set the all-day attribute for filtering
// Add to container
@ -348,7 +339,7 @@ export class AllDayManager {
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
eventId: cloneElement.dataset.eventId,
gridColumn: targetColumn,
gridColumn: targetColumnBounds,
gridRow: 1
});
}
@ -398,13 +389,16 @@ export class AllDayManager {
if (!allDayContainer) return;
// Calculate target column using ColumnDetectionUtils
const targetColumn = ColumnDetectionUtils.getColumnIndexFromX(mousePosition.x);
const targetColumn = ColumnDetectionUtils.getColumnBounds(mousePosition);
if (targetColumn == null)
return;
// Update clone position - ALWAYS keep in row 1 during drag
// Use simple grid positioning that matches all-day container structure
dragClone.style.gridColumn = targetColumn.toString();
dragClone.style.gridColumn = targetColumn.index.toString();
dragClone.style.gridRow = '1'; // Force row 1 during drag
dragClone.style.gridArea = `1 / ${targetColumn} / 2 / ${targetColumn + 1}`;
dragClone.style.gridArea = `1 / ${targetColumn.index} / 2 / ${targetColumn.index + 1}`;
console.log('🔄 AllDayManager: Updated all-day drag clone position', {
eventId: dragClone.dataset.eventId,
@ -418,31 +412,38 @@ export class AllDayManager {
/**
* Handle drag end for all-day events - WITH DIFFERENTIAL UPDATES
*/
private handleDragEnd(originalElement: HTMLElement, dragClone: HTMLElement, finalPosition: { column: string; y: number }): void {
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
console.log('🎯 AllDayManager: Starting drag end with differential updates', {
eventId: dragClone.dataset.eventId,
finalColumn: finalPosition.column
dragEndEvent
});
if (dragEndEvent.draggedClone == null)
return;
// 1. Store current layouts BEFORE any changes
this.storeCurrentLayouts();
// 2. Normalize clone ID
const cloneId = dragClone.dataset.eventId;
const cloneId = dragEndEvent.draggedClone?.dataset.eventId;
if (cloneId?.startsWith('clone-')) {
dragClone.dataset.eventId = cloneId.replace('clone-', '');
dragEndEvent.draggedClone.dataset.eventId = cloneId.replace('clone-', '');
}
// 3. Create temporary array with existing events + the dropped event
const droppedEventId = dragClone.dataset.eventId || '';
const droppedEventDate = dragClone.dataset.allDayDate || finalPosition.column;
let eventId = dragEndEvent.draggedClone.dataset.eventId;
let eventDate = dragEndEvent.finalPosition.column?.date;
let eventType = dragEndEvent.draggedClone.dataset.type;
if (eventDate == null || eventId == null || eventType == null)
return;
const droppedEvent: CalendarEvent = {
id: droppedEventId,
title: dragClone.dataset.title || dragClone.textContent || '',
start: new Date(droppedEventDate),
end: new Date(droppedEventDate),
type: 'work',
id: eventId,
title: dragEndEvent.draggedClone.dataset.title || dragEndEvent.draggedClone.textContent || '',
start: new Date(eventDate),
end: new Date(eventDate),
type: eventType,
allDay: true,
syncStatus: 'synced'
};
@ -477,13 +478,13 @@ export class AllDayManager {
});
// 6. Clean up drag styles from the dropped clone
dragClone.classList.remove('dragging');
dragClone.style.zIndex = '';
dragClone.style.cursor = '';
dragClone.style.opacity = '';
dragEndEvent.draggedClone.classList.remove('dragging');
dragEndEvent.draggedClone.style.zIndex = '';
dragEndEvent.draggedClone.style.cursor = '';
dragEndEvent.draggedClone.style.opacity = '';
// 7. Restore original element opacity
originalElement.style.opacity = '';
//originalElement.style.opacity = '';
// 8. Check if height adjustment is needed
this.checkAndAnimateAllDayHeight();