Improves drag and drop with snap and autoscroll

Enhances the drag and drop functionality by snapping the dragged element to a grid and ensuring continuous updates during auto-scroll.

The changes include tracking mouse position for consistent clone positioning, snapping to 15-minute intervals, and updating clone position during auto-scroll.
Also, removes unnecessary logging.
This commit is contained in:
Janus Knudsen 2025-08-25 18:15:53 +02:00
parent fc354ad618
commit 6ede297bb5

View file

@ -19,6 +19,7 @@ export class ColumnDetector {
private autoScrollAnimationId: number | null = null;
private scrollSpeed = 10; // pixels per frame
private scrollThreshold = 30; // pixels from edge
private currentMouseY = 0; // Track current mouse Y for scroll updates
// Konfiguration for snap interval
private snapIntervalMinutes = 15; // 15 minutter
@ -81,14 +82,23 @@ export class ColumnDetector {
eventBus.on('header:mouseover', (event) => {
const { dayHeader, headerRenderer } = (event as CustomEvent).detail;
if (this.isMouseDown && this.draggedClone) {
console.log('Dragging clone over header - calling addToAllDay');
console.log('Dragging clone over header - expanding and converting to all-day');
headerRenderer.addToAllDay(dayHeader);
// Convert clone to all-day format immediately
const targetDate = dayHeader.dataset.date;
if (targetDate) {
this.convertToAllDayPreview(targetDate);
}
}
});
}
private handleMouseMove(event: MouseEvent): void {
// Track current mouse position for auto-scroll updates
this.currentMouseY = event.clientY;
// Hvis musen er holdt nede, tjek for snap interval vertikal bevægelse
if (this.isMouseDown) {
const deltaY = Math.abs(event.clientY - this.lastLoggedPosition.y);
@ -102,17 +112,26 @@ export class ColumnDetector {
});
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
// Opdater klonens Y-position ved snap interval (relativt til kolonne)
// Snap klonens position til nærmeste 15-min interval
if (this.draggedClone && this.draggedClone.parentElement) {
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
this.draggedClone.style.top = relativeY + 'px';
const rawRelativeY = event.clientY - columnRect.top - this.mouseOffset.y;
// Snap til nærmeste 15-min grid
const snappedY = Math.round(rawRelativeY / this.snapDistancePx) * this.snapDistancePx;
this.draggedClone.style.top = snappedY + 'px';
}
}
// Kontinuerlig opdatering under auto-scroll for at sikre klonen følger musen
if (this.draggedClone && this.draggedClone.parentElement && this.autoScrollAnimationId !== null) {
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
this.draggedClone.style.top = relativeY + 'px';
}
// Auto-scroll detection når der er en aktiv clone
if (this.draggedClone) {
console.log('ColumnDetector: Checking auto-scroll, mouse at:', event.clientY);
this.checkAutoScroll(event);
}
}
@ -287,18 +306,24 @@ export class ColumnDetector {
// Drop operationen: fade out original og remove clone suffix
if (this.originalEvent && this.draggedClone) {
// Fade out og fjern originalen
this.fadeOutAndRemove(this.originalEvent);
// Check if clone was converted to all-day (is now in header)
const cloneInHeader = this.draggedClone.closest('swp-calendar-header');
// Fjern clone suffix fra klonen
this.removeClonePrefix(this.draggedClone);
if (cloneInHeader) {
console.log('Drop completed: all-day event created');
// Clone is now an all-day event, just fade out original
this.fadeOutAndRemove(this.originalEvent);
} else {
console.log('Drop in regular area - keeping as timed event');
// Normal drop: fade out original and keep clone as timed
this.fadeOutAndRemove(this.originalEvent);
this.removeClonePrefix(this.draggedClone);
}
// Ryd op
this.originalEvent = null;
this.draggedClone = null;
this.scrollContainer = null;
console.log('Drop completed: original faded out and removed, clone became permanent');
}
// Cleanup hvis ingen drop (ingen clone var aktiv)
@ -343,14 +368,6 @@ export class ColumnDetector {
const distanceFromTop = mouseY - containerRect.top;
const distanceFromBottom = containerRect.bottom - mouseY;
console.log('ColumnDetector: Auto-scroll check:', {
mouseY,
containerTop: containerRect.top,
containerBottom: containerRect.bottom,
distanceFromTop: Math.round(distanceFromTop),
distanceFromBottom: Math.round(distanceFromBottom),
threshold: this.scrollThreshold
});
// Check if we need to scroll up
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
@ -386,6 +403,13 @@ export class ColumnDetector {
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
this.scrollContainer.scrollTop += scrollAmount;
// Update clone position based on current mouse position after scroll
if (this.draggedClone && this.draggedClone.parentElement) {
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
this.draggedClone.style.top = relativeY + 'px';
}
// Continue animation
this.autoScrollAnimationId = requestAnimationFrame(scroll);
};
@ -403,6 +427,114 @@ export class ColumnDetector {
}
}
/**
* Convert dragged clone to all-day event preview
*/
private convertToAllDayPreview(targetDate: string): void {
if (!this.draggedClone) return;
// Only convert once
if (this.draggedClone.tagName === 'SWP-ALLDAY-EVENT') {
return;
}
// Transform clone to all-day format
this.transformCloneToAllDay(this.draggedClone, targetDate);
// No need to recalculate height - addToAllDay already handles this
console.log(`Converted clone to all-day preview for date: ${targetDate}`);
}
/**
* Transform clone from timed event to all-day event format
*/
private transformCloneToAllDay(clone: HTMLElement, targetDate: string): void {
console.log('transformCloneToAllDay called with:', { clone, targetDate });
const calendarHeader = document.querySelector('swp-calendar-header');
if (!calendarHeader) {
console.error('No calendar header found');
return;
}
// Find or create all-day container for target date
const container = this.findOrCreateAllDayContainer(calendarHeader as HTMLElement, targetDate);
if (!container) {
console.error('No container found/created');
return;
}
// Extract title from original clone (remove time info)
const titleElement = clone.querySelector('swp-event-title');
const eventTitle = titleElement ? titleElement.textContent || 'Untitled Event' : 'Untitled Event';
console.log('Creating all-day event with title:', eventTitle);
// Create new all-day event element
const allDayEvent = document.createElement('swp-allday-event');
allDayEvent.setAttribute('data-event-id', clone.dataset.eventId || '');
allDayEvent.setAttribute('data-type', clone.dataset.type || 'work');
allDayEvent.textContent = eventTitle;
console.log('All-day event created:', allDayEvent);
// Remove the original clone from its current parent
if (clone.parentElement) {
console.log('Removing original clone from parent:', clone.parentElement);
clone.parentElement.removeChild(clone);
}
// Add new all-day event to container
container.appendChild(allDayEvent);
console.log('All-day event added to container:', container);
// Update reference to point to new element
this.draggedClone = allDayEvent;
console.log(`Transformed clone to all-day event in container for date: ${targetDate}`);
}
/**
* Find existing or create new all-day container for specific date
*/
private findOrCreateAllDayContainer(calendarHeader: HTMLElement, targetDate: string): HTMLElement | null {
// Find day headers to determine column index
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
let columnIndex = -1;
for (let i = 0; i < dayHeaders.length; i++) {
const dayHeader = dayHeaders[i] as HTMLElement;
if (dayHeader.dataset.date === targetDate) {
columnIndex = i + 1; // 1-based grid index
break;
}
}
if (columnIndex === -1) {
console.error(`Could not find column for date: ${targetDate}`);
return null;
}
// Look for existing container for this single column
const containerKey = `${columnIndex}-1`; // single column span
let container = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
if (!container) {
// Create new container
container = document.createElement('swp-allday-container');
container.setAttribute('data-container-key', containerKey);
container.setAttribute('data-date', targetDate);
(container as HTMLElement).style.gridColumn = `${columnIndex}`;
(container as HTMLElement).style.gridRow = '2'; // All-day row
calendarHeader.appendChild(container);
console.log(`Created new all-day container for column ${columnIndex}, date: ${targetDate}`);
}
return container as HTMLElement;
}
public destroy(): void {
this.stopAutoScroll();
document.body.removeEventListener('mousemove', this.handleMouseMove.bind(this));