Improves all-day event drag and drop
Refactors all-day event drag and drop handling for improved accuracy and performance. Introduces a shared `ColumnDetectionUtils` for consistent column detection. Simplifies all-day conversion during drag, placing events in row 1 and calculating the column from the target date. Implements differential updates during drag end, updating only changed events for smoother transitions.
This commit is contained in:
parent
41d078e2e8
commit
0553089085
6 changed files with 307 additions and 185 deletions
|
|
@ -4,12 +4,14 @@ import { eventBus } from '../core/EventBus';
|
||||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||||
import { AllDayLayoutEngine } from '../utils/AllDayLayoutEngine';
|
import { AllDayLayoutEngine } from '../utils/AllDayLayoutEngine';
|
||||||
|
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import {
|
import {
|
||||||
DragMouseEnterHeaderEventPayload,
|
DragMouseEnterHeaderEventPayload,
|
||||||
DragStartEventPayload,
|
DragStartEventPayload,
|
||||||
DragMoveEventPayload,
|
DragMoveEventPayload,
|
||||||
DragEndEventPayload
|
DragEndEventPayload,
|
||||||
|
DragColumnChangeEventPayload
|
||||||
} from '../types/EventTypes';
|
} from '../types/EventTypes';
|
||||||
import { DragOffset, MousePosition } from '../types/DragDropTypes';
|
import { DragOffset, MousePosition } from '../types/DragDropTypes';
|
||||||
|
|
||||||
|
|
@ -21,6 +23,11 @@ export class AllDayManager {
|
||||||
private allDayEventRenderer: AllDayEventRenderer;
|
private allDayEventRenderer: AllDayEventRenderer;
|
||||||
private layoutEngine: AllDayLayoutEngine | null = null;
|
private layoutEngine: AllDayLayoutEngine | null = null;
|
||||||
|
|
||||||
|
// State tracking for differential updates
|
||||||
|
private currentLayouts: Map<string, string> = new Map();
|
||||||
|
private currentAllDayEvents: CalendarEvent[] = [];
|
||||||
|
private currentWeekDates: string[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.allDayEventRenderer = new AllDayEventRenderer();
|
this.allDayEventRenderer = new AllDayEventRenderer();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|
@ -53,10 +60,6 @@ export class AllDayManager {
|
||||||
originalElementId: originalElement?.dataset?.eventId
|
originalElementId: originalElement?.dataset?.eventId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cloneElement && cloneElement.classList.contains('all-day-style')) {
|
|
||||||
this.handleConvertFromAllDay(cloneElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -73,17 +76,26 @@ export class AllDayManager {
|
||||||
this.handleDragStart(draggedElement, eventId || '', mouseOffset);
|
this.handleDragStart(draggedElement, eventId || '', mouseOffset);
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.on('drag:move', (event) => {
|
eventBus.on('drag:column-change', (event) => {
|
||||||
const { draggedElement, mousePosition } = (event as CustomEvent<DragMoveEventPayload>).detail;
|
const { draggedElement, mousePosition } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
// Only handle for all-day events - check if original element is all-day
|
|
||||||
const isAllDayEvent = draggedElement.closest('swp-allday-container');
|
|
||||||
if (!isAllDayEvent) return;
|
|
||||||
|
|
||||||
|
// Check if there's an all-day clone for this event
|
||||||
const eventId = draggedElement.dataset.eventId;
|
const eventId = draggedElement.dataset.eventId;
|
||||||
const dragClone = document.querySelector(`swp-allday-container swp-event[data-event-id="clone-${eventId}"]`);
|
const dragClone = document.querySelector(`swp-allday-container swp-event[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||||
|
|
||||||
|
if (!dragClone.hasAttribute('data-allday')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If we find an all-day clone, handle the drag move
|
||||||
if (dragClone) {
|
if (dragClone) {
|
||||||
this.handleDragMove(dragClone as HTMLElement, mousePosition);
|
console.log('🔄 AllDayManager: Found all-day clone, handling drag:column-change', {
|
||||||
|
eventId,
|
||||||
|
cloneId: dragClone.dataset.eventId
|
||||||
|
});
|
||||||
|
this.handleColumnChange(dragClone, mousePosition);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -259,6 +271,42 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store current layouts from DOM for comparison
|
||||||
|
*/
|
||||||
|
private storeCurrentLayouts(): void {
|
||||||
|
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;
|
||||||
|
const gridArea = htmlElement.style.gridArea;
|
||||||
|
if (eventId && gridArea) {
|
||||||
|
this.currentLayouts.set(eventId, gridArea);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 AllDayManager: Stored current layouts', {
|
||||||
|
count: this.currentLayouts.size,
|
||||||
|
layouts: Array.from(this.currentLayouts.entries())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current events and week dates (called by EventRendererManager)
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
||||||
* This is the correct method that processes all events together for proper overlap detection
|
* This is the correct method that processes all events together for proper overlap detection
|
||||||
|
|
@ -276,6 +324,10 @@ export class AllDayManager {
|
||||||
weekDates
|
weekDates
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store current state
|
||||||
|
this.currentAllDayEvents = events;
|
||||||
|
this.currentWeekDates = weekDates;
|
||||||
|
|
||||||
// Initialize layout engine with provided week dates
|
// Initialize layout engine with provided week dates
|
||||||
this.layoutEngine = new AllDayLayoutEngine(weekDates);
|
this.layoutEngine = new AllDayLayoutEngine(weekDates);
|
||||||
|
|
||||||
|
|
@ -313,95 +365,37 @@ export class AllDayManager {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle conversion of timed event to all-day event using CSS styling
|
* 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(targetDate: string, cloneElement: HTMLElement): void {
|
||||||
console.log('🔄 AllDayManager: Converting to all-day using AllDayLayoutEngine', {
|
console.log('🔄 AllDayManager: Converting to all-day (row 1 only during drag)', {
|
||||||
eventId: cloneElement.dataset.eventId,
|
eventId: cloneElement.dataset.eventId,
|
||||||
targetDate
|
targetDate
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all-day container, request creation if needed
|
// Get all-day container, request creation if needed
|
||||||
let allDayContainer = this.getAllDayContainer();
|
let allDayContainer = this.getAllDayContainer();
|
||||||
if (!allDayContainer) {
|
|
||||||
console.log('🔄 AllDayManager: All-day container not found, requesting creation...');
|
|
||||||
// Request HeaderManager to create container
|
|
||||||
eventBus.emit('header:ensure-allday-container');
|
|
||||||
|
|
||||||
// Try again after request
|
// Calculate target column from targetDate using ColumnDetectionUtils
|
||||||
allDayContainer = this.getAllDayContainer();
|
const targetColumn = ColumnDetectionUtils.getColumnIndexFromDate(targetDate);
|
||||||
if (!allDayContainer) {
|
|
||||||
console.error('All-day container still not found after creation request');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock event for layout calculation
|
cloneElement.removeAttribute('style');
|
||||||
const mockEvent: CalendarEvent = {
|
|
||||||
id: cloneElement.dataset.eventId || '',
|
|
||||||
title: cloneElement.dataset.title || '',
|
|
||||||
start: new Date(targetDate),
|
|
||||||
end: new Date(targetDate),
|
|
||||||
type: 'work',
|
|
||||||
allDay: true,
|
|
||||||
syncStatus: 'synced'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get existing all-day events from EventManager
|
|
||||||
const existingEvents = this.getExistingAllDayEvents();
|
|
||||||
|
|
||||||
// Add the new drag event to the array
|
|
||||||
const allEvents = [...existingEvents, mockEvent];
|
|
||||||
|
|
||||||
// Get actual visible dates from DOM headers (same as EventRendererManager does)
|
|
||||||
const weekDates = this.getVisibleDatesFromDOM();
|
|
||||||
|
|
||||||
// Calculate layout for all events including the new one
|
|
||||||
const layouts = this.calculateAllDayEventsLayout(allEvents, weekDates);
|
|
||||||
const layout = layouts.get(mockEvent.id);
|
|
||||||
|
|
||||||
if (!layout) {
|
|
||||||
console.error('AllDayManager: No layout found for drag event', mockEvent.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all properties BEFORE adding to DOM
|
|
||||||
cloneElement.classList.add('all-day-style');
|
cloneElement.classList.add('all-day-style');
|
||||||
cloneElement.style.gridColumn = layout.startColumn.toString();
|
cloneElement.style.gridRow = '1';
|
||||||
cloneElement.style.gridRow = layout.row.toString();
|
cloneElement.style.gridColumn = targetColumn.toString();
|
||||||
cloneElement.dataset.allDayDate = targetDate;
|
cloneElement.dataset.allday = 'true'; // Set the all-day attribute for filtering
|
||||||
cloneElement.style.display = '';
|
|
||||||
|
|
||||||
// NOW add to container (after all positioning is calculated)
|
// Add to container
|
||||||
allDayContainer.appendChild(cloneElement);
|
allDayContainer?.appendChild(cloneElement);
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Converted to all-day style using AllDayLayoutEngine', {
|
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
|
||||||
eventId: cloneElement.dataset.eventId,
|
eventId: cloneElement.dataset.eventId,
|
||||||
gridColumn: layout.startColumn,
|
gridColumn: targetColumn,
|
||||||
gridRow: layout.row
|
gridRow: 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle conversion from all-day back to timed event
|
|
||||||
*/
|
|
||||||
private handleConvertFromAllDay(cloneElement: HTMLElement): void {
|
|
||||||
console.log('🔄 AllDayManager: Converting from all-day back to timed', {
|
|
||||||
eventId: cloneElement.dataset.eventId
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove all-day CSS class
|
|
||||||
cloneElement.classList.remove('all-day-style');
|
|
||||||
|
|
||||||
// Reset grid positioning
|
|
||||||
cloneElement.style.gridColumn = '';
|
|
||||||
cloneElement.style.gridRow = '';
|
|
||||||
|
|
||||||
// Remove all-day date attribute
|
|
||||||
delete cloneElement.dataset.allDayDate;
|
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Converted from all-day back to timed');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag start for all-day events
|
* Handle drag start for all-day events
|
||||||
|
|
@ -439,49 +433,114 @@ export class AllDayManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag move for all-day events
|
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
|
||||||
*/
|
*/
|
||||||
private handleDragMove(dragClone: HTMLElement, mousePosition: MousePosition): void {
|
private handleColumnChange(dragClone: HTMLElement, mousePosition: MousePosition): void {
|
||||||
// Calculate grid column based on mouse position
|
// Get the all-day container to understand its grid structure
|
||||||
const dayHeaders = document.querySelectorAll('swp-day-header');
|
const allDayContainer = this.getAllDayContainer();
|
||||||
let targetColumn = 1;
|
if (!allDayContainer) return;
|
||||||
|
|
||||||
dayHeaders.forEach((header, index) => {
|
// Calculate target column using ColumnDetectionUtils
|
||||||
const rect = header.getBoundingClientRect();
|
const targetColumn = ColumnDetectionUtils.getColumnIndexFromX(mousePosition.x);
|
||||||
if (mousePosition.x >= rect.left && mousePosition.x <= rect.right) {
|
|
||||||
targetColumn = index + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update clone position
|
// 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.toString();
|
||||||
|
dragClone.style.gridRow = '1'; // Force row 1 during drag
|
||||||
|
dragClone.style.gridArea = `1 / ${targetColumn} / 2 / ${targetColumn + 1}`;
|
||||||
|
|
||||||
console.log('🔄 AllDayManager: Updated drag clone position', {
|
console.log('🔄 AllDayManager: Updated all-day drag clone position', {
|
||||||
eventId: dragClone.dataset.eventId,
|
eventId: dragClone.dataset.eventId,
|
||||||
targetColumn,
|
targetColumn,
|
||||||
|
gridRow: 1,
|
||||||
|
gridArea: dragClone.style.gridArea,
|
||||||
mouseX: mousePosition.x
|
mouseX: mousePosition.x
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag end for all-day events
|
* Handle drag end for all-day events - WITH DIFFERENTIAL UPDATES
|
||||||
*/
|
*/
|
||||||
private handleDragEnd(originalElement: HTMLElement, dragClone: HTMLElement, finalPosition: { column: string; y: number }): void {
|
private handleDragEnd(originalElement: HTMLElement, dragClone: HTMLElement, finalPosition: { column: string; y: number }): void {
|
||||||
// Normalize clone
|
console.log('🎯 AllDayManager: Starting drag end with differential updates', {
|
||||||
|
eventId: dragClone.dataset.eventId,
|
||||||
|
finalColumn: finalPosition.column
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. Store current layouts BEFORE any changes
|
||||||
|
this.storeCurrentLayouts();
|
||||||
|
|
||||||
|
// 2. Normalize clone ID
|
||||||
const cloneId = dragClone.dataset.eventId;
|
const cloneId = dragClone.dataset.eventId;
|
||||||
if (cloneId?.startsWith('clone-')) {
|
if (cloneId?.startsWith('clone-')) {
|
||||||
dragClone.dataset.eventId = cloneId.replace('clone-', '');
|
dragClone.dataset.eventId = cloneId.replace('clone-', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove dragging styles
|
// 3. Create temporary array with existing events + the dropped event
|
||||||
|
const droppedEventId = dragClone.dataset.eventId || '';
|
||||||
|
const droppedEventDate = dragClone.dataset.allDayDate || finalPosition.column;
|
||||||
|
|
||||||
|
const droppedEvent: CalendarEvent = {
|
||||||
|
id: droppedEventId,
|
||||||
|
title: dragClone.dataset.title || dragClone.textContent || '',
|
||||||
|
start: new Date(droppedEventDate),
|
||||||
|
end: new Date(droppedEventDate),
|
||||||
|
type: 'work',
|
||||||
|
allDay: true,
|
||||||
|
syncStatus: 'synced'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use current events + dropped event for calculation
|
||||||
|
const tempEvents = [...this.currentAllDayEvents, droppedEvent];
|
||||||
|
|
||||||
|
// 4. Calculate new layouts for ALL events
|
||||||
|
const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
|
||||||
|
|
||||||
|
// 5. Apply differential updates - only update events that changed
|
||||||
|
let changedCount = 0;
|
||||||
|
newLayouts.forEach((layout, eventId) => {
|
||||||
|
const oldGridArea = this.currentLayouts.get(eventId);
|
||||||
|
const newGridArea = layout.gridArea;
|
||||||
|
|
||||||
|
if (oldGridArea !== newGridArea) {
|
||||||
|
changedCount++;
|
||||||
|
const element = document.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement;
|
||||||
|
if (element) {
|
||||||
|
console.log('🔄 AllDayManager: Updating event position', {
|
||||||
|
eventId,
|
||||||
|
oldGridArea,
|
||||||
|
newGridArea
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add transition class for smooth animation
|
||||||
|
element.classList.add('transitioning');
|
||||||
|
element.style.gridArea = newGridArea;
|
||||||
|
element.style.gridRow = layout.row.toString();
|
||||||
|
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
||||||
|
|
||||||
|
// Remove transition class after animation
|
||||||
|
setTimeout(() => element.classList.remove('transitioning'), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Clean up drag styles from the dropped clone
|
||||||
dragClone.classList.remove('dragging');
|
dragClone.classList.remove('dragging');
|
||||||
dragClone.style.zIndex = '';
|
dragClone.style.zIndex = '';
|
||||||
dragClone.style.cursor = '';
|
dragClone.style.cursor = '';
|
||||||
dragClone.style.opacity = '';
|
dragClone.style.opacity = '';
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Completed drag operation for all-day event', {
|
// 7. Restore original element opacity
|
||||||
eventId: dragClone.dataset.eventId,
|
originalElement.style.opacity = '';
|
||||||
finalColumn: dragClone.style.gridColumn
|
|
||||||
|
// 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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
|
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import {
|
import {
|
||||||
DragStartEventPayload,
|
DragStartEventPayload,
|
||||||
DragMoveEventPayload,
|
DragMoveEventPayload,
|
||||||
DragEndEventPayload,
|
DragEndEventPayload,
|
||||||
DragMouseEnterHeaderEventPayload,
|
DragMouseEnterHeaderEventPayload,
|
||||||
DragMouseLeaveHeaderEventPayload
|
DragMouseLeaveHeaderEventPayload,
|
||||||
|
DragColumnChangeEventPayload
|
||||||
} from '../types/EventTypes';
|
} from '../types/EventTypes';
|
||||||
|
|
||||||
interface CachedElements {
|
interface CachedElements {
|
||||||
|
|
@ -25,11 +27,6 @@ interface Position {
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColumnBounds {
|
|
||||||
date: string;
|
|
||||||
left: number;
|
|
||||||
right: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DragDropManager {
|
export class DragDropManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
|
|
@ -59,8 +56,6 @@ export class DragDropManager {
|
||||||
lastColumnDate: null
|
lastColumnDate: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Column bounds cache for coordinate-based column detection
|
|
||||||
private columnBoundsCache: ColumnBounds[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
// Auto-scroll properties
|
// Auto-scroll properties
|
||||||
|
|
@ -120,16 +115,16 @@ export class DragDropManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize column bounds cache
|
// Initialize column bounds cache
|
||||||
this.updateColumnBoundsCache();
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||||
|
|
||||||
// Listen to resize events to update cache
|
// Listen to resize events to update cache
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
this.updateColumnBoundsCache();
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen to navigation events to update cache
|
// Listen to navigation events to update cache
|
||||||
this.eventBus.on('navigation:completed', () => {
|
this.eventBus.on('navigation:completed', () => {
|
||||||
this.updateColumnBoundsCache();
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -247,12 +242,13 @@ export class DragDropManager {
|
||||||
const previousColumn = this.currentColumn;
|
const previousColumn = this.currentColumn;
|
||||||
this.currentColumn = newColumn;
|
this.currentColumn = newColumn;
|
||||||
|
|
||||||
this.eventBus.emit('drag:column-change', {
|
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
previousColumn,
|
previousColumn,
|
||||||
newColumn,
|
newColumn,
|
||||||
mousePosition: currentPosition
|
mousePosition: currentPosition
|
||||||
});
|
};
|
||||||
|
this.eventBus.emit('drag:column-change', dragColumnChangePayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -377,58 +373,13 @@ export class DragDropManager {
|
||||||
return Math.max(0, snappedY);
|
return Math.max(0, snappedY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update column bounds cache for coordinate-based column detection
|
|
||||||
*/
|
|
||||||
private updateColumnBoundsCache(): void {
|
|
||||||
// Reset cache
|
|
||||||
this.columnBoundsCache = [];
|
|
||||||
|
|
||||||
// Find alle kolonner
|
|
||||||
const columns = document.querySelectorAll('swp-day-column');
|
|
||||||
|
|
||||||
// Cache hver kolonnes x-grænser
|
|
||||||
columns.forEach(column => {
|
|
||||||
const rect = column.getBoundingClientRect();
|
|
||||||
const date = (column as HTMLElement).dataset.date;
|
|
||||||
|
|
||||||
if (date) {
|
|
||||||
this.columnBoundsCache.push({
|
|
||||||
date,
|
|
||||||
left: rect.left,
|
|
||||||
right: rect.right
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sorter efter x-position (fra venstre til højre)
|
|
||||||
this.columnBoundsCache.sort((a, b) => a.left - b.left);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get column date from X coordinate using cached bounds
|
|
||||||
*/
|
|
||||||
private getColumnDateFromX(x: number): string | null {
|
|
||||||
// Opdater cache hvis tom
|
|
||||||
if (this.columnBoundsCache.length === 0) {
|
|
||||||
this.updateColumnBoundsCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find den kolonne hvor x-koordinaten er indenfor grænserne
|
|
||||||
const column = this.columnBoundsCache.find(col =>
|
|
||||||
x >= col.left && x <= col.right
|
|
||||||
);
|
|
||||||
|
|
||||||
return column ? column.date : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinate-based column detection (replaces DOM traversal)
|
* Coordinate-based column detection (replaces DOM traversal)
|
||||||
*/
|
*/
|
||||||
private detectColumn(mouseX: number, mouseY: number): string | null {
|
private detectColumn(mouseX: number, mouseY: number): string | null {
|
||||||
// Brug den koordinatbaserede metode direkte
|
// Brug den koordinatbaserede metode direkte
|
||||||
const columnDate = this.getColumnDateFromX(mouseX);
|
const columnDate = ColumnDetectionUtils.getColumnDateFromX(mouseX);
|
||||||
|
|
||||||
// Opdater stadig den eksisterende cache hvis vi finder en kolonne
|
// Opdater stadig den eksisterende cache hvis vi finder en kolonne
|
||||||
if (columnDate && columnDate !== this.cachedElements.lastColumnDate) {
|
if (columnDate && columnDate !== this.cachedElements.lastColumnDate) {
|
||||||
|
|
@ -610,7 +561,7 @@ export class DragDropManager {
|
||||||
this.isInHeader = true;
|
this.isInHeader = true;
|
||||||
|
|
||||||
// Calculate target date using existing method
|
// Calculate target date using existing method
|
||||||
const targetDate = this.getColumnDateFromX(event.clientX);
|
const targetDate = ColumnDetectionUtils.getColumnDateFromX(event.clientX);
|
||||||
|
|
||||||
if (targetDate) {
|
if (targetDate) {
|
||||||
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate });
|
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate });
|
||||||
|
|
@ -636,7 +587,7 @@ export class DragDropManager {
|
||||||
console.log('🚪 DragDropManager: Emitting drag:mouseleave-header');
|
console.log('🚪 DragDropManager: Emitting drag:mouseleave-header');
|
||||||
|
|
||||||
// Calculate target date using existing method
|
// Calculate target date using existing method
|
||||||
const targetDate = this.getColumnDateFromX(event.clientX);
|
const targetDate = ColumnDetectionUtils.getColumnDateFromX(event.clientX);
|
||||||
|
|
||||||
// Find clone element (if it exists)
|
// Find clone element (if it exists)
|
||||||
const eventId = this.draggedElement?.dataset.eventId;
|
const eventId = this.draggedElement?.dataset.eventId;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { AllDayManager } from '../managers/AllDayManager';
|
||||||
import { EventRendererStrategy } from './EventRenderer';
|
import { EventRendererStrategy } from './EventRenderer';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import { AllDayEventRenderer } from './AllDayEventRenderer';
|
import { AllDayEventRenderer } from './AllDayEventRenderer';
|
||||||
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
|
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
|
||||||
/**
|
/**
|
||||||
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
||||||
* Håndterer event positioning og overlap detection
|
* Håndterer event positioning og overlap detection
|
||||||
|
|
@ -190,6 +190,11 @@ export class EventRenderingService {
|
||||||
// Handle drag move
|
// Handle drag move
|
||||||
this.eventBus.on('drag:move', (event: Event) => {
|
this.eventBus.on('drag:move', (event: Event) => {
|
||||||
const { draggedElement, snappedY, column, mouseOffset } = (event as CustomEvent<DragMoveEventPayload>).detail;
|
const { draggedElement, snappedY, column, mouseOffset } = (event as CustomEvent<DragMoveEventPayload>).detail;
|
||||||
|
|
||||||
|
// Filter: Only handle events WITHOUT data-allday attribute (normal timed events)
|
||||||
|
if (draggedElement.hasAttribute('data-allday')) {
|
||||||
|
return; // This is an all-day event, let AllDayManager handle it
|
||||||
|
}
|
||||||
if (this.strategy.handleDragMove && column) {
|
if (this.strategy.handleDragMove && column) {
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = draggedElement.dataset.eventId || '';
|
||||||
this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset);
|
this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset);
|
||||||
|
|
@ -241,7 +246,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
// Handle column change
|
// Handle column change
|
||||||
this.eventBus.on('drag:column-change', (event: Event) => {
|
this.eventBus.on('drag:column-change', (event: Event) => {
|
||||||
const { draggedElement, newColumn } = (event as CustomEvent).detail;
|
const { draggedElement, newColumn } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||||
if (this.strategy.handleColumnChange) {
|
if (this.strategy.handleColumnChange) {
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = draggedElement.dataset.eventId || '';
|
||||||
this.strategy.handleColumnChange(eventId, newColumn);
|
this.strategy.handleColumnChange(eventId, newColumn);
|
||||||
|
|
@ -359,6 +364,9 @@ export class EventRenderingService {
|
||||||
count: weekDates.length
|
count: weekDates.length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pass current events to AllDayManager for state tracking
|
||||||
|
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
|
||||||
|
|
||||||
// Calculate layout for ALL all-day events together using AllDayLayoutEngine
|
// Calculate layout for ALL all-day events together using AllDayLayoutEngine
|
||||||
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,14 @@ export interface DragMouseLeaveHeaderEventPayload {
|
||||||
cloneElement: HTMLElement| null;
|
cloneElement: HTMLElement| null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag column change event payload
|
||||||
|
export interface DragColumnChangeEventPayload {
|
||||||
|
draggedElement: HTMLElement;
|
||||||
|
previousColumn: string | null;
|
||||||
|
newColumn: string;
|
||||||
|
mousePosition: MousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
// Header ready event payload
|
// Header ready event payload
|
||||||
export interface HeaderReadyEventPayload {
|
export interface HeaderReadyEventPayload {
|
||||||
headerElement: HTMLElement;
|
headerElement: HTMLElement;
|
||||||
|
|
|
||||||
94
src/utils/ColumnDetectionUtils.ts
Normal file
94
src/utils/ColumnDetectionUtils.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* ColumnDetectionUtils - Shared utility for column detection and caching
|
||||||
|
* Used by both DragDropManager and AllDayManager for consistent column detection
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ColumnBounds {
|
||||||
|
date: string;
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ColumnDetectionUtils {
|
||||||
|
private static columnBoundsCache: ColumnBounds[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update column bounds cache for coordinate-based column detection
|
||||||
|
*/
|
||||||
|
public static updateColumnBoundsCache(): void {
|
||||||
|
// Reset cache
|
||||||
|
this.columnBoundsCache = [];
|
||||||
|
|
||||||
|
// Find alle kolonner
|
||||||
|
const columns = document.querySelectorAll('swp-day-column');
|
||||||
|
|
||||||
|
// Cache hver kolonnes x-grænser
|
||||||
|
columns.forEach(column => {
|
||||||
|
const rect = column.getBoundingClientRect();
|
||||||
|
const date = (column as HTMLElement).dataset.date;
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
this.columnBoundsCache.push({
|
||||||
|
date,
|
||||||
|
left: rect.left,
|
||||||
|
right: rect.right
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sorter efter x-position (fra venstre til højre)
|
||||||
|
this.columnBoundsCache.sort((a, b) => a.left - b.left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get column date from X coordinate using cached bounds
|
||||||
|
*/
|
||||||
|
public static getColumnDateFromX(x: number): string | null {
|
||||||
|
// Opdater cache hvis tom
|
||||||
|
if (this.columnBoundsCache.length === 0) {
|
||||||
|
this.updateColumnBoundsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find den kolonne hvor x-koordinaten er indenfor grænserne
|
||||||
|
const column = this.columnBoundsCache.find(col =>
|
||||||
|
x >= col.left && x <= col.right
|
||||||
|
);
|
||||||
|
|
||||||
|
return column ? column.date : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get column index (1-based) from date
|
||||||
|
*/
|
||||||
|
public static getColumnIndexFromDate(date: string): number {
|
||||||
|
// Opdater cache hvis tom
|
||||||
|
if (this.columnBoundsCache.length === 0) {
|
||||||
|
this.updateColumnBoundsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnIndex = this.columnBoundsCache.findIndex(col => col.date === date);
|
||||||
|
return columnIndex >= 0 ? columnIndex + 1 : 1; // 1-based index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get column index from X coordinate
|
||||||
|
*/
|
||||||
|
public static getColumnIndexFromX(x: number): number {
|
||||||
|
const date = this.getColumnDateFromX(x);
|
||||||
|
return date ? this.getColumnIndexFromDate(date) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear cache (useful for testing or when DOM structure changes)
|
||||||
|
*/
|
||||||
|
public static clearCache(): void {
|
||||||
|
this.columnBoundsCache = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current cache for debugging
|
||||||
|
*/
|
||||||
|
public static getCache(): ColumnBounds[] {
|
||||||
|
return [...this.columnBoundsCache];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -125,8 +125,6 @@ swp-resize-handle[data-position="bottom"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resize handles controlled by JavaScript - no general hover */
|
/* Resize handles controlled by JavaScript - no general hover */
|
||||||
|
|
||||||
/* Hit area */
|
|
||||||
swp-handle-hitarea {
|
swp-handle-hitarea {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -8px;
|
left: -8px;
|
||||||
|
|
@ -136,14 +134,13 @@ swp-resize-handle[data-position="bottom"] {
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-position="top"] {
|
swp-handle-hitarea[data-position="top"] {
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-position="bottom"] {
|
swp-handle-hitarea[data-position="bottom"] {
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Multi-day events */
|
/* Multi-day events */
|
||||||
swp-multi-day-event {
|
swp-multi-day-event {
|
||||||
|
|
@ -250,3 +247,8 @@ swp-event-group swp-event {
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* All-day event transition for smooth repositioning */
|
||||||
|
swp-allday-container swp-event.transitioning {
|
||||||
|
transition: grid-area 200ms ease-out, grid-row 200ms ease-out, grid-column 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue