From 4fea01c76b5c77ac3676d5c51b88ffe61d7bbd35 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 3 Oct 2025 19:09:44 +0200 Subject: [PATCH] Improves all-day event drag and drop Addresses issues with all-day event duration calculation and positioning during drag and drop. - Uses `differenceInCalendarDays` to correctly calculate event duration across timezone and DST boundaries. - Preserves the original event time when moving events to a different day. - Snaps dragged event to the top of the target time slot. --- src/managers/AllDayManager.ts | 20 ++++++++++++++++++-- src/managers/DragDropManager.ts | 6 +++++- src/renderers/EventRenderer.ts | 5 +++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts index 61be364..de7032d 100644 --- a/src/managers/AllDayManager.ts +++ b/src/managers/AllDayManager.ts @@ -17,6 +17,7 @@ import { import { DragOffset, MousePosition } from '../types/DragDropTypes'; import { CoreEvents } from '../constants/CoreEvents'; import { EventManager } from './EventManager'; +import { differenceInCalendarDays } from 'date-fns'; /** * AllDayManager - Handles all-day row height animations and management @@ -379,7 +380,9 @@ export class AllDayManager { throw new Error('Ugyldig start eller slut-dato i dataset'); } - return Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); + // Use differenceInCalendarDays for proper calendar day calculation + // This correctly handles timezone differences and DST changes + return differenceInCalendarDays(endDate, startDate); }; if (dragEndEvent.draggedClone == null) @@ -403,10 +406,23 @@ export class AllDayManager { const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end); + + // Get original dates to preserve time + const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!); + const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!); + + // Create new start date with the new day but preserve original time const newStartDate = new Date(eventDate); - const newEndDate = new Date(newStartDate); + newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds()); + + // Create new end date with the new day + duration, preserving original end time + const newEndDate = new Date(eventDate); newEndDate.setDate(newEndDate.getDate() + durationDays); + newEndDate.setHours(originalEndDate.getHours(), originalEndDate.getMinutes(), originalEndDate.getSeconds(), originalEndDate.getMilliseconds()); + // Update data attributes with new dates + dragEndEvent.draggedClone.dataset.start = newStartDate.toISOString(); + dragEndEvent.draggedClone.dataset.end = newEndDate.toISOString(); const droppedEvent: CalendarEvent = { id: eventId, diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index f074504..b3d9ec3 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -367,7 +367,11 @@ export class DragDropManager { * Optimized snap position calculation using PositionUtils */ private calculateSnapPosition(mouseY: number, column: ColumnBounds): number { - const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, column); + // Calculate where the event top would be (accounting for mouse offset) + const eventTopY = mouseY - this.mouseOffset.y; + + // Snap the event top position, not the mouse position + const snappedY = PositionUtils.getPositionFromCoordinate(eventTopY, column); return Math.max(0, snappedY); } diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index 2d16267..7ffa8d8 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -239,8 +239,9 @@ export class DateEventRenderer implements EventRendererStrategy { public handleDragMove(payload: DragMoveEventPayload): void { if (!this.draggedClone) return; - // Update position - this.draggedClone.style.top = (payload.snappedY - payload.mouseOffset.y) + 'px'; + // Update position - snappedY is already the event top position + // Add +1px to match the initial positioning offset from SwpEventElement + this.draggedClone.style.top = (payload.snappedY + 1) + 'px'; // Update timestamp display this.updateCloneTimestamp(payload);