diff --git a/src/elements/SwpEventElement.ts b/src/elements/SwpEventElement.ts
index 87be4cd..a883fe1 100644
--- a/src/elements/SwpEventElement.ts
+++ b/src/elements/SwpEventElement.ts
@@ -2,6 +2,7 @@ import { CalendarEvent } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
import { TimeFormatter } from '../utils/TimeFormatter';
import { PositionUtils } from '../utils/PositionUtils';
+import { EventLayout } from '../utils/AllDayLayoutEngine';
/**
* Abstract base class for event DOM elements
@@ -75,7 +76,7 @@ export class SwpEventElement extends BaseEventElement {
private createInnerStructure(): void {
const timeRange = TimeFormatter.formatTimeRange(this.event.start, this.event.end);
const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60);
-
+
this.element.innerHTML = `
${timeRange}
${this.event.title}
@@ -107,20 +108,20 @@ export class SwpEventElement extends BaseEventElement {
public createClone(): SwpEventElement {
// Clone the underlying DOM element
const clonedElement = this.element.cloneNode(true) as HTMLElement;
-
+
// Create new SwpEventElement instance from the cloned DOM
const clonedSwpEvent = SwpEventElement.fromExistingElement(clonedElement);
-
+
// Apply "clone-" prefix to ID
clonedSwpEvent.updateEventId(`clone-${this.event.id}`);
-
+
// Cache original duration for drag operations
const originalDuration = this.getOriginalEventDuration();
clonedSwpEvent.element.dataset.originalDuration = originalDuration.toString();
-
+
// Set height from original element
clonedSwpEvent.element.style.height = this.element.style.height || `${this.element.getBoundingClientRect().height}px`;
-
+
return clonedSwpEvent;
}
@@ -130,11 +131,11 @@ export class SwpEventElement extends BaseEventElement {
public static fromExistingElement(element: HTMLElement): SwpEventElement {
// Extract CalendarEvent data from DOM element
const event = this.extractCalendarEventFromElement(element);
-
+
// Create new instance but replace the created element with the existing one
const swpEvent = new SwpEventElement(event);
swpEvent.element = element;
-
+
return swpEvent;
}
@@ -202,7 +203,7 @@ export class SwpEventElement extends BaseEventElement {
const now = new Date();
const startDate = new Date(originalStart);
startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0);
-
+
const endDate = new Date(startDate);
endDate.setMinutes(endDate.getMinutes() + duration);
@@ -228,14 +229,12 @@ export class SwpEventElement extends BaseEventElement {
* All-day event element (now using unified swp-event tag)
*/
export class SwpAllDayEventElement extends BaseEventElement {
- private columnIndex: number;
- private constructor(event: CalendarEvent, columnIndex: number) {
+ constructor(event: CalendarEvent) {
super(event);
- this.columnIndex = columnIndex;
this.setAllDayAttributes();
this.createInnerStructure();
- this.applyGridPositioning();
+ // this.applyGridPositioning();
}
protected createElement(): HTMLElement {
@@ -264,128 +263,9 @@ export class SwpAllDayEventElement extends BaseEventElement {
/**
* Apply CSS grid positioning
*/
- private applyGridPositioning(): void {
- this.element.style.gridColumn = this.columnIndex.toString();
- }
-
- /**
- * Set grid row for this all-day event
- */
- public setGridRow(row: number): void {
- this.element.style.gridRow = row.toString();
- }
-
- /**
- * Set grid column span for this all-day event
- */
- public setColumnSpan(startColumn: number, endColumn: number): void {
- this.element.style.gridColumn = `${startColumn} / ${endColumn + 1}`;
- }
-
- /**
- * Factory method to create from CalendarEvent and layout (provided by AllDayManager)
- */
- public static fromCalendarEventWithLayout(
- event: CalendarEvent,
- layout: { startColumn: number; endColumn: number; row: number; columnSpan: number }
- ): SwpAllDayEventElement {
- // Create element with provided layout
- const element = new SwpAllDayEventElement(event, layout.startColumn);
-
- // Set complete grid-area instead of individual properties
+ public applyGridPositioning(layout: EventLayout): void {
const gridArea = `${layout.row} / ${layout.startColumn} / ${layout.row + 1} / ${layout.endColumn + 1}`;
- element.element.style.gridArea = gridArea;
-
- console.log('✅ SwpAllDayEventElement: Created all-day event with AllDayLayoutEngine', {
- eventId: event.id,
- title: event.title,
- gridArea: gridArea,
- layout: layout
- });
-
- return element;
+ this.element.style.gridArea = gridArea;
}
- /**
- * Factory method to create from CalendarEvent and target date (DEPRECATED - use AllDayManager.calculateAllDayEventLayout)
- * @deprecated Use AllDayManager.calculateAllDayEventLayout() and fromCalendarEventWithLayout() instead
- */
- public static fromCalendarEvent(event: CalendarEvent, targetDate?: string): SwpAllDayEventElement {
- console.warn('⚠️ SwpAllDayEventElement.fromCalendarEvent is deprecated. Use AllDayManager.calculateAllDayEventLayout() instead.');
-
- // Fallback to simple column calculation without overlap detection
- const { startColumn, endColumn } = this.calculateColumnSpan(event);
- const finalStartColumn = targetDate ? this.getColumnIndexForDate(targetDate) : startColumn;
- const finalEndColumn = targetDate ? finalStartColumn : endColumn;
-
- // Create element with row 1 (no overlap detection)
- const element = new SwpAllDayEventElement(event, finalStartColumn);
- element.setGridRow(1);
- element.setColumnSpan(finalStartColumn, finalEndColumn);
-
- return element;
- }
-
- /**
- * Calculate column span based on event start and end dates
- */
- private static calculateColumnSpan(event: CalendarEvent): { startColumn: number; endColumn: number; columnSpan: number } {
- const dayHeaders = document.querySelectorAll('swp-day-header');
-
- // Extract dates from headers
- const headerDates: string[] = [];
- dayHeaders.forEach(header => {
- const date = (header as HTMLElement).dataset.date;
- if (date) {
- headerDates.push(date);
- }
- });
-
- // Format event dates for comparison (YYYY-MM-DD format)
- const eventStartDate = event.start.toISOString().split('T')[0];
- const eventEndDate = event.end.toISOString().split('T')[0];
-
- // Find start and end column indices
- let startColumn = 1;
- let endColumn = headerDates.length;
-
- headerDates.forEach((dateStr, index) => {
- if (dateStr === eventStartDate) {
- startColumn = index + 1;
- }
- if (dateStr === eventEndDate) {
- endColumn = index + 1;
- }
- });
-
- // Ensure end column is at least start column
- if (endColumn < startColumn) {
- endColumn = startColumn;
- }
-
- const columnSpan = endColumn - startColumn + 1;
-
- return { startColumn, endColumn, columnSpan };
- }
-
- /**
- * Get column index for a specific date
- */
- private static getColumnIndexForDate(targetDate: string): number {
- const dayHeaders = document.querySelectorAll('swp-day-header');
- let columnIndex = 1;
- dayHeaders.forEach((header, index) => {
- if ((header as HTMLElement).dataset.date === targetDate) {
- columnIndex = index + 1;
- }
- });
- return columnIndex;
- }
-
- /**
- * Check if two column ranges overlap
- */
- private static columnsOverlap(startA: number, endA: number, startB: number, endB: number): boolean {
- return !(endA < startB || endB < startA);
- }
}
\ No newline at end of file
diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts
index d4c4011..eed4f7f 100644
--- a/src/managers/AllDayManager.ts
+++ b/src/managers/AllDayManager.ts
@@ -3,7 +3,7 @@
import { eventBus } from '../core/EventBus';
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
-import { AllDayLayoutEngine } from '../utils/AllDayLayoutEngine';
+import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { CalendarEvent } from '../types/CalendarTypes';
import {
@@ -96,7 +96,7 @@ export class AllDayManager {
});
eventBus.on('drag:end', (event) => {
- const { draggedElement, mousePosition, finalPosition, target } = (event as CustomEvent).detail;
+ const { draggedElement, mousePosition, finalPosition, target, draggedClone } = (event as CustomEvent).detail;
if (target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
return;
@@ -106,10 +106,9 @@ export class AllDayManager {
eventId: eventId,
finalPosition
});
- const dragClone = document.querySelector(`swp-allday-container swp-event[data-event-id="clone-${eventId}"]`);
console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId });
- this.handleDragEnd(draggedElement, dragClone as HTMLElement, { column: finalPosition.column || '', y: 0 });
+ this.handleDragEnd(draggedElement, draggedClone as HTMLElement, { column: finalPosition.column || '', y: 0 });
});
// Listen for drag cancellation to recalculate height
@@ -307,18 +306,7 @@ export class AllDayManager {
* Calculate layout for ALL all-day events using AllDayLayoutEngine
* This is the correct method that processes all events together for proper overlap detection
*/
- public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): Map {
- console.log('🔍 AllDayManager: calculateAllDayEventsLayout - Processing all events together', {
- eventCount: events.length,
- events: events.map(e => ({ id: e.id, title: e.title, start: e.start.toISOString().split('T')[0], end: e.end.toISOString().split('T')[0] })),
- weekDates
- });
+ public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
// Store current state
this.currentAllDayEvents = events;
@@ -328,35 +316,8 @@ export class AllDayManager {
this.layoutEngine = new AllDayLayoutEngine(weekDates);
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
- const layouts = this.layoutEngine.calculateLayout(events);
+ return this.layoutEngine.calculateLayout(events);
- // Convert to expected return format
- const result = new Map();
-
- layouts.forEach((layout, eventId) => {
- result.set(eventId, {
- startColumn: layout.startColumn,
- endColumn: layout.endColumn,
- row: layout.row,
- columnSpan: layout.columnSpan,
- gridArea: layout.gridArea
- });
-
- console.log('✅ AllDayManager: Calculated layout for event', {
- eventId,
- title: events.find(e => e.id === eventId)?.title,
- gridArea: layout.gridArea,
- layout: layout
- });
- });
-
- return result;
}
@@ -494,19 +455,14 @@ export class AllDayManager {
// 5. Apply differential updates - only update events that changed
let changedCount = 0;
- newLayouts.forEach((layout, eventId) => {
- const oldGridArea = this.currentLayouts.get(eventId);
+ newLayouts.forEach((layout) => {
+ const oldGridArea = this.currentLayouts.get(layout.calenderEvent.id);
const newGridArea = layout.gridArea;
if (oldGridArea !== newGridArea) {
changedCount++;
- const element = document.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement;
+ const element = document.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
if (element) {
- console.log('🔄 AllDayManager: Updating event position', {
- eventId,
- oldGridArea,
- newGridArea
- });
// Add transition class for smooth animation
element.classList.add('transitioning');
@@ -532,61 +488,6 @@ export class AllDayManager {
// 8. Check if height adjustment is needed
this.checkAndAnimateAllDayHeight();
- console.log('✅ AllDayManager: Completed differential drag end', {
- eventId: droppedEventId,
- totalEvents: newLayouts.size,
- changedEvents: changedCount,
- finalGridArea: newLayouts.get(droppedEventId)?.gridArea
- });
- }
-
- /**
- * Get existing all-day events from DOM
- * Since we don't have direct access to EventManager, we'll get events from the current DOM
- */
- private getExistingAllDayEvents(): CalendarEvent[] {
- const allDayContainer = this.getAllDayContainer();
- if (!allDayContainer) {
- return [];
- }
-
- const existingElements = allDayContainer.querySelectorAll('swp-event');
- const events: CalendarEvent[] = [];
-
- existingElements.forEach(element => {
- const htmlElement = element as HTMLElement;
- const eventId = htmlElement.dataset.eventId;
- const title = htmlElement.dataset.title || htmlElement.textContent || '';
- const allDayDate = htmlElement.dataset.allDayDate;
-
- if (eventId && allDayDate) {
- events.push({
- id: eventId,
- title: title,
- start: new Date(allDayDate),
- end: new Date(allDayDate),
- type: 'work',
- allDay: true,
- syncStatus: 'synced'
- });
- }
- });
-
- return events;
- }
-
- private getVisibleDatesFromDOM(): string[] {
- const dayHeaders = document.querySelectorAll('swp-calendar-header swp-day-header');
- const weekDates: string[] = [];
-
- dayHeaders.forEach(header => {
- const dateAttr = header.getAttribute('data-date');
- if (dateAttr) {
- weekDates.push(dateAttr);
- }
- });
-
- return weekDates;
}
}
\ No newline at end of file
diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts
index b6b2933..e521918 100644
--- a/src/managers/DragDropManager.ts
+++ b/src/managers/DragDropManager.ts
@@ -132,7 +132,10 @@ export class DragDropManager {
}
private handleMouseDown(event: MouseEvent): void {
- this.isDragStarted = false;
+
+ // Clean up drag state first
+ this.cleanupDragState();
+
this.lastMousePosition = { x: event.clientX, y: event.clientY };
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
this.initialMousePosition = { x: event.clientX, y: event.clientY };
@@ -274,11 +277,10 @@ export class DragDropManager {
if (this.draggedElement) {
// Store variables locally before cleanup
- const draggedElement = this.draggedElement;
+ //const draggedElement = this.draggedElement;
const isDragStarted = this.isDragStarted;
- // Clean up drag state first
- this.cleanupDragState();
+
// Only emit drag:end if drag was actually started
@@ -292,7 +294,7 @@ export class DragDropManager {
const dropTarget = this.detectDropTarget(mousePosition);
console.log('🎯 DragDropManager: Emitting drag:end', {
- draggedElement: draggedElement.dataset.eventId,
+ draggedElement: this.draggedElement.dataset.eventId,
finalColumn: positionData.column,
finalY: positionData.snappedY,
dropTarget: dropTarget,
@@ -300,19 +302,20 @@ export class DragDropManager {
});
const dragEndPayload: DragEndEventPayload = {
- draggedElement: draggedElement,
+ draggedElement: this.draggedElement,
+ draggedClone : this.draggedClone,
mousePosition,
finalPosition: positionData,
target: dropTarget
};
this.eventBus.emit('drag:end', dragEndPayload);
- draggedElement.remove();
+ this.draggedElement.remove(); // TODO: this should be changed into a subscriber which only after a succesful placement is fired, not just mouseup as this can remove elements that are not placed.
} else {
// This was just a click - emit click event instead
this.eventBus.emit('event:click', {
- draggedElement: draggedElement,
+ draggedElement: this.draggedElement,
mousePosition: { x: event.clientX, y: event.clientY }
});
}
@@ -540,13 +543,11 @@ export class DragDropManager {
* Detect drop target - whether dropped in swp-day-column or swp-day-header
*/
private detectDropTarget(position: Position): 'swp-day-column' | 'swp-day-header' | null {
- const elementAtPosition = document.elementFromPoint(position.x, position.y);
- if (!elementAtPosition) return null;
-
+
// Traverse up the DOM tree to find the target container
- let currentElement = elementAtPosition as HTMLElement;
+ let currentElement = this.draggedClone;
while (currentElement && currentElement !== document.body) {
- if (currentElement.tagName === 'SWP-DAY-HEADER') {
+ if (currentElement.tagName === 'SWP-ALLDAY-CONTAINER') {
return 'swp-day-header';
}
if (currentElement.tagName === 'SWP-DAY-COLUMN') {
diff --git a/src/renderers/AllDayEventRenderer.ts b/src/renderers/AllDayEventRenderer.ts
index 4f95a26..acdce87 100644
--- a/src/renderers/AllDayEventRenderer.ts
+++ b/src/renderers/AllDayEventRenderer.ts
@@ -1,5 +1,6 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
+import { EventLayout } from '../utils/AllDayLayoutEngine';
/**
* AllDayEventRenderer - Simple rendering of all-day events
@@ -17,19 +18,19 @@ export class AllDayEventRenderer {
* Get or cache all-day container, create if it doesn't exist - SIMPLIFIED (no ghost columns)
*/
private getContainer(): HTMLElement | null {
-
- const header = document.querySelector('swp-calendar-header');
- if (header) {
- this.container = header.querySelector('swp-allday-container');
-
- if (!this.container) {
- this.container = document.createElement('swp-allday-container');
- header.appendChild(this.container);
-
- }
+
+ const header = document.querySelector('swp-calendar-header');
+ if (header) {
+ this.container = header.querySelector('swp-allday-container');
+
+ if (!this.container) {
+ this.container = document.createElement('swp-allday-container');
+ header.appendChild(this.container);
+
}
- return this.container;
-
+ }
+ return this.container;
+
}
// REMOVED: createGhostColumns() method - no longer needed!
@@ -39,16 +40,15 @@ export class AllDayEventRenderer {
*/
public renderAllDayEventWithLayout(
event: CalendarEvent,
- layout: { startColumn: number; endColumn: number; row: number; columnSpan: number }
- ): HTMLElement | null {
+ layout: EventLayout
+ ) {
const container = this.getContainer();
if (!container) return null;
- const allDayElement = SwpAllDayEventElement.fromCalendarEventWithLayout(event, layout);
- const element = allDayElement.getElement();
-
- container.appendChild(element);
- return element;
+ let dayEvent = new SwpAllDayEventElement(event);
+ dayEvent.applyGridPositioning(layout);
+
+ container.appendChild(dayEvent.getElement());
}
diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts
index 53e9b02..581b7d0 100644
--- a/src/renderers/EventRendererManager.ts
+++ b/src/renderers/EventRendererManager.ts
@@ -372,35 +372,11 @@ export class EventRenderingService {
// Pass current events to AllDayManager for state tracking
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
- // Calculate layout for ALL all-day events together using AllDayLayoutEngine
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
// Render each all-day event with pre-calculated layout
- allDayEvents.forEach(event => {
- const layout = layouts.get(event.id);
- if (!layout) {
- console.warn('❌ EventRenderingService: No layout found for all-day event', {
- id: event.id,
- title: event.title
- });
- return;
- }
-
- // Render with pre-calculated layout
- const renderedElement = this.allDayEventRenderer.renderAllDayEventWithLayout(event, layout);
- if (renderedElement) {
- console.log('✅ EventRenderingService: Rendered all-day event with AllDayLayoutEngine', {
- id: event.id,
- title: event.title,
- gridArea: layout.gridArea,
- element: renderedElement.tagName
- });
- } else {
- console.warn('❌ EventRenderingService: Failed to render all-day event', {
- id: event.id,
- title: event.title
- });
- }
+ layouts.forEach(layout => {
+ this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout);
});
// Check and adjust all-day container height after rendering
diff --git a/src/types/EventTypes.ts b/src/types/EventTypes.ts
index d9ff01a..a649740 100644
--- a/src/types/EventTypes.ts
+++ b/src/types/EventTypes.ts
@@ -64,6 +64,7 @@ export interface DragMoveEventPayload {
// Drag end event payload
export interface DragEndEventPayload {
draggedElement: HTMLElement;
+ draggedClone: HTMLElement | null;
mousePosition: MousePosition;
finalPosition: {
column: string | null;
diff --git a/src/utils/AllDayLayoutEngine.ts b/src/utils/AllDayLayoutEngine.ts
index 6503901..ac8bad8 100644
--- a/src/utils/AllDayLayoutEngine.ts
+++ b/src/utils/AllDayLayoutEngine.ts
@@ -1,7 +1,7 @@
import { CalendarEvent } from '../types/CalendarTypes';
export interface EventLayout {
- id: string;
+ calenderEvent: CalendarEvent;
gridArea: string; // "row-start / col-start / row-end / col-end"
startColumn: number;
endColumn: number;
@@ -21,42 +21,38 @@ export class AllDayLayoutEngine {
/**
* Calculate layout for all events using clean day-based logic
*/
- public calculateLayout(events: CalendarEvent[]): Map {
- const layouts = new Map();
-
- if (this.weekDates.length === 0) {
- return layouts;
- }
+ public calculateLayout(events: CalendarEvent[]): EventLayout[] {
+ let layouts: EventLayout[] = [];
// Reset tracks for new calculation
this.tracks = [new Array(this.weekDates.length).fill(false)];
-
+
// Filter to only visible events
const visibleEvents = events.filter(event => this.isEventVisible(event));
-
+
// Process events in input order (no sorting)
for (const event of visibleEvents) {
const startDay = this.getEventStartDay(event);
const endDay = this.getEventEndDay(event);
-
+
if (startDay > 0 && endDay > 0) {
const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks
-
+
// Mark days as occupied
for (let day = startDay - 1; day <= endDay - 1; day++) {
this.tracks[track][day] = true;
}
-
+
const layout: EventLayout = {
- id: event.id,
+ calenderEvent: event,
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
startColumn: startDay,
endColumn: endDay,
row: track + 1,
columnSpan: endDay - startDay + 1
};
-
- layouts.set(event.id, layout);
+ layouts.push(layout);
+
}
}
@@ -72,7 +68,7 @@ export class AllDayLayoutEngine {
return trackIndex;
}
}
-
+
// Create new track if none available
this.tracks.push(new Array(this.weekDates.length).fill(false));
return this.tracks.length - 1;
@@ -96,10 +92,10 @@ export class AllDayLayoutEngine {
private getEventStartDay(event: CalendarEvent): number {
const eventStartDate = this.formatDate(event.start);
const firstVisibleDate = this.weekDates[0];
-
+
// If event starts before visible range, clip to first visible day
const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate;
-
+
const dayIndex = this.weekDates.indexOf(clippedStartDate);
return dayIndex >= 0 ? dayIndex + 1 : 0;
}
@@ -110,10 +106,10 @@ export class AllDayLayoutEngine {
private getEventEndDay(event: CalendarEvent): number {
const eventEndDate = this.formatDate(event.end);
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
-
+
// If event ends after visible range, clip to last visible day
const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate;
-
+
const dayIndex = this.weekDates.indexOf(clippedEndDate);
return dayIndex >= 0 ? dayIndex + 1 : 0;
}
@@ -123,12 +119,12 @@ export class AllDayLayoutEngine {
*/
private isEventVisible(event: CalendarEvent): boolean {
if (this.weekDates.length === 0) return false;
-
+
const eventStartDate = this.formatDate(event.start);
const eventEndDate = this.formatDate(event.end);
const firstVisibleDate = this.weekDates[0];
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
-
+
// Event overlaps if it doesn't end before visible range starts
// AND doesn't start after visible range ends
return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate);