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:
parent
4141bffca4
commit
6ccc071587
8 changed files with 262 additions and 377 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||
import {
|
||||
DragStartEventPayload,
|
||||
|
|
@ -16,33 +16,30 @@ import {
|
|||
DragMouseLeaveHeaderEventPayload,
|
||||
DragColumnChangeEventPayload
|
||||
} from '../types/EventTypes';
|
||||
import { MousePosition } from '../types/DragDropTypes';
|
||||
|
||||
interface CachedElements {
|
||||
scrollContainer: HTMLElement | null;
|
||||
currentColumn: HTMLElement | null;
|
||||
lastColumnDate: string | null;
|
||||
}
|
||||
|
||||
interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class DragDropManager {
|
||||
private eventBus: IEventBus;
|
||||
|
||||
// Mouse tracking with optimized state
|
||||
private lastMousePosition: Position = { x: 0, y: 0 };
|
||||
private lastLoggedPosition: Position = { x: 0, y: 0 };
|
||||
private lastMousePosition: MousePosition = { x: 0, y: 0 };
|
||||
private lastLoggedPosition: MousePosition = { x: 0, y: 0 };
|
||||
private currentMouseY = 0;
|
||||
private mouseOffset: Position = { x: 0, y: 0 };
|
||||
private initialMousePosition: Position = { x: 0, y: 0 };
|
||||
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
||||
private initialMousePosition: MousePosition = { x: 0, y: 0 };
|
||||
private lastColumn: ColumnBounds | null = null;
|
||||
|
||||
// Drag state
|
||||
private draggedElement!: HTMLElement | null;
|
||||
private draggedClone!: HTMLElement | null;
|
||||
private currentColumn: string | null = null;
|
||||
private currentColumnBounds: ColumnBounds | null = null;
|
||||
private isDragStarted = false;
|
||||
|
||||
// Header tracking state
|
||||
|
|
@ -51,12 +48,9 @@ export class DragDropManager {
|
|||
// Movement threshold to distinguish click from drag
|
||||
private readonly dragThreshold = 5; // pixels
|
||||
|
||||
private scrollContainer!: HTMLElement | null;
|
||||
// Cached DOM elements for performance
|
||||
private cachedElements: CachedElements = {
|
||||
scrollContainer: null,
|
||||
currentColumn: null,
|
||||
lastColumnDate: null
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -106,7 +100,7 @@ export class DragDropManager {
|
|||
document.body.addEventListener('mousedown', this.boundHandlers.mouseDown);
|
||||
document.body.addEventListener('mouseup', this.boundHandlers.mouseUp);
|
||||
|
||||
// Add mouseleave listener to calendar container for drag cancellation
|
||||
this.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement;
|
||||
const calendarContainer = document.querySelector('swp-calendar-container');
|
||||
if (calendarContainer) {
|
||||
calendarContainer.addEventListener('mouseleave', () => {
|
||||
|
|
@ -133,9 +127,9 @@ export class DragDropManager {
|
|||
|
||||
private handleMouseDown(event: MouseEvent): void {
|
||||
|
||||
// Clean up drag state first
|
||||
this.cleanupDragState();
|
||||
|
||||
// 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 };
|
||||
|
|
@ -160,21 +154,13 @@ export class DragDropManager {
|
|||
// Found an event - prepare for potential dragging
|
||||
if (eventElement) {
|
||||
this.draggedElement = eventElement;
|
||||
|
||||
this.lastColumn = ColumnDetectionUtils.getColumnBounds(this.lastMousePosition)
|
||||
// Calculate mouse offset within event
|
||||
const eventRect = eventElement.getBoundingClientRect();
|
||||
this.mouseOffset = {
|
||||
x: event.clientX - eventRect.left,
|
||||
y: event.clientY - eventRect.top
|
||||
};
|
||||
|
||||
// Detect current column
|
||||
const column = this.detectColumn(event.clientX, event.clientY);
|
||||
if (column) {
|
||||
this.currentColumn = column;
|
||||
}
|
||||
|
||||
// Don't emit drag:start yet - wait for movement threshold
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +177,7 @@ export class DragDropManager {
|
|||
}
|
||||
|
||||
if (event.buttons === 1 && this.draggedElement) {
|
||||
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
||||
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; //TODO: Is this really needed? why not just use event.clientX + Y directly
|
||||
|
||||
// Check if we need to start drag (movement threshold)
|
||||
if (!this.isDragStarted) {
|
||||
|
|
@ -203,20 +189,22 @@ export class DragDropManager {
|
|||
// Start drag - emit drag:start event
|
||||
this.isDragStarted = true;
|
||||
|
||||
// Create SwpEventElement from existing DOM element and clone it
|
||||
const originalSwpEvent = SwpEventElement.fromExistingElement(this.draggedElement);
|
||||
const clonedSwpEvent = originalSwpEvent.createClone();
|
||||
|
||||
// Get the cloned DOM element
|
||||
this.draggedClone = clonedSwpEvent.getElement();
|
||||
// Detect current column
|
||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
|
||||
// Create SwpEventElement from existing DOM element and clone it
|
||||
const originalSwpEvent = SwpEventElement.fromExistingElement(this.draggedElement);
|
||||
const clonedSwpEvent = originalSwpEvent.createClone();
|
||||
|
||||
// Get the cloned DOM element
|
||||
this.draggedClone = clonedSwpEvent.getElement();
|
||||
|
||||
|
||||
const dragStartPayload: DragStartEventPayload = {
|
||||
draggedElement: this.draggedElement,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.initialMousePosition,
|
||||
mouseOffset: this.mouseOffset,
|
||||
column: this.currentColumn
|
||||
columnBounds: this.currentColumnBounds
|
||||
};
|
||||
this.eventBus.emit('drag:start', dragStartPayload);
|
||||
} else {
|
||||
|
|
@ -241,20 +229,21 @@ export class DragDropManager {
|
|||
draggedElement: this.draggedElement,
|
||||
mousePosition: currentPosition,
|
||||
snappedY: positionData.snappedY,
|
||||
column: positionData.column,
|
||||
columnBounds: positionData.column,
|
||||
mouseOffset: this.mouseOffset
|
||||
};
|
||||
this.eventBus.emit('drag:move', dragMovePayload);
|
||||
}
|
||||
|
||||
// Check for auto-scroll
|
||||
this.checkAutoScroll(event);
|
||||
this.checkAutoScroll(currentPosition);
|
||||
|
||||
// Check for column change using cached data
|
||||
const newColumn = this.getColumnFromCache(currentPosition);
|
||||
if (newColumn && newColumn !== this.currentColumn) {
|
||||
const previousColumn = this.currentColumn;
|
||||
this.currentColumn = newColumn;
|
||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
|
||||
if (newColumn && newColumn !== this.currentColumnBounds) {
|
||||
const previousColumn = this.currentColumnBounds;
|
||||
this.currentColumnBounds = newColumn;
|
||||
|
||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||
draggedElement: this.draggedElement,
|
||||
|
|
@ -276,16 +265,10 @@ export class DragDropManager {
|
|||
this.stopAutoScroll();
|
||||
|
||||
if (this.draggedElement) {
|
||||
// Store variables locally before cleanup
|
||||
//const draggedElement = this.draggedElement;
|
||||
const isDragStarted = this.isDragStarted;
|
||||
|
||||
|
||||
|
||||
|
||||
// Only emit drag:end if drag was actually started
|
||||
if (isDragStarted) {
|
||||
const mousePosition: Position = { x: event.clientX, y: event.clientY };
|
||||
if (this.isDragStarted) {
|
||||
const mousePosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Use consolidated position calculation
|
||||
const positionData = this.calculateDragPosition(mousePosition);
|
||||
|
|
@ -298,12 +281,12 @@ export class DragDropManager {
|
|||
finalColumn: positionData.column,
|
||||
finalY: positionData.snappedY,
|
||||
dropTarget: dropTarget,
|
||||
isDragStarted: isDragStarted
|
||||
isDragStarted: this.isDragStarted
|
||||
});
|
||||
|
||||
const dragEndPayload: DragEndEventPayload = {
|
||||
draggedElement: this.draggedElement,
|
||||
draggedClone : this.draggedClone,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition,
|
||||
finalPosition: positionData,
|
||||
target: dropTarget
|
||||
|
|
@ -325,7 +308,7 @@ export class DragDropManager {
|
|||
private cleanupAllClones(): void {
|
||||
// Remove clones from all possible locations
|
||||
const allClones = document.querySelectorAll('[data-event-id^="clone"]');
|
||||
|
||||
|
||||
if (allClones.length > 0) {
|
||||
console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`);
|
||||
allClones.forEach(clone => clone.remove());
|
||||
|
|
@ -365,9 +348,13 @@ export class DragDropManager {
|
|||
/**
|
||||
* Consolidated position calculation method using PositionUtils
|
||||
*/
|
||||
private calculateDragPosition(mousePosition: Position): { column: string | null; snappedY: number } {
|
||||
const column = this.detectColumn(mousePosition.x, mousePosition.y);
|
||||
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||
private calculateDragPosition(mousePosition: MousePosition): { column: ColumnBounds | null; snappedY: number } {
|
||||
let column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||
let snappedY = 0;
|
||||
if (column) {
|
||||
snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||
return { column, snappedY };
|
||||
}
|
||||
|
||||
return { column, snappedY };
|
||||
}
|
||||
|
|
@ -375,100 +362,33 @@ export class DragDropManager {
|
|||
/**
|
||||
* Optimized snap position calculation using PositionUtils
|
||||
*/
|
||||
private calculateSnapPosition(mouseY: number, column: string | null = null): number {
|
||||
const targetColumn = column || this.currentColumn;
|
||||
|
||||
// Use cached column element if available
|
||||
const columnElement = this.getCachedColumnElement(targetColumn);
|
||||
if (!columnElement) return mouseY;
|
||||
|
||||
// Use PositionUtils for consistent snapping behavior
|
||||
const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, columnElement);
|
||||
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
|
||||
const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, column);
|
||||
|
||||
return Math.max(0, snappedY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coordinate-based column detection (replaces DOM traversal)
|
||||
*/
|
||||
private detectColumn(mouseX: number, mouseY: number): string | null {
|
||||
// Brug den koordinatbaserede metode direkte
|
||||
const columnDate = ColumnDetectionUtils.getColumnDateFromX(mouseX);
|
||||
|
||||
// Opdater stadig den eksisterende cache hvis vi finder en kolonne
|
||||
if (columnDate && columnDate !== this.cachedElements.lastColumnDate) {
|
||||
const columnElement = document.querySelector(`swp-day-column[data-date="${columnDate}"]`) as HTMLElement;
|
||||
if (columnElement) {
|
||||
this.cachedElements.currentColumn = columnElement;
|
||||
this.cachedElements.lastColumnDate = columnDate;
|
||||
}
|
||||
}
|
||||
|
||||
return columnDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column from cache or detect new one
|
||||
*/
|
||||
private getColumnFromCache(mousePosition: Position): string | null {
|
||||
// Try to use cached column first
|
||||
if (this.cachedElements.currentColumn && this.cachedElements.lastColumnDate) {
|
||||
const rect = this.cachedElements.currentColumn.getBoundingClientRect();
|
||||
if (mousePosition.x >= rect.left && mousePosition.x <= rect.right) {
|
||||
return this.cachedElements.lastColumnDate;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss - detect new column
|
||||
return this.detectColumn(mousePosition.x, mousePosition.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached column element or query for new one
|
||||
*/
|
||||
private getCachedColumnElement(columnDate: string | null): HTMLElement | null {
|
||||
if (!columnDate) return null;
|
||||
|
||||
// Return cached element if it matches
|
||||
if (this.cachedElements.lastColumnDate === columnDate && this.cachedElements.currentColumn) {
|
||||
return this.cachedElements.currentColumn;
|
||||
}
|
||||
|
||||
// Query for new element and cache it
|
||||
const element = document.querySelector(`swp-day-column[data-date="${columnDate}"]`) as HTMLElement;
|
||||
if (element) {
|
||||
this.cachedElements.currentColumn = element;
|
||||
this.cachedElements.lastColumnDate = columnDate;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized auto-scroll check with cached container
|
||||
*/
|
||||
private checkAutoScroll(event: MouseEvent): void {
|
||||
// Use cached scroll container
|
||||
if (!this.cachedElements.scrollContainer) {
|
||||
this.cachedElements.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement;
|
||||
if (!this.cachedElements.scrollContainer) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
private checkAutoScroll(mousePosition: MousePosition): void {
|
||||
|
||||
const containerRect = this.cachedElements.scrollContainer.getBoundingClientRect();
|
||||
const mouseY = event.clientY;
|
||||
if (this.scrollContainer == null)
|
||||
return;
|
||||
|
||||
const containerRect = this.scrollContainer.getBoundingClientRect();
|
||||
const mouseY = mousePosition.clientY;
|
||||
|
||||
// Calculate distances from edges
|
||||
const distanceFromTop = mouseY - containerRect.top;
|
||||
const distanceFromBottom = containerRect.bottom - mouseY;
|
||||
const distanceFromTop = mousePosition.y - containerRect.top;
|
||||
const distanceFromBottom = containerRect.bottom - mousePosition.y;
|
||||
|
||||
// Check if we need to scroll
|
||||
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
||||
this.startAutoScroll('up');
|
||||
this.startAutoScroll('up', mousePosition);
|
||||
} else if (distanceFromBottom <= this.scrollThreshold && distanceFromBottom > 0) {
|
||||
this.startAutoScroll('down');
|
||||
this.startAutoScroll('down', mousePosition);
|
||||
} else {
|
||||
this.stopAutoScroll();
|
||||
}
|
||||
|
|
@ -477,25 +397,26 @@ export class DragDropManager {
|
|||
/**
|
||||
* Optimized auto-scroll with cached container reference
|
||||
*/
|
||||
private startAutoScroll(direction: 'up' | 'down'): void {
|
||||
private startAutoScroll(direction: 'up' | 'down', event: MousePosition): void {
|
||||
if (this.autoScrollAnimationId !== null) return;
|
||||
|
||||
const scroll = () => {
|
||||
if (!this.cachedElements.scrollContainer || !this.draggedElement) {
|
||||
if (!this.scrollContainer || !this.draggedElement) {
|
||||
this.stopAutoScroll();
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
||||
this.cachedElements.scrollContainer.scrollTop += scrollAmount;
|
||||
this.scrollContainer.scrollTop += scrollAmount;
|
||||
|
||||
// Emit updated position during scroll - adjust for scroll movement
|
||||
if (this.draggedElement) {
|
||||
// During autoscroll, we need to calculate position relative to the scrolled content
|
||||
// The mouse hasn't moved, but the content has scrolled
|
||||
const columnElement = this.getCachedColumnElement(this.currentColumn);
|
||||
const columnElement = ColumnDetectionUtils.getColumnBounds(event);
|
||||
|
||||
if (columnElement) {
|
||||
const columnRect = columnElement.getBoundingClientRect();
|
||||
const columnRect = columnElement.boundingClientRect;
|
||||
// Calculate free position relative to column, accounting for scroll movement (no snapping during scroll)
|
||||
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
||||
const freeY = Math.max(0, relativeY);
|
||||
|
|
@ -503,7 +424,7 @@ export class DragDropManager {
|
|||
this.eventBus.emit('drag:auto-scroll', {
|
||||
draggedElement: this.draggedElement,
|
||||
snappedY: freeY, // Actually free position during scroll
|
||||
scrollTop: this.cachedElements.scrollContainer.scrollTop
|
||||
scrollTop: this.scrollContainer.scrollTop
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -530,20 +451,15 @@ export class DragDropManager {
|
|||
private cleanupDragState(): void {
|
||||
this.draggedElement = null;
|
||||
this.draggedClone = null;
|
||||
this.currentColumn = null;
|
||||
this.isDragStarted = false;
|
||||
this.isInHeader = false;
|
||||
|
||||
// Clear cached elements
|
||||
this.cachedElements.currentColumn = null;
|
||||
this.cachedElements.lastColumnDate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
||||
*/
|
||||
private detectDropTarget(position: Position): 'swp-day-column' | 'swp-day-header' | null {
|
||||
|
||||
private detectDropTarget(position: MousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
||||
|
||||
// Traverse up the DOM tree to find the target container
|
||||
let currentElement = this.draggedClone;
|
||||
while (currentElement && currentElement !== document.body) {
|
||||
|
|
@ -563,6 +479,8 @@ export class DragDropManager {
|
|||
* Check for header enter/leave during drag operations
|
||||
*/
|
||||
private checkHeaderEnterLeave(event: MouseEvent): void {
|
||||
|
||||
let position: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY);
|
||||
if (!elementAtPosition) return;
|
||||
|
||||
|
|
@ -575,17 +493,17 @@ export class DragDropManager {
|
|||
this.isInHeader = true;
|
||||
|
||||
// Calculate target date using existing method
|
||||
const targetDate = ColumnDetectionUtils.getColumnDateFromX(event.clientX);
|
||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
|
||||
if (targetDate) {
|
||||
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate });
|
||||
if (targetColumn) {
|
||||
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate: targetColumn });
|
||||
|
||||
// Find clone element (if it exists)
|
||||
const eventId = this.draggedElement?.dataset.eventId;
|
||||
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||
|
||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||
targetDate,
|
||||
targetColumn: targetColumn,
|
||||
mousePosition: { x: event.clientX, y: event.clientY },
|
||||
originalElement: this.draggedElement,
|
||||
cloneElement: cloneElement
|
||||
|
|
@ -601,14 +519,18 @@ export class DragDropManager {
|
|||
console.log('🚪 DragDropManager: Emitting drag:mouseleave-header');
|
||||
|
||||
// Calculate target date using existing method
|
||||
const targetDate = ColumnDetectionUtils.getColumnDateFromX(event.clientX);
|
||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
if (!targetColumn) {
|
||||
console.warn("No column detected, unknown reason");
|
||||
return;
|
||||
|
||||
}
|
||||
// Find clone element (if it exists)
|
||||
const eventId = this.draggedElement?.dataset.eventId;
|
||||
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||
|
||||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
||||
targetDate,
|
||||
targetDate: targetColumn.date,
|
||||
mousePosition: { x: event.clientX, y: event.clientY },
|
||||
originalElement: this.draggedElement,
|
||||
cloneElement: cloneElement
|
||||
|
|
@ -628,11 +550,6 @@ export class DragDropManager {
|
|||
document.body.removeEventListener('mousedown', this.boundHandlers.mouseDown);
|
||||
document.body.removeEventListener('mouseup', this.boundHandlers.mouseUp);
|
||||
|
||||
// Clear all cached elements
|
||||
this.cachedElements.scrollContainer = null;
|
||||
this.cachedElements.currentColumn = null;
|
||||
this.cachedElements.lastColumnDate = null;
|
||||
|
||||
// Clean up drag state
|
||||
this.cleanupDragState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export class HeaderManager {
|
|||
|
||||
// Create and store event listeners
|
||||
this.dragMouseEnterHeaderListener = (event: Event) => {
|
||||
const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
||||
const { targetColumn: targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
||||
|
||||
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
|
||||
targetDate,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue