Improves drag and drop functionality

Refactors drag and drop logic to use the dragged clone consistently, fixing issues with event handling and element manipulation during drag operations.
Also includes a fix where the original element is removed after a drag is completed.
Adds column bounds cache update after drag operations for improved column detection.
This commit is contained in:
Janus C. H. Knudsen 2025-09-30 00:13:52 +02:00
parent 83e01f9cb7
commit 5417a2b6b1
7 changed files with 50 additions and 45 deletions

View file

@ -74,7 +74,7 @@ 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 { originalElement: draggedElement, draggedClone, mousePosition } = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
if (draggedClone == null) if (draggedClone == null)
return; return;
@ -316,8 +316,13 @@ export class AllDayManager {
* During drag: Place in row 1 only, calculate column from targetDate * During drag: Place in row 1 only, calculate column from targetDate
*/ */
private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void { private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void {
if(payload.draggedClone?.dataset == null)
console.error("payload.cloneElement.dataset.eventId is null");
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: payload.cloneElement.dataset.eventId, eventId: payload.draggedClone.dataset.eventId,
targetDate: payload.targetColumn targetDate: payload.targetColumn
}); });
@ -325,17 +330,18 @@ export class AllDayManager {
let allDayContainer = this.getAllDayContainer(); let allDayContainer = this.getAllDayContainer();
payload.cloneElement.removeAttribute('style'); payload.draggedClone.removeAttribute('style');
payload.cloneElement.classList.add('all-day-style'); payload.draggedClone.style.gridRow = '1';
payload.cloneElement.style.gridRow = '1'; payload.draggedClone.style.gridColumn = payload.targetColumn.index.toString();
payload.cloneElement.style.gridColumn = payload.targetColumn.index.toString(); payload.draggedClone.dataset.allday = 'true'; // Set the all-day attribute for filtering
payload.cloneElement.dataset.allday = 'true'; // Set the all-day attribute for filtering
// Add to container // Add to container
allDayContainer?.appendChild(payload.cloneElement); allDayContainer?.appendChild(payload.draggedClone);
ColumnDetectionUtils.updateColumnBoundsCache();
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', { console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
eventId: payload.cloneElement.dataset.eventId, eventId: payload.draggedClone.dataset.eventId,
gridColumn: payload.targetColumn, gridColumn: payload.targetColumn,
gridRow: 1 gridRow: 1
}); });
@ -459,7 +465,7 @@ export class AllDayManager {
if (oldGridArea !== newGridArea) { if (oldGridArea !== newGridArea) {
changedCount++; changedCount++;
const element = document.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement; const element = dragEndEvent.draggedClone; //:end document.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
if (element) { if (element) {
// Add transition class for smooth animation // Add transition class for smooth animation
@ -481,6 +487,7 @@ export class AllDayManager {
dragEndEvent.draggedClone.style.opacity = ''; dragEndEvent.draggedClone.style.opacity = '';
// 7. Restore original element opacity // 7. Restore original element opacity
dragEndEvent.originalElement.remove();
//originalElement.style.opacity = ''; //originalElement.style.opacity = '';
// 8. Check if height adjustment is needed // 8. Check if height adjustment is needed

View file

@ -129,7 +129,7 @@ export class DragDropManager {
// Clean up drag state first // Clean up drag state first
this.cleanupDragState(); this.cleanupDragState();
ColumnDetectionUtils.updateColumnBoundsCache();
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 };
@ -171,16 +171,18 @@ export class DragDropManager {
this.currentMouseY = event.clientY; this.currentMouseY = event.clientY;
this.lastMousePosition = { x: event.clientX, y: event.clientY }; this.lastMousePosition = { x: event.clientX, y: event.clientY };
// Check for header enter/leave during drag
if (this.draggedElement) {
this.checkHeaderEnterLeave(event);
}
if (event.buttons === 1 && this.draggedElement) {
if (event.buttons === 1) {
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; //TODO: Is this really needed? why not just use event.clientX + Y directly const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; //TODO: Is this really needed? why not just use event.clientX + Y directly
// Check for header enter/leave during drag
if (this.draggedClone) {
this.checkHeaderEnterLeave(event);
}
// Check if we need to start drag (movement threshold) // Check if we need to start drag (movement threshold)
if (!this.isDragStarted) { if (!this.isDragStarted && this.draggedElement) {
const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x); const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x);
const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y); const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y);
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY); const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
@ -214,7 +216,7 @@ export class DragDropManager {
} }
// Continue with normal drag behavior only if drag has started // Continue with normal drag behavior only if drag has started
if (this.isDragStarted) { if (this.isDragStarted && this.draggedElement && this.draggedClone) {
const deltaY = Math.abs(currentPosition.y - this.lastLoggedPosition.y); const deltaY = Math.abs(currentPosition.y - this.lastLoggedPosition.y);
// Check for snap interval vertical movement (normal drag behavior) // Check for snap interval vertical movement (normal drag behavior)
@ -227,6 +229,7 @@ export class DragDropManager {
// Emit drag move event with snapped position (normal behavior) // Emit drag move event with snapped position (normal behavior)
const dragMovePayload: DragMoveEventPayload = { const dragMovePayload: DragMoveEventPayload = {
draggedElement: this.draggedElement, draggedElement: this.draggedElement,
draggedClone: this.draggedClone,
mousePosition: currentPosition, mousePosition: currentPosition,
snappedY: positionData.snappedY, snappedY: positionData.snappedY,
columnBounds: positionData.column, columnBounds: positionData.column,
@ -246,7 +249,7 @@ export class DragDropManager {
this.currentColumnBounds = newColumn; this.currentColumnBounds = newColumn;
const dragColumnChangePayload: DragColumnChangeEventPayload = { const dragColumnChangePayload: DragColumnChangeEventPayload = {
draggedElement: this.draggedElement, originalElement: this.draggedElement,
draggedClone: this.draggedClone, draggedClone: this.draggedClone,
previousColumn, previousColumn,
newColumn, newColumn,
@ -276,6 +279,9 @@ export class DragDropManager {
// Detect drop target (swp-day-column or swp-day-header) // Detect drop target (swp-day-column or swp-day-header)
const dropTarget = this.detectDropTarget(mousePosition); const dropTarget = this.detectDropTarget(mousePosition);
if(!dropTarget)
throw "dropTarget is null";
console.log('🎯 DragDropManager: Emitting drag:end', { console.log('🎯 DragDropManager: Emitting drag:end', {
draggedElement: this.draggedElement.dataset.eventId, draggedElement: this.draggedElement.dataset.eventId,
finalColumn: positionData.column, finalColumn: positionData.column,
@ -285,7 +291,7 @@ export class DragDropManager {
}); });
const dragEndPayload: DragEndEventPayload = { const dragEndPayload: DragEndEventPayload = {
draggedElement: this.draggedElement, originalElement: this.draggedElement,
draggedClone: this.draggedClone, draggedClone: this.draggedClone,
mousePosition, mousePosition,
finalPosition: positionData, finalPosition: positionData,
@ -293,7 +299,6 @@ export class DragDropManager {
}; };
this.eventBus.emit('drag:end', dragEndPayload); this.eventBus.emit('drag:end', dragEndPayload);
this.draggedElement.remove(); // TODO: this should be changed into a subscriber which only after a succesful placement is fired, not just mouseup as this can remove elements that are not placed.
} else { } else {
// This was just a click - emit click event instead // This was just a click - emit click event instead
@ -489,7 +494,7 @@ export class DragDropManager {
const isCurrentlyInHeader = !!headerElement; const isCurrentlyInHeader = !!headerElement;
// Detect header enter // Detect header enter
if (!this.isInHeader && isCurrentlyInHeader) { if (!this.isInHeader && isCurrentlyInHeader && this.draggedClone) {
this.isInHeader = true; this.isInHeader = true;
// Calculate target date using existing method // Calculate target date using existing method
@ -498,15 +503,11 @@ export class DragDropManager {
if (targetColumn) { if (targetColumn) {
console.log('🎯 DragDropManager: Emitting drag:mouseenter-header', { targetDate: 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 = { const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
targetColumn: targetColumn, targetColumn: targetColumn,
mousePosition: { x: event.clientX, y: event.clientY }, mousePosition: { x: event.clientX, y: event.clientY },
originalElement: this.draggedElement, originalElement: this.draggedElement,
cloneElement: cloneElement draggedClone: this.draggedClone
}; };
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload); this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
} }
@ -525,15 +526,12 @@ export class DragDropManager {
return; 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 = { const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
targetDate: targetColumn.date, 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 draggedClone: this.draggedClone
}; };
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload); this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
} }

View file

@ -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 { targetColumn: targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail; const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
console.log('🎯 HeaderManager: Received drag:mouseenter-header', { console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
targetDate, targetDate,
@ -65,7 +65,7 @@ export class HeaderManager {
}; };
this.dragMouseLeaveHeaderListener = (event: Event) => { this.dragMouseLeaveHeaderListener = (event: Event) => {
const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail; const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
console.log('🚪 HeaderManager: Received drag:mouseleave-header', { console.log('🚪 HeaderManager: Received drag:mouseleave-header', {
targetDate, targetDate,

View file

@ -210,7 +210,7 @@ export class EventRenderingService {
// Handle drag end events and delegate to appropriate renderer // Handle drag end events and delegate to appropriate renderer
this.eventBus.on('drag:end', (event: Event) => { this.eventBus.on('drag:end', (event: Event) => {
const { draggedElement, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail; const { originalElement: draggedElement, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
const finalColumn = finalPosition.column; const finalColumn = finalPosition.column;
const finalY = finalPosition.snappedY; const finalY = finalPosition.snappedY;
const eventId = draggedElement.dataset.eventId || ''; const eventId = draggedElement.dataset.eventId || '';
@ -252,14 +252,14 @@ export class EventRenderingService {
} }
if (this.strategy.handleColumnChange) { if (this.strategy.handleColumnChange) {
const eventId = columnChangeEvent.draggedElement.dataset.eventId || ''; const eventId = columnChangeEvent.originalElement.dataset.eventId || '';
this.strategy.handleColumnChange(columnChangeEvent); this.strategy.handleColumnChange(columnChangeEvent);
} }
}); });
this.dragMouseLeaveHeaderListener = (event: Event) => { this.dragMouseLeaveHeaderListener = (event: Event) => {
const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail; const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
if (cloneElement) if (cloneElement)
cloneElement.style.display = ''; cloneElement.style.display = '';

View file

@ -49,6 +49,7 @@ export class GridStyleManager {
* Set time-related CSS variables * Set time-related CSS variables
*/ */
private setTimeVariables(root: HTMLElement, gridSettings: GridSettings): void { private setTimeVariables(root: HTMLElement, gridSettings: GridSettings): void {
root.style.setProperty('--header-height', '80px'); // Fixed header height
root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`); root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`); root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`);
root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString()); root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString());

View file

@ -57,6 +57,7 @@ export interface DragStartEventPayload {
// Drag move event payload // Drag move event payload
export interface DragMoveEventPayload { export interface DragMoveEventPayload {
draggedElement: HTMLElement; draggedElement: HTMLElement;
draggedClone: HTMLElement;
mousePosition: MousePosition; mousePosition: MousePosition;
mouseOffset: MousePosition; mouseOffset: MousePosition;
columnBounds: ColumnBounds | null; columnBounds: ColumnBounds | null;
@ -65,7 +66,7 @@ export interface DragMoveEventPayload {
// Drag end event payload // Drag end event payload
export interface DragEndEventPayload { export interface DragEndEventPayload {
draggedElement: HTMLElement; originalElement: HTMLElement;
draggedClone: HTMLElement | null; draggedClone: HTMLElement | null;
mousePosition: MousePosition; mousePosition: MousePosition;
finalPosition: { finalPosition: {
@ -80,7 +81,7 @@ export interface DragMouseEnterHeaderEventPayload {
targetColumn: ColumnBounds; targetColumn: ColumnBounds;
mousePosition: MousePosition; mousePosition: MousePosition;
originalElement: HTMLElement | null; originalElement: HTMLElement | null;
cloneElement: HTMLElement; draggedClone: HTMLElement;
} }
// Drag mouse leave header event payload // Drag mouse leave header event payload
@ -88,12 +89,12 @@ export interface DragMouseLeaveHeaderEventPayload {
targetDate: string | null; targetDate: string | null;
mousePosition: MousePosition; mousePosition: MousePosition;
originalElement: HTMLElement| null; originalElement: HTMLElement| null;
cloneElement: HTMLElement| null; draggedClone: HTMLElement| null;
} }
// Drag column change event payload // Drag column change event payload
export interface DragColumnChangeEventPayload { export interface DragColumnChangeEventPayload {
draggedElement: HTMLElement; originalElement: HTMLElement;
draggedClone: HTMLElement | null; draggedClone: HTMLElement | null;
previousColumn: ColumnBounds | null; previousColumn: ColumnBounds | null;
newColumn: ColumnBounds; newColumn: ColumnBounds;

View file

@ -317,14 +317,12 @@ swp-allday-container swp-event {
} }
/* Hide time element for all-day styled events */ /* Hide time element for all-day styled events */
swp-allday-container swp-event swp-event-time, swp-allday-container swp-event swp-event-time{
swp-event.all-day-style swp-event-time {
display: none; display: none;
} }
/* Adjust title display for all-day styled events */ /* Adjust title display for all-day styled events */
swp-allday-container swp-event swp-event-title, swp-allday-container swp-event swp-event-title {
swp-event.all-day-style swp-event-title {
display: block; display: block;
font-size: 12px; font-size: 12px;
line-height: 18px; line-height: 18px;