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 { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||||
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import {
|
import {
|
||||||
DragMouseEnterHeaderEventPayload,
|
DragMouseEnterHeaderEventPayload,
|
||||||
|
|
@ -22,7 +22,7 @@ import { DragOffset, MousePosition } from '../types/DragDropTypes';
|
||||||
export class AllDayManager {
|
export class AllDayManager {
|
||||||
private allDayEventRenderer: AllDayEventRenderer;
|
private allDayEventRenderer: AllDayEventRenderer;
|
||||||
private layoutEngine: AllDayLayoutEngine | null = null;
|
private layoutEngine: AllDayLayoutEngine | null = null;
|
||||||
|
|
||||||
// State tracking for differential updates
|
// State tracking for differential updates
|
||||||
private currentLayouts: Map<string, string> = new Map();
|
private currentLayouts: Map<string, string> = new Map();
|
||||||
private currentAllDayEvents: CalendarEvent[] = [];
|
private currentAllDayEvents: CalendarEvent[] = [];
|
||||||
|
|
@ -38,16 +38,16 @@ export class AllDayManager {
|
||||||
*/
|
*/
|
||||||
private setupEventListeners(): void {
|
private setupEventListeners(): void {
|
||||||
eventBus.on('drag:mouseenter-header', (event) => {
|
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', {
|
console.log('🔄 AllDayManager: Received drag:mouseenter-header', {
|
||||||
targetDate,
|
targetDate: targetColumnBounds,
|
||||||
originalElementId: originalElement?.dataset?.eventId,
|
originalElementId: originalElement?.dataset?.eventId,
|
||||||
originalElementTag: originalElement?.tagName
|
originalElementTag: originalElement?.tagName
|
||||||
});
|
});
|
||||||
|
|
||||||
if (targetDate && cloneElement) {
|
if (targetColumnBounds && cloneElement) {
|
||||||
this.handleConvertToAllDay(targetDate, cloneElement);
|
this.handleConvertToAllDay(targetColumnBounds, cloneElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
|
|
@ -78,8 +78,8 @@ export class AllDayManager {
|
||||||
|
|
||||||
eventBus.on('drag:column-change', (event) => {
|
eventBus.on('drag:column-change', (event) => {
|
||||||
const { draggedElement, draggedClone, mousePosition } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
const { draggedElement, draggedClone, mousePosition } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
if(draggedClone == null)
|
if (draggedClone == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Filter: Only handle events where clone IS an all-day event
|
// 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', {
|
console.log('🔄 AllDayManager: Handling drag:column-change for all-day event', {
|
||||||
eventId : draggedElement.dataset.eventId,
|
eventId: draggedElement.dataset.eventId,
|
||||||
cloneId: draggedClone.dataset.eventId
|
cloneId: draggedClone.dataset.eventId
|
||||||
});
|
});
|
||||||
|
|
||||||
this.handleColumnChange(draggedClone, mousePosition);
|
this.handleColumnChange(draggedClone, mousePosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.on('drag:end', (event) => {
|
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;
|
return;
|
||||||
|
|
||||||
const eventId = draggedElement.dataset.eventId;
|
this.handleDragEnd(draggedElement);
|
||||||
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 });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for drag cancellation to recalculate height
|
// Listen for drag cancellation to recalculate height
|
||||||
|
|
@ -273,7 +266,7 @@ export class AllDayManager {
|
||||||
this.currentLayouts.clear();
|
this.currentLayouts.clear();
|
||||||
const container = this.getAllDayContainer();
|
const container = this.getAllDayContainer();
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
container.querySelectorAll('swp-event').forEach(element => {
|
container.querySelectorAll('swp-event').forEach(element => {
|
||||||
const htmlElement = element as HTMLElement;
|
const htmlElement = element as HTMLElement;
|
||||||
const eventId = htmlElement.dataset.eventId;
|
const eventId = htmlElement.dataset.eventId;
|
||||||
|
|
@ -282,7 +275,7 @@ export class AllDayManager {
|
||||||
this.currentLayouts.set(eventId, gridArea);
|
this.currentLayouts.set(eventId, gridArea);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('📋 AllDayManager: Stored current layouts', {
|
console.log('📋 AllDayManager: Stored current layouts', {
|
||||||
count: this.currentLayouts.size,
|
count: this.currentLayouts.size,
|
||||||
layouts: Array.from(this.currentLayouts.entries())
|
layouts: Array.from(this.currentLayouts.entries())
|
||||||
|
|
@ -295,7 +288,7 @@ export class AllDayManager {
|
||||||
public setCurrentEvents(events: CalendarEvent[], weekDates: string[]): void {
|
public setCurrentEvents(events: CalendarEvent[], weekDates: string[]): void {
|
||||||
this.currentAllDayEvents = events;
|
this.currentAllDayEvents = events;
|
||||||
this.currentWeekDates = weekDates;
|
this.currentWeekDates = weekDates;
|
||||||
|
|
||||||
console.log('📝 AllDayManager: Set current events', {
|
console.log('📝 AllDayManager: Set current events', {
|
||||||
eventCount: events.length,
|
eventCount: events.length,
|
||||||
weekDatesCount: weekDates.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
|
* This is the correct method that processes all events together for proper overlap detection
|
||||||
*/
|
*/
|
||||||
public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
|
public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
|
||||||
|
|
||||||
// Store current state
|
// Store current state
|
||||||
this.currentAllDayEvents = events;
|
this.currentAllDayEvents = events;
|
||||||
this.currentWeekDates = weekDates;
|
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);
|
||||||
|
|
||||||
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
|
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
|
||||||
return this.layoutEngine.calculateLayout(events);
|
return this.layoutEngine.calculateLayout(events);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -325,22 +318,20 @@ export class AllDayManager {
|
||||||
* Handle conversion of timed event to all-day event - SIMPLIFIED
|
* Handle conversion of timed event to all-day event - SIMPLIFIED
|
||||||
* During drag: Place in row 1 only, calculate column from targetDate
|
* 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)', {
|
console.log('🔄 AllDayManager: Converting to all-day (row 1 only during drag)', {
|
||||||
eventId: cloneElement.dataset.eventId,
|
eventId: cloneElement.dataset.eventId,
|
||||||
targetDate
|
targetDate: targetColumnBounds
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all-day container, request creation if needed
|
// Get all-day container, request creation if needed
|
||||||
let allDayContainer = this.getAllDayContainer();
|
let allDayContainer = this.getAllDayContainer();
|
||||||
|
|
||||||
// Calculate target column from targetDate using ColumnDetectionUtils
|
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnIndexFromDate(targetDate);
|
|
||||||
|
|
||||||
cloneElement.removeAttribute('style');
|
cloneElement.removeAttribute('style');
|
||||||
cloneElement.classList.add('all-day-style');
|
cloneElement.classList.add('all-day-style');
|
||||||
cloneElement.style.gridRow = '1';
|
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
|
cloneElement.dataset.allday = 'true'; // Set the all-day attribute for filtering
|
||||||
|
|
||||||
// Add to container
|
// Add to container
|
||||||
|
|
@ -348,7 +339,7 @@ export class AllDayManager {
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
|
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
|
||||||
eventId: cloneElement.dataset.eventId,
|
eventId: cloneElement.dataset.eventId,
|
||||||
gridColumn: targetColumn,
|
gridColumn: targetColumnBounds,
|
||||||
gridRow: 1
|
gridRow: 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -398,13 +389,16 @@ export class AllDayManager {
|
||||||
if (!allDayContainer) return;
|
if (!allDayContainer) return;
|
||||||
|
|
||||||
// Calculate target column using ColumnDetectionUtils
|
// 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
|
// Update clone position - ALWAYS keep in row 1 during drag
|
||||||
// Use simple grid positioning that matches all-day container structure
|
// 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.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', {
|
console.log('🔄 AllDayManager: Updated all-day drag clone position', {
|
||||||
eventId: dragClone.dataset.eventId,
|
eventId: dragClone.dataset.eventId,
|
||||||
|
|
@ -418,31 +412,38 @@ export class AllDayManager {
|
||||||
/**
|
/**
|
||||||
* Handle drag end for all-day events - WITH DIFFERENTIAL UPDATES
|
* 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', {
|
console.log('🎯 AllDayManager: Starting drag end with differential updates', {
|
||||||
eventId: dragClone.dataset.eventId,
|
dragEndEvent
|
||||||
finalColumn: finalPosition.column
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (dragEndEvent.draggedClone == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// 1. Store current layouts BEFORE any changes
|
// 1. Store current layouts BEFORE any changes
|
||||||
this.storeCurrentLayouts();
|
this.storeCurrentLayouts();
|
||||||
|
|
||||||
// 2. Normalize clone ID
|
// 2. Normalize clone ID
|
||||||
const cloneId = dragClone.dataset.eventId;
|
const cloneId = dragEndEvent.draggedClone?.dataset.eventId;
|
||||||
if (cloneId?.startsWith('clone-')) {
|
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
|
// 3. Create temporary array with existing events + the dropped event
|
||||||
const droppedEventId = dragClone.dataset.eventId || '';
|
let eventId = dragEndEvent.draggedClone.dataset.eventId;
|
||||||
const droppedEventDate = dragClone.dataset.allDayDate || finalPosition.column;
|
let eventDate = dragEndEvent.finalPosition.column?.date;
|
||||||
|
let eventType = dragEndEvent.draggedClone.dataset.type;
|
||||||
|
|
||||||
|
if (eventDate == null || eventId == null || eventType == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
const droppedEvent: CalendarEvent = {
|
const droppedEvent: CalendarEvent = {
|
||||||
id: droppedEventId,
|
id: eventId,
|
||||||
title: dragClone.dataset.title || dragClone.textContent || '',
|
title: dragEndEvent.draggedClone.dataset.title || dragEndEvent.draggedClone.textContent || '',
|
||||||
start: new Date(droppedEventDate),
|
start: new Date(eventDate),
|
||||||
end: new Date(droppedEventDate),
|
end: new Date(eventDate),
|
||||||
type: 'work',
|
type: eventType,
|
||||||
allDay: true,
|
allDay: true,
|
||||||
syncStatus: 'synced'
|
syncStatus: 'synced'
|
||||||
};
|
};
|
||||||
|
|
@ -477,13 +478,13 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. Clean up drag styles from the dropped clone
|
// 6. Clean up drag styles from the dropped clone
|
||||||
dragClone.classList.remove('dragging');
|
dragEndEvent.draggedClone.classList.remove('dragging');
|
||||||
dragClone.style.zIndex = '';
|
dragEndEvent.draggedClone.style.zIndex = '';
|
||||||
dragClone.style.cursor = '';
|
dragEndEvent.draggedClone.style.cursor = '';
|
||||||
dragClone.style.opacity = '';
|
dragEndEvent.draggedClone.style.opacity = '';
|
||||||
|
|
||||||
// 7. Restore original element opacity
|
// 7. Restore original element opacity
|
||||||
originalElement.style.opacity = '';
|
//originalElement.style.opacity = '';
|
||||||
|
|
||||||
// 8. Check if height adjustment is needed
|
// 8. Check if height adjustment is needed
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
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 { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import {
|
import {
|
||||||
DragStartEventPayload,
|
DragStartEventPayload,
|
||||||
|
|
@ -16,33 +16,30 @@ import {
|
||||||
DragMouseLeaveHeaderEventPayload,
|
DragMouseLeaveHeaderEventPayload,
|
||||||
DragColumnChangeEventPayload
|
DragColumnChangeEventPayload
|
||||||
} from '../types/EventTypes';
|
} from '../types/EventTypes';
|
||||||
|
import { MousePosition } from '../types/DragDropTypes';
|
||||||
|
|
||||||
interface CachedElements {
|
interface CachedElements {
|
||||||
scrollContainer: HTMLElement | null;
|
scrollContainer: HTMLElement | null;
|
||||||
currentColumn: HTMLElement | null;
|
|
||||||
lastColumnDate: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Position {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class DragDropManager {
|
export class DragDropManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
|
|
||||||
// Mouse tracking with optimized state
|
// Mouse tracking with optimized state
|
||||||
private lastMousePosition: Position = { x: 0, y: 0 };
|
private lastMousePosition: MousePosition = { x: 0, y: 0 };
|
||||||
private lastLoggedPosition: Position = { x: 0, y: 0 };
|
private lastLoggedPosition: MousePosition = { x: 0, y: 0 };
|
||||||
private currentMouseY = 0;
|
private currentMouseY = 0;
|
||||||
private mouseOffset: Position = { x: 0, y: 0 };
|
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
||||||
private initialMousePosition: Position = { x: 0, y: 0 };
|
private initialMousePosition: MousePosition = { x: 0, y: 0 };
|
||||||
|
private lastColumn: ColumnBounds | null = null;
|
||||||
|
|
||||||
// Drag state
|
// Drag state
|
||||||
private draggedElement!: HTMLElement | null;
|
private draggedElement!: HTMLElement | null;
|
||||||
private draggedClone!: HTMLElement | null;
|
private draggedClone!: HTMLElement | null;
|
||||||
private currentColumn: string | null = null;
|
private currentColumnBounds: ColumnBounds | null = null;
|
||||||
private isDragStarted = false;
|
private isDragStarted = false;
|
||||||
|
|
||||||
// Header tracking state
|
// Header tracking state
|
||||||
|
|
@ -51,12 +48,9 @@ export class DragDropManager {
|
||||||
// Movement threshold to distinguish click from drag
|
// Movement threshold to distinguish click from drag
|
||||||
private readonly dragThreshold = 5; // pixels
|
private readonly dragThreshold = 5; // pixels
|
||||||
|
|
||||||
|
private scrollContainer!: HTMLElement | null;
|
||||||
// Cached DOM elements for performance
|
// 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('mousedown', this.boundHandlers.mouseDown);
|
||||||
document.body.addEventListener('mouseup', this.boundHandlers.mouseUp);
|
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');
|
const calendarContainer = document.querySelector('swp-calendar-container');
|
||||||
if (calendarContainer) {
|
if (calendarContainer) {
|
||||||
calendarContainer.addEventListener('mouseleave', () => {
|
calendarContainer.addEventListener('mouseleave', () => {
|
||||||
|
|
@ -133,9 +127,9 @@ export class DragDropManager {
|
||||||
|
|
||||||
private handleMouseDown(event: MouseEvent): void {
|
private handleMouseDown(event: MouseEvent): void {
|
||||||
|
|
||||||
// Clean up drag state first
|
// Clean up drag state first
|
||||||
this.cleanupDragState();
|
this.cleanupDragState();
|
||||||
|
|
||||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
||||||
this.initialMousePosition = { 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
|
// Found an event - prepare for potential dragging
|
||||||
if (eventElement) {
|
if (eventElement) {
|
||||||
this.draggedElement = eventElement;
|
this.draggedElement = eventElement;
|
||||||
|
this.lastColumn = ColumnDetectionUtils.getColumnBounds(this.lastMousePosition)
|
||||||
// Calculate mouse offset within event
|
// Calculate mouse offset within event
|
||||||
const eventRect = eventElement.getBoundingClientRect();
|
const eventRect = eventElement.getBoundingClientRect();
|
||||||
this.mouseOffset = {
|
this.mouseOffset = {
|
||||||
x: event.clientX - eventRect.left,
|
x: event.clientX - eventRect.left,
|
||||||
y: event.clientY - eventRect.top
|
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) {
|
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)
|
// Check if we need to start drag (movement threshold)
|
||||||
if (!this.isDragStarted) {
|
if (!this.isDragStarted) {
|
||||||
|
|
@ -203,20 +189,22 @@ export class DragDropManager {
|
||||||
// Start drag - emit drag:start event
|
// Start drag - emit drag:start event
|
||||||
this.isDragStarted = true;
|
this.isDragStarted = true;
|
||||||
|
|
||||||
// Create SwpEventElement from existing DOM element and clone it
|
// Detect current column
|
||||||
const originalSwpEvent = SwpEventElement.fromExistingElement(this.draggedElement);
|
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
const clonedSwpEvent = originalSwpEvent.createClone();
|
|
||||||
|
// Create SwpEventElement from existing DOM element and clone it
|
||||||
// Get the cloned DOM element
|
const originalSwpEvent = SwpEventElement.fromExistingElement(this.draggedElement);
|
||||||
this.draggedClone = clonedSwpEvent.getElement();
|
const clonedSwpEvent = originalSwpEvent.createClone();
|
||||||
|
|
||||||
|
// Get the cloned DOM element
|
||||||
|
this.draggedClone = clonedSwpEvent.getElement();
|
||||||
|
|
||||||
|
|
||||||
const dragStartPayload: DragStartEventPayload = {
|
const dragStartPayload: DragStartEventPayload = {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition: this.initialMousePosition,
|
mousePosition: this.initialMousePosition,
|
||||||
mouseOffset: this.mouseOffset,
|
mouseOffset: this.mouseOffset,
|
||||||
column: this.currentColumn
|
columnBounds: this.currentColumnBounds
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:start', dragStartPayload);
|
this.eventBus.emit('drag:start', dragStartPayload);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -241,20 +229,21 @@ export class DragDropManager {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
mousePosition: currentPosition,
|
mousePosition: currentPosition,
|
||||||
snappedY: positionData.snappedY,
|
snappedY: positionData.snappedY,
|
||||||
column: positionData.column,
|
columnBounds: positionData.column,
|
||||||
mouseOffset: this.mouseOffset
|
mouseOffset: this.mouseOffset
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:move', dragMovePayload);
|
this.eventBus.emit('drag:move', dragMovePayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for auto-scroll
|
// Check for auto-scroll
|
||||||
this.checkAutoScroll(event);
|
this.checkAutoScroll(currentPosition);
|
||||||
|
|
||||||
// Check for column change using cached data
|
// Check for column change using cached data
|
||||||
const newColumn = this.getColumnFromCache(currentPosition);
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
if (newColumn && newColumn !== this.currentColumn) {
|
|
||||||
const previousColumn = this.currentColumn;
|
if (newColumn && newColumn !== this.currentColumnBounds) {
|
||||||
this.currentColumn = newColumn;
|
const previousColumn = this.currentColumnBounds;
|
||||||
|
this.currentColumnBounds = newColumn;
|
||||||
|
|
||||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
|
|
@ -276,16 +265,10 @@ export class DragDropManager {
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
|
|
||||||
if (this.draggedElement) {
|
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
|
// Only emit drag:end if drag was actually started
|
||||||
if (isDragStarted) {
|
if (this.isDragStarted) {
|
||||||
const mousePosition: Position = { x: event.clientX, y: event.clientY };
|
const mousePosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Use consolidated position calculation
|
// Use consolidated position calculation
|
||||||
const positionData = this.calculateDragPosition(mousePosition);
|
const positionData = this.calculateDragPosition(mousePosition);
|
||||||
|
|
@ -298,12 +281,12 @@ export class DragDropManager {
|
||||||
finalColumn: positionData.column,
|
finalColumn: positionData.column,
|
||||||
finalY: positionData.snappedY,
|
finalY: positionData.snappedY,
|
||||||
dropTarget: dropTarget,
|
dropTarget: dropTarget,
|
||||||
isDragStarted: isDragStarted
|
isDragStarted: this.isDragStarted
|
||||||
});
|
});
|
||||||
|
|
||||||
const dragEndPayload: DragEndEventPayload = {
|
const dragEndPayload: DragEndEventPayload = {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
draggedClone : this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition,
|
mousePosition,
|
||||||
finalPosition: positionData,
|
finalPosition: positionData,
|
||||||
target: dropTarget
|
target: dropTarget
|
||||||
|
|
@ -325,7 +308,7 @@ export class DragDropManager {
|
||||||
private cleanupAllClones(): void {
|
private cleanupAllClones(): void {
|
||||||
// Remove clones from all possible locations
|
// Remove clones from all possible locations
|
||||||
const allClones = document.querySelectorAll('[data-event-id^="clone"]');
|
const allClones = document.querySelectorAll('[data-event-id^="clone"]');
|
||||||
|
|
||||||
if (allClones.length > 0) {
|
if (allClones.length > 0) {
|
||||||
console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`);
|
console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`);
|
||||||
allClones.forEach(clone => clone.remove());
|
allClones.forEach(clone => clone.remove());
|
||||||
|
|
@ -365,9 +348,13 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Consolidated position calculation method using PositionUtils
|
* Consolidated position calculation method using PositionUtils
|
||||||
*/
|
*/
|
||||||
private calculateDragPosition(mousePosition: Position): { column: string | null; snappedY: number } {
|
private calculateDragPosition(mousePosition: MousePosition): { column: ColumnBounds | null; snappedY: number } {
|
||||||
const column = this.detectColumn(mousePosition.x, mousePosition.y);
|
let column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||||
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
let snappedY = 0;
|
||||||
|
if (column) {
|
||||||
|
snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||||
|
return { column, snappedY };
|
||||||
|
}
|
||||||
|
|
||||||
return { column, snappedY };
|
return { column, snappedY };
|
||||||
}
|
}
|
||||||
|
|
@ -375,100 +362,33 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Optimized snap position calculation using PositionUtils
|
* Optimized snap position calculation using PositionUtils
|
||||||
*/
|
*/
|
||||||
private calculateSnapPosition(mouseY: number, column: string | null = null): number {
|
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
|
||||||
const targetColumn = column || this.currentColumn;
|
const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, column);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
return Math.max(0, snappedY);
|
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
|
* Optimized auto-scroll check with cached container
|
||||||
*/
|
*/
|
||||||
private checkAutoScroll(event: MouseEvent): void {
|
private checkAutoScroll(mousePosition: MousePosition): void {
|
||||||
// Use cached scroll container
|
|
||||||
if (!this.cachedElements.scrollContainer) {
|
|
||||||
this.cachedElements.scrollContainer = document.querySelector('swp-scrollable-content') as HTMLElement;
|
|
||||||
if (!this.cachedElements.scrollContainer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerRect = this.cachedElements.scrollContainer.getBoundingClientRect();
|
if (this.scrollContainer == null)
|
||||||
const mouseY = event.clientY;
|
return;
|
||||||
|
|
||||||
|
const containerRect = this.scrollContainer.getBoundingClientRect();
|
||||||
|
const mouseY = mousePosition.clientY;
|
||||||
|
|
||||||
// Calculate distances from edges
|
// Calculate distances from edges
|
||||||
const distanceFromTop = mouseY - containerRect.top;
|
const distanceFromTop = mousePosition.y - containerRect.top;
|
||||||
const distanceFromBottom = containerRect.bottom - mouseY;
|
const distanceFromBottom = containerRect.bottom - mousePosition.y;
|
||||||
|
|
||||||
// Check if we need to scroll
|
// Check if we need to scroll
|
||||||
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
||||||
this.startAutoScroll('up');
|
this.startAutoScroll('up', mousePosition);
|
||||||
} else if (distanceFromBottom <= this.scrollThreshold && distanceFromBottom > 0) {
|
} else if (distanceFromBottom <= this.scrollThreshold && distanceFromBottom > 0) {
|
||||||
this.startAutoScroll('down');
|
this.startAutoScroll('down', mousePosition);
|
||||||
} else {
|
} else {
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
}
|
}
|
||||||
|
|
@ -477,25 +397,26 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Optimized auto-scroll with cached container reference
|
* 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;
|
if (this.autoScrollAnimationId !== null) return;
|
||||||
|
|
||||||
const scroll = () => {
|
const scroll = () => {
|
||||||
if (!this.cachedElements.scrollContainer || !this.draggedElement) {
|
if (!this.scrollContainer || !this.draggedElement) {
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
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
|
// Emit updated position during scroll - adjust for scroll movement
|
||||||
if (this.draggedElement) {
|
if (this.draggedElement) {
|
||||||
// During autoscroll, we need to calculate position relative to the scrolled content
|
// During autoscroll, we need to calculate position relative to the scrolled content
|
||||||
// The mouse hasn't moved, but the content has scrolled
|
// The mouse hasn't moved, but the content has scrolled
|
||||||
const columnElement = this.getCachedColumnElement(this.currentColumn);
|
const columnElement = ColumnDetectionUtils.getColumnBounds(event);
|
||||||
|
|
||||||
if (columnElement) {
|
if (columnElement) {
|
||||||
const columnRect = columnElement.getBoundingClientRect();
|
const columnRect = columnElement.boundingClientRect;
|
||||||
// Calculate free position relative to column, accounting for scroll movement (no snapping during scroll)
|
// Calculate free position relative to column, accounting for scroll movement (no snapping during scroll)
|
||||||
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
||||||
const freeY = Math.max(0, relativeY);
|
const freeY = Math.max(0, relativeY);
|
||||||
|
|
@ -503,7 +424,7 @@ export class DragDropManager {
|
||||||
this.eventBus.emit('drag:auto-scroll', {
|
this.eventBus.emit('drag:auto-scroll', {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
snappedY: freeY, // Actually free position during scroll
|
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 {
|
private cleanupDragState(): void {
|
||||||
this.draggedElement = null;
|
this.draggedElement = null;
|
||||||
this.draggedClone = null;
|
this.draggedClone = null;
|
||||||
this.currentColumn = null;
|
|
||||||
this.isDragStarted = false;
|
this.isDragStarted = false;
|
||||||
this.isInHeader = 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
|
* 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
|
// Traverse up the DOM tree to find the target container
|
||||||
let currentElement = this.draggedClone;
|
let currentElement = this.draggedClone;
|
||||||
while (currentElement && currentElement !== document.body) {
|
while (currentElement && currentElement !== document.body) {
|
||||||
|
|
@ -563,6 +479,8 @@ export class DragDropManager {
|
||||||
* Check for header enter/leave during drag operations
|
* Check for header enter/leave during drag operations
|
||||||
*/
|
*/
|
||||||
private checkHeaderEnterLeave(event: MouseEvent): void {
|
private checkHeaderEnterLeave(event: MouseEvent): void {
|
||||||
|
|
||||||
|
let position: MousePosition = { x: event.clientX, y: event.clientY };
|
||||||
const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY);
|
const elementAtPosition = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
if (!elementAtPosition) return;
|
if (!elementAtPosition) return;
|
||||||
|
|
||||||
|
|
@ -575,17 +493,17 @@ export class DragDropManager {
|
||||||
this.isInHeader = true;
|
this.isInHeader = true;
|
||||||
|
|
||||||
// Calculate target date using existing method
|
// Calculate target date using existing method
|
||||||
const targetDate = ColumnDetectionUtils.getColumnDateFromX(event.clientX);
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||||
|
|
||||||
if (targetDate) {
|
if (targetColumn) {
|
||||||
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate });
|
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate: targetColumn });
|
||||||
|
|
||||||
// Find clone element (if it exists)
|
// Find clone element (if it exists)
|
||||||
const eventId = this.draggedElement?.dataset.eventId;
|
const eventId = this.draggedElement?.dataset.eventId;
|
||||||
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||||
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||||
targetDate,
|
targetColumn: targetColumn,
|
||||||
mousePosition: { x: event.clientX, y: event.clientY },
|
mousePosition: { x: event.clientX, y: event.clientY },
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.draggedElement,
|
||||||
cloneElement: cloneElement
|
cloneElement: cloneElement
|
||||||
|
|
@ -601,14 +519,18 @@ 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 = 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)
|
// Find clone element (if it exists)
|
||||||
const eventId = this.draggedElement?.dataset.eventId;
|
const eventId = this.draggedElement?.dataset.eventId;
|
||||||
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
const cloneElement = document.querySelector(`[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||||
|
|
||||||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
||||||
targetDate,
|
targetDate: targetColumn.date,
|
||||||
mousePosition: { x: event.clientX, y: event.clientY },
|
mousePosition: { x: event.clientX, y: event.clientY },
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.draggedElement,
|
||||||
cloneElement: cloneElement
|
cloneElement: cloneElement
|
||||||
|
|
@ -628,11 +550,6 @@ export class DragDropManager {
|
||||||
document.body.removeEventListener('mousedown', this.boundHandlers.mouseDown);
|
document.body.removeEventListener('mousedown', this.boundHandlers.mouseDown);
|
||||||
document.body.removeEventListener('mouseup', this.boundHandlers.mouseUp);
|
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
|
// Clean up drag state
|
||||||
this.cleanupDragState();
|
this.cleanupDragState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export class HeaderManager {
|
||||||
|
|
||||||
// Create and store event listeners
|
// Create and store event listeners
|
||||||
this.dragMouseEnterHeaderListener = (event: Event) => {
|
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', {
|
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
|
||||||
targetDate,
|
targetDate,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import { TimeFormatter } from '../utils/TimeFormatter';
|
import { TimeFormatter } from '../utils/TimeFormatter';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { DragOffset, StackLinkData } from '../types/DragDropTypes';
|
import { DragOffset, StackLinkData } from '../types/DragDropTypes';
|
||||||
|
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||||
|
import { DragColumnChangeEventPayload, DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for event rendering strategies
|
* Interface for event rendering strategies
|
||||||
|
|
@ -16,12 +18,12 @@ import { DragOffset, StackLinkData } from '../types/DragDropTypes';
|
||||||
export interface EventRendererStrategy {
|
export interface EventRendererStrategy {
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
|
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
|
||||||
clearEvents(container?: HTMLElement): void;
|
clearEvents(container?: HTMLElement): void;
|
||||||
handleDragStart?(payload: import('../types/EventTypes').DragStartEventPayload): void;
|
handleDragStart?(payload: DragStartEventPayload): void;
|
||||||
handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: DragOffset): void;
|
handleDragMove?(payload: DragMoveEventPayload): void;
|
||||||
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
||||||
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void;
|
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void;
|
||||||
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
||||||
handleColumnChange?(eventId: string, newColumn: string): void;
|
handleColumnChange?(payload: DragColumnChangeEventPayload): void;
|
||||||
handleNavigationCompleted?(): void;
|
handleNavigationCompleted?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,13 +162,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
/**
|
/**
|
||||||
* Handle drag start event
|
* Handle drag start event
|
||||||
*/
|
*/
|
||||||
public handleDragStart(payload: import('../types/EventTypes').DragStartEventPayload): void {
|
public handleDragStart(payload: DragStartEventPayload): void {
|
||||||
const originalElement = payload.draggedElement;
|
|
||||||
const eventId = originalElement.dataset.eventId || '';
|
this.originalEvent = payload.draggedElement;;
|
||||||
const mouseOffset = payload.mouseOffset;
|
|
||||||
const column = payload.column || '';
|
|
||||||
|
|
||||||
this.originalEvent = originalElement;
|
|
||||||
|
|
||||||
// Use the clone from the payload instead of creating a new one
|
// Use the clone from the payload instead of creating a new one
|
||||||
this.draggedClone = payload.draggedClone;
|
this.draggedClone = payload.draggedClone;
|
||||||
|
|
@ -176,35 +174,29 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
this.applyDragStyling(this.draggedClone);
|
this.applyDragStyling(this.draggedClone);
|
||||||
|
|
||||||
// Add to current column's events layer (not directly to column)
|
// Add to current column's events layer (not directly to column)
|
||||||
const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`);
|
const eventsLayer = payload.columnBounds?.element.querySelector('swp-events-layer');
|
||||||
if (columnElement) {
|
if (eventsLayer) {
|
||||||
const eventsLayer = columnElement.querySelector('swp-events-layer');
|
eventsLayer.appendChild(this.draggedClone);
|
||||||
if (eventsLayer) {
|
|
||||||
eventsLayer.appendChild(this.draggedClone);
|
|
||||||
} else {
|
|
||||||
// Fallback to column if events layer not found
|
|
||||||
columnElement.appendChild(this.draggedClone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make original semi-transparent
|
// Make original semi-transparent
|
||||||
originalElement.style.opacity = '0.3';
|
this.originalEvent.style.opacity = '0.3';
|
||||||
originalElement.style.userSelect = 'none';
|
this.originalEvent.style.userSelect = 'none';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag move event
|
* Handle drag move event
|
||||||
*/
|
*/
|
||||||
public handleDragMove(eventId: string, snappedY: number, column: string, mouseOffset: DragOffset): void {
|
public handleDragMove(payload: DragMoveEventPayload): void {
|
||||||
if (!this.draggedClone) return;
|
if (!this.draggedClone) return;
|
||||||
|
|
||||||
// Update position
|
// Update position
|
||||||
this.draggedClone.style.top = snappedY + 'px';
|
this.draggedClone.style.top = payload.snappedY + 'px';
|
||||||
|
|
||||||
// Update timestamp display
|
// Update timestamp display
|
||||||
this.updateCloneTimestamp(this.draggedClone, snappedY);
|
this.updateCloneTimestamp(this.draggedClone, payload.snappedY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,26 +216,20 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
/**
|
/**
|
||||||
* Handle column change during drag
|
* Handle column change during drag
|
||||||
*/
|
*/
|
||||||
public handleColumnChange(eventId: string, newColumn: string): void {
|
public handleColumnChange(dragColumnChangeEvent: DragColumnChangeEventPayload): void {
|
||||||
if (!this.draggedClone) return;
|
if (!this.draggedClone) return;
|
||||||
|
|
||||||
// Move clone to new column's events layer
|
const eventsLayer = dragColumnChangeEvent.newColumn.element.querySelector('swp-events-layer');
|
||||||
const newColumnElement = document.querySelector(`swp-day-column[data-date="${newColumn}"]`);
|
if (eventsLayer && this.draggedClone.parentElement !== eventsLayer) {
|
||||||
if (newColumnElement) {
|
eventsLayer.appendChild(this.draggedClone);
|
||||||
const eventsLayer = newColumnElement.querySelector('swp-events-layer');
|
|
||||||
if (eventsLayer && this.draggedClone.parentElement !== eventsLayer) {
|
|
||||||
eventsLayer.appendChild(this.draggedClone);
|
|
||||||
} else if (!eventsLayer && this.draggedClone.parentElement !== newColumnElement) {
|
|
||||||
// Fallback to column if events layer not found
|
|
||||||
newColumnElement.appendChild(this.draggedClone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag end event
|
* Handle drag end event
|
||||||
*/
|
*/
|
||||||
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void {
|
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void {
|
||||||
|
|
||||||
if (!draggedClone || !originalElement) {
|
if (!draggedClone || !originalElement) {
|
||||||
console.warn('Missing draggedClone or originalElement');
|
console.warn('Missing draggedClone or originalElement');
|
||||||
|
|
@ -398,11 +384,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
/**
|
/**
|
||||||
* Handle overlap detection and re-rendering after drag-drop
|
* Handle overlap detection and re-rendering after drag-drop
|
||||||
*/
|
*/
|
||||||
private handleDragDropOverlaps(droppedElement: HTMLElement, targetColumn: string): void {
|
private handleDragDropOverlaps(droppedElement: HTMLElement, targetColumn: ColumnBounds): void {
|
||||||
const targetColumnElement = document.querySelector(`swp-day-column[data-date="${targetColumn}"]`);
|
|
||||||
if (!targetColumnElement) return;
|
|
||||||
|
|
||||||
const eventsLayer = targetColumnElement.querySelector('swp-events-layer') as HTMLElement;
|
const eventsLayer = targetColumn.element.querySelector('swp-events-layer') as HTMLElement;
|
||||||
if (!eventsLayer) return;
|
if (!eventsLayer) return;
|
||||||
|
|
||||||
// Convert dropped element to CalendarEvent with new position
|
// Convert dropped element to CalendarEvent with new position
|
||||||
|
|
@ -543,16 +527,16 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
|
|
||||||
|
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
|
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
|
||||||
|
|
||||||
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
||||||
const timedEvents = events.filter(event => !event.allDay);
|
const timedEvents = events.filter(event => !event.allDay);
|
||||||
|
|
||||||
console.log('🎯 EventRenderer: Filtering events', {
|
console.log('🎯 EventRenderer: Filtering events', {
|
||||||
totalEvents: events.length,
|
totalEvents: events.length,
|
||||||
timedEvents: timedEvents.length,
|
timedEvents: timedEvents.length,
|
||||||
filteredOutAllDay: events.length - timedEvents.length
|
filteredOutAllDay: events.length - timedEvents.length
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find columns in the specific container for regular events
|
// Find columns in the specific container for regular events
|
||||||
const columns = this.getColumns(container);
|
const columns = this.getColumns(container);
|
||||||
|
|
||||||
|
|
@ -561,7 +545,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
|
|
||||||
const eventsLayer = column.querySelector('swp-events-layer');
|
const eventsLayer = column.querySelector('swp-events-layer');
|
||||||
if (eventsLayer) {
|
if (eventsLayer) {
|
||||||
|
|
||||||
this.handleEventOverlaps(columnEvents, eventsLayer as HTMLElement);
|
this.handleEventOverlaps(columnEvents, eventsLayer as HTMLElement);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class EventRenderingService {
|
||||||
// Cache strategy at initialization
|
// Cache strategy at initialization
|
||||||
const calendarType = calendarConfig.getCalendarMode();
|
const calendarType = calendarConfig.getCalendarMode();
|
||||||
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||||
|
|
||||||
// Initialize all-day event renderer and manager
|
// Initialize all-day event renderer and manager
|
||||||
this.allDayEventRenderer = new AllDayEventRenderer();
|
this.allDayEventRenderer = new AllDayEventRenderer();
|
||||||
this.allDayManager = new AllDayManager();
|
this.allDayManager = new AllDayManager();
|
||||||
|
|
@ -92,7 +92,7 @@ export class EventRenderingService {
|
||||||
startDate: startDate.toISOString(),
|
startDate: startDate.toISOString(),
|
||||||
endDate: endDate.toISOString()
|
endDate: endDate.toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render all-day events using period from header
|
// Render all-day events using period from header
|
||||||
this.renderAllDayEventsForPeriod(startDate, endDate);
|
this.renderAllDayEventsForPeriod(startDate, endDate);
|
||||||
});
|
});
|
||||||
|
|
@ -181,22 +181,21 @@ export class EventRenderingService {
|
||||||
this.eventBus.on('drag:start', (event: Event) => {
|
this.eventBus.on('drag:start', (event: Event) => {
|
||||||
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
||||||
// Use the draggedElement directly - no need for DOM query
|
// Use the draggedElement directly - no need for DOM query
|
||||||
if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.column) {
|
if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
||||||
this.strategy.handleDragStart(dragStartPayload);
|
this.strategy.handleDragStart(dragStartPayload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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;
|
let dragEvent = (event as CustomEvent<DragMoveEventPayload>).detail;
|
||||||
|
|
||||||
// Filter: Only handle events WITHOUT data-allday attribute (normal timed events)
|
// Filter: Only handle events WITHOUT data-allday attribute (normal timed events)
|
||||||
if (draggedElement.hasAttribute('data-allday')) {
|
if (dragEvent.draggedElement.hasAttribute('data-allday')) {
|
||||||
return; // This is an all-day event, let AllDayManager handle it
|
return; // This is an all-day event, let AllDayManager handle it
|
||||||
}
|
}
|
||||||
if (this.strategy.handleDragMove && column) {
|
if (this.strategy.handleDragMove) {
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
this.strategy.handleDragMove(dragEvent);
|
||||||
this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -239,22 +238,22 @@ export class EventRenderingService {
|
||||||
// Use draggedElement directly - no need for DOM query
|
// Use draggedElement directly - no need for DOM query
|
||||||
if (draggedElement && this.strategy.handleEventClick) {
|
if (draggedElement && this.strategy.handleEventClick) {
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = draggedElement.dataset.eventId || '';
|
||||||
this.strategy.handleEventClick(eventId, draggedElement);
|
this.strategy.handleEventClick(eventId, draggedElement); //TODO: fix this redundant parameters
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle column change
|
// Handle column change
|
||||||
this.eventBus.on('drag:column-change', (event: Event) => {
|
this.eventBus.on('drag:column-change', (event: Event) => {
|
||||||
const { draggedElement, draggedClone, newColumn } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
let columnChangeEvent = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
|
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
|
||||||
if (draggedClone && draggedClone.hasAttribute('data-allday')) {
|
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
|
||||||
return; // This is an all-day event, let AllDayManager handle it
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.strategy.handleColumnChange) {
|
if (this.strategy.handleColumnChange) {
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = columnChangeEvent.draggedElement.dataset.eventId || '';
|
||||||
this.strategy.handleColumnChange(eventId, newColumn); //TODO: Should be refactored to use payload, no need to lookup clone again inside
|
this.strategy.handleColumnChange(columnChangeEvent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -363,17 +362,17 @@ export class EventRenderingService {
|
||||||
|
|
||||||
// Get actual visible dates from DOM headers instead of generating them
|
// Get actual visible dates from DOM headers instead of generating them
|
||||||
const weekDates = this.getVisibleDatesFromDOM();
|
const weekDates = this.getVisibleDatesFromDOM();
|
||||||
|
|
||||||
console.log('🔍 EventRenderingService: Using visible dates from DOM', {
|
console.log('🔍 EventRenderingService: Using visible dates from DOM', {
|
||||||
weekDates,
|
weekDates,
|
||||||
count: weekDates.length
|
count: weekDates.length
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pass current events to AllDayManager for state tracking
|
// Pass current events to AllDayManager for state tracking
|
||||||
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
|
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
|
||||||
|
|
||||||
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
||||||
|
|
||||||
// Render each all-day event with pre-calculated layout
|
// Render each all-day event with pre-calculated layout
|
||||||
layouts.forEach(layout => {
|
layouts.forEach(layout => {
|
||||||
this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout);
|
this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout);
|
||||||
|
|
@ -395,7 +394,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private clearEvents(container?: HTMLElement): void {
|
private clearEvents(container?: HTMLElement): void {
|
||||||
this.strategy.clearEvents(container);
|
this.strategy.clearEvents(container);
|
||||||
|
|
||||||
// Also clear all-day events
|
// Also clear all-day events
|
||||||
this.clearAllDayEvents();
|
this.clearAllDayEvents();
|
||||||
}
|
}
|
||||||
|
|
@ -409,18 +408,18 @@ export class EventRenderingService {
|
||||||
* Get visible dates from DOM headers - only the dates that are actually displayed
|
* Get visible dates from DOM headers - only the dates that are actually displayed
|
||||||
*/
|
*/
|
||||||
private getVisibleDatesFromDOM(): string[] {
|
private getVisibleDatesFromDOM(): string[] {
|
||||||
|
|
||||||
const dayHeaders = document.querySelectorAll('swp-calendar-header swp-day-header');
|
const dayHeaders = document.querySelectorAll('swp-calendar-header swp-day-header');
|
||||||
const weekDates: string[] = [];
|
const weekDates: string[] = [];
|
||||||
|
|
||||||
dayHeaders.forEach(header => {
|
dayHeaders.forEach(header => {
|
||||||
const dateAttr = header.getAttribute('data-date');
|
const dateAttr = header.getAttribute('data-date');
|
||||||
if (dateAttr) {
|
if (dateAttr) {
|
||||||
weekDates.push(dateAttr);
|
weekDates.push(dateAttr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return weekDates;
|
return weekDates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
* Type definitions for calendar events
|
* Type definitions for calendar events
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ColumnBounds } from "../utils/ColumnDetectionUtils";
|
||||||
|
|
||||||
export interface AllDayEvent {
|
export interface AllDayEvent {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -49,7 +51,7 @@ export interface DragStartEventPayload {
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: MousePosition;
|
||||||
column: string | null;
|
columnBounds: ColumnBounds | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag move event payload
|
// Drag move event payload
|
||||||
|
|
@ -57,8 +59,8 @@ export interface DragMoveEventPayload {
|
||||||
draggedElement: HTMLElement;
|
draggedElement: HTMLElement;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: MousePosition;
|
||||||
|
columnBounds: ColumnBounds | null;
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
column: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag end event payload
|
// Drag end event payload
|
||||||
|
|
@ -67,7 +69,7 @@ export interface DragEndEventPayload {
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
finalPosition: {
|
finalPosition: {
|
||||||
column: string | null;
|
column: ColumnBounds | null;
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
};
|
};
|
||||||
target: 'swp-day-column' | 'swp-day-header' | null;
|
target: 'swp-day-column' | 'swp-day-header' | null;
|
||||||
|
|
@ -75,7 +77,7 @@ export interface DragEndEventPayload {
|
||||||
|
|
||||||
// Drag mouse enter header event payload
|
// Drag mouse enter header event payload
|
||||||
export interface DragMouseEnterHeaderEventPayload {
|
export interface DragMouseEnterHeaderEventPayload {
|
||||||
targetDate: string;
|
targetColumn: ColumnBounds;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
cloneElement: HTMLElement | null;
|
cloneElement: HTMLElement | null;
|
||||||
|
|
@ -93,8 +95,8 @@ export interface DragMouseLeaveHeaderEventPayload {
|
||||||
export interface DragColumnChangeEventPayload {
|
export interface DragColumnChangeEventPayload {
|
||||||
draggedElement: HTMLElement;
|
draggedElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
previousColumn: string | null;
|
previousColumn: ColumnBounds | null;
|
||||||
newColumn: string;
|
newColumn: ColumnBounds;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,92 +3,81 @@
|
||||||
* Used by both DragDropManager and AllDayManager for consistent column detection
|
* Used by both DragDropManager and AllDayManager for consistent column detection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MousePosition } from "../types/DragDropTypes";
|
||||||
|
|
||||||
|
|
||||||
export interface ColumnBounds {
|
export interface ColumnBounds {
|
||||||
date: string;
|
date: string;
|
||||||
left: number;
|
left: number;
|
||||||
right: number;
|
right: number;
|
||||||
|
boundingClientRect: DOMRect,
|
||||||
|
element : HTMLElement,
|
||||||
|
index: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ColumnDetectionUtils {
|
export class ColumnDetectionUtils {
|
||||||
private static columnBoundsCache: ColumnBounds[] = [];
|
private static columnBoundsCache: ColumnBounds[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update column bounds cache for coordinate-based column detection
|
* Update column bounds cache for coordinate-based column detection
|
||||||
*/
|
*/
|
||||||
public static updateColumnBoundsCache(): void {
|
public static updateColumnBoundsCache(): void {
|
||||||
// Reset cache
|
// Reset cache
|
||||||
this.columnBoundsCache = [];
|
this.columnBoundsCache = [];
|
||||||
|
|
||||||
// Find alle kolonner
|
// Find alle kolonner
|
||||||
const columns = document.querySelectorAll('swp-day-column');
|
const columns = document.querySelectorAll('swp-day-column');
|
||||||
|
let index = 0;
|
||||||
|
// Cache hver kolonnes x-grænser
|
||||||
|
columns.forEach(column => {
|
||||||
|
const rect = column.getBoundingClientRect();
|
||||||
|
const date = (column as HTMLElement).dataset.date;
|
||||||
|
|
||||||
// Cache hver kolonnes x-grænser
|
if (date) {
|
||||||
columns.forEach(column => {
|
this.columnBoundsCache.push({
|
||||||
const rect = column.getBoundingClientRect();
|
boundingClientRect : rect,
|
||||||
const date = (column as HTMLElement).dataset.date;
|
element: column as HTMLElement,
|
||||||
|
date,
|
||||||
if (date) {
|
left: rect.left,
|
||||||
this.columnBoundsCache.push({
|
right: rect.right,
|
||||||
date,
|
index: index++
|
||||||
left: rect.left,
|
});
|
||||||
right: rect.right
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sorter efter x-position (fra venstre til højre)
|
// Sorter efter x-position (fra venstre til højre)
|
||||||
this.columnBoundsCache.sort((a, b) => a.left - b.left);
|
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 =>
|
* Get column date from X coordinate using cached bounds
|
||||||
x >= col.left && x <= col.right
|
*/
|
||||||
);
|
public static getColumnBounds(position: MousePosition): ColumnBounds | null{
|
||||||
|
if (this.columnBoundsCache.length === 0) {
|
||||||
|
this.updateColumnBoundsCache();
|
||||||
|
}
|
||||||
|
|
||||||
return column ? column.date : null;
|
// Find den kolonne hvor x-koordinaten er indenfor grænserne
|
||||||
}
|
let column = this.columnBoundsCache.find(col =>
|
||||||
|
position.x >= col.left && position.x <= col.right
|
||||||
|
);
|
||||||
|
if (column)
|
||||||
|
return column;
|
||||||
|
|
||||||
/**
|
return 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
|
* Clear cache (useful for testing or when DOM structure changes)
|
||||||
}
|
*/
|
||||||
|
public static clearCache(): void {
|
||||||
|
this.columnBoundsCache = [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get column index from X coordinate
|
* Get current cache for debugging
|
||||||
*/
|
*/
|
||||||
public static getColumnIndexFromX(x: number): number {
|
public static getCache(): ColumnBounds[] {
|
||||||
const date = this.getColumnDateFromX(x);
|
return [...this.columnBoundsCache];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
|
import { ColumnBounds } from './ColumnDetectionUtils';
|
||||||
import { DateCalculator } from './DateCalculator';
|
import { DateCalculator } from './DateCalculator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -157,22 +158,14 @@ export class PositionUtils {
|
||||||
/**
|
/**
|
||||||
* Beregn Y position fra mouse/touch koordinat
|
* Beregn Y position fra mouse/touch koordinat
|
||||||
*/
|
*/
|
||||||
public static getPositionFromCoordinate(clientY: number, containerElement: HTMLElement): number {
|
public static getPositionFromCoordinate(clientY: number, column: ColumnBounds): number {
|
||||||
const rect = containerElement.getBoundingClientRect();
|
|
||||||
const relativeY = clientY - rect.top;
|
const relativeY = clientY - column.boundingClientRect.top;
|
||||||
|
|
||||||
// Snap til grid
|
// Snap til grid
|
||||||
return PositionUtils.snapToGrid(relativeY);
|
return PositionUtils.snapToGrid(relativeY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Beregn tid fra mouse/touch koordinat
|
|
||||||
*/
|
|
||||||
public static getTimeFromCoordinate(clientY: number, containerElement: HTMLElement): string {
|
|
||||||
const position = PositionUtils.getPositionFromCoordinate(clientY, containerElement);
|
|
||||||
return PositionUtils.pixelsToTime(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valider at tid er inden for arbejdstimer
|
* Valider at tid er inden for arbejdstimer
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue