Improves drag and drop functionality
Refactors drag and drop to use the original element as the source and introduces edge scrolling. This change aims to enhance the user experience during drag and drop operations by ensuring the correct element is used as the source, fixing issues, and by automatically scrolling the view when the dragged element reaches the edge of the scrollable area.
This commit is contained in:
parent
8df1f6c4f1
commit
e620c919aa
6 changed files with 120 additions and 121 deletions
|
|
@ -23,16 +23,14 @@ export class DragDropManager {
|
|||
private eventBus: IEventBus;
|
||||
|
||||
// Mouse tracking with optimized state
|
||||
private lastMousePosition: MousePosition = { x: 0, y: 0 };
|
||||
private currentMouseY = 0;
|
||||
private mouseDownPosition: MousePosition = { x: 0, y: 0 };
|
||||
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
||||
private initialMousePosition: MousePosition = { x: 0, y: 0 };
|
||||
|
||||
// Drag state
|
||||
private draggedElement!: HTMLElement | null;
|
||||
private originalElement!: HTMLElement | null;
|
||||
private draggedClone!: HTMLElement | null;
|
||||
private currentColumnBounds: ColumnBounds | null = null;
|
||||
private initialColumnBounds: ColumnBounds | null = null; // Track source column
|
||||
private currentColumn: ColumnBounds | null = null;
|
||||
private previousColumn: ColumnBounds | null = null;
|
||||
private isDragStarted = false;
|
||||
|
||||
// Hover state
|
||||
|
|
@ -70,7 +68,7 @@ export class DragDropManager {
|
|||
|
||||
if (calendarContainer) {
|
||||
calendarContainer.addEventListener('mouseleave', () => {
|
||||
if (this.draggedElement && this.isDragStarted) {
|
||||
if (this.originalElement && this.isDragStarted) {
|
||||
this.cancelDrag();
|
||||
}
|
||||
});
|
||||
|
|
@ -116,11 +114,13 @@ export class DragDropManager {
|
|||
// Clean up drag state first
|
||||
this.cleanupDragState();
|
||||
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||
this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
||||
//this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||
//this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Check if mousedown is on an event
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.closest('swp-resize-handle')) return;
|
||||
|
||||
let eventElement = target;
|
||||
|
||||
while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') {
|
||||
|
|
@ -131,21 +131,18 @@ export class DragDropManager {
|
|||
if (!eventElement) return;
|
||||
}
|
||||
|
||||
|
||||
// Found an event - check if clicking on resize handle first
|
||||
if (eventElement) {
|
||||
// Check if click is on resize handle
|
||||
if (target.closest('swp-resize-handle')) {
|
||||
return; // Exit early - this is a resize operation, let ResizeHandleManager handle it
|
||||
}
|
||||
|
||||
// Normal drag - prepare for potential dragging
|
||||
this.draggedElement = eventElement;
|
||||
this.originalElement = eventElement;
|
||||
// Calculate mouse offset within event
|
||||
const eventRect = eventElement.getBoundingClientRect();
|
||||
this.mouseOffset = {
|
||||
x: event.clientX - eventRect.left,
|
||||
y: event.clientY - eventRect.top
|
||||
};
|
||||
this.mouseDownPosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,8 +150,8 @@ export class DragDropManager {
|
|||
* Optimized mouse move handler with consolidated position calculations
|
||||
*/
|
||||
private handleMouseMove(event: MouseEvent): void {
|
||||
this.currentMouseY = event.clientY;
|
||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||
//this.currentMouseY = event.clientY;
|
||||
// this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Check for event hover (coordinate-based) - only when mouse button is up
|
||||
if (this.isHoverTrackingActive && event.buttons === 0) {
|
||||
|
|
@ -165,17 +162,17 @@ export class DragDropManager {
|
|||
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Try to initialize drag if not started
|
||||
if (!this.isDragStarted && this.draggedElement) {
|
||||
if (!this.tryInitializeDrag(currentPosition)) {
|
||||
if (!this.isDragStarted && this.originalElement) {
|
||||
if (!this.initializeDrag(currentPosition)) {
|
||||
return; // Not enough movement yet
|
||||
}
|
||||
}
|
||||
|
||||
// Continue drag if started
|
||||
if (this.isDragStarted && this.draggedElement && this.draggedClone) {
|
||||
if (this.isDragStarted && this.originalElement && this.draggedClone) {
|
||||
console.log("Continue drag if started", this.draggedClone);
|
||||
this.continueDrag(currentPosition);
|
||||
this.detectAndEmitColumnChange(currentPosition);
|
||||
this.detectColumnChange(currentPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,9 +181,9 @@ export class DragDropManager {
|
|||
* Try to initialize drag based on movement threshold
|
||||
* Returns true if drag was initialized, false if not enough movement
|
||||
*/
|
||||
private tryInitializeDrag(currentPosition: MousePosition): boolean {
|
||||
const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x);
|
||||
const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y);
|
||||
private initializeDrag(currentPosition: MousePosition): boolean {
|
||||
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
||||
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
||||
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
if (totalMovement < this.dragThreshold) {
|
||||
|
|
@ -197,27 +194,23 @@ export class DragDropManager {
|
|||
this.isDragStarted = true;
|
||||
|
||||
// Set high z-index on event-group if exists, otherwise on event itself
|
||||
const eventGroup = this.draggedElement!.closest<HTMLElement>('swp-event-group');
|
||||
const eventGroup = this.originalElement!.closest<HTMLElement>('swp-event-group');
|
||||
if (eventGroup) {
|
||||
eventGroup.style.zIndex = '9999';
|
||||
} else {
|
||||
this.draggedElement!.style.zIndex = '9999';
|
||||
this.originalElement!.style.zIndex = '9999';
|
||||
}
|
||||
|
||||
// Detect current column and save as initial source column
|
||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
this.initialColumnBounds = this.currentColumnBounds;
|
||||
|
||||
// Cast to BaseSwpEventElement and create clone
|
||||
const originalElement = this.draggedElement as BaseSwpEventElement;
|
||||
const originalElement = this.originalElement as BaseSwpEventElement;
|
||||
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
this.draggedClone = originalElement.createClone();
|
||||
|
||||
const dragStartPayload: DragStartEventPayload = {
|
||||
draggedElement: this.draggedElement!,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.initialMousePosition,
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.mouseDownPosition,
|
||||
mouseOffset: this.mouseOffset,
|
||||
columnBounds: this.currentColumnBounds
|
||||
columnBounds: this.currentColumn
|
||||
};
|
||||
this.eventBus.emit('drag:start', dragStartPayload);
|
||||
|
||||
|
|
@ -250,18 +243,18 @@ export class DragDropManager {
|
|||
/**
|
||||
* Detect column change and emit event
|
||||
*/
|
||||
private detectAndEmitColumnChange(currentPosition: MousePosition): void {
|
||||
private detectColumnChange(currentPosition: MousePosition): void {
|
||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
if (newColumn == null) return;
|
||||
|
||||
if (newColumn.index !== this.currentColumnBounds?.index) {
|
||||
const previousColumn = this.currentColumnBounds;
|
||||
this.currentColumnBounds = newColumn;
|
||||
if (newColumn.index !== this.currentColumn?.index) {
|
||||
this.previousColumn = this.currentColumn;
|
||||
this.currentColumn = newColumn;
|
||||
|
||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||
originalElement: this.draggedElement!,
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone!,
|
||||
previousColumn,
|
||||
previousColumn: this.previousColumn,
|
||||
newColumn,
|
||||
mousePosition: currentPosition
|
||||
};
|
||||
|
|
@ -275,7 +268,7 @@ export class DragDropManager {
|
|||
private handleMouseUp(event: MouseEvent): void {
|
||||
this.stopDragAnimation();
|
||||
|
||||
if (this.draggedElement) {
|
||||
if (this.originalElement) {
|
||||
|
||||
// Only emit drag:end if drag was actually started
|
||||
if (this.isDragStarted) {
|
||||
|
|
@ -284,13 +277,9 @@ export class DragDropManager {
|
|||
// Snap to grid on mouse up (like ResizeHandleManager)
|
||||
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||
|
||||
if (!column) {
|
||||
console.warn('No column detected on mouseUp');
|
||||
return;
|
||||
}
|
||||
if (!column) return;
|
||||
|
||||
// Get current position and snap it to grid
|
||||
const currentY = parseFloat(this.draggedClone?.style.top || '0');
|
||||
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||
|
||||
// Update clone to snapped position immediately
|
||||
|
|
@ -304,22 +293,17 @@ export class DragDropManager {
|
|||
if (!dropTarget)
|
||||
throw "dropTarget is null";
|
||||
|
||||
console.log('🎯 DragDropManager: Emitting drag:end', {
|
||||
draggedElement: this.draggedElement.dataset.eventId,
|
||||
finalColumn: column,
|
||||
finalY: snappedY,
|
||||
dropTarget: dropTarget,
|
||||
isDragStarted: this.isDragStarted
|
||||
});
|
||||
|
||||
const dragEndPayload: DragEndEventPayload = {
|
||||
originalElement: this.draggedElement,
|
||||
originalElement: this.originalElement,
|
||||
draggedClone: this.draggedClone,
|
||||
sourceColumn: this.initialColumnBounds, // Where drag started
|
||||
mousePosition,
|
||||
sourceColumn: this.previousColumn!!,
|
||||
finalPosition: { column, snappedY }, // Where drag ended
|
||||
target: dropTarget
|
||||
};
|
||||
|
||||
console.log('DragEndEventPayload', dragEndPayload);
|
||||
|
||||
this.eventBus.emit('drag:end', dragEndPayload);
|
||||
|
||||
this.cleanupDragState();
|
||||
|
|
@ -327,7 +311,7 @@ export class DragDropManager {
|
|||
} else {
|
||||
// This was just a click - emit click event instead
|
||||
this.eventBus.emit('event:click', {
|
||||
draggedElement: this.draggedElement,
|
||||
clickedElement: this.originalElement,
|
||||
mousePosition: { x: event.clientX, y: event.clientY }
|
||||
});
|
||||
}
|
||||
|
|
@ -348,46 +332,24 @@ export class DragDropManager {
|
|||
* Cancel drag operation when mouse leaves grid container
|
||||
*/
|
||||
private cancelDrag(): void {
|
||||
if (!this.draggedElement) return;
|
||||
if (!this.originalElement) return;
|
||||
|
||||
console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container');
|
||||
|
||||
const draggedElement = this.draggedElement;
|
||||
|
||||
// 1. Remove all clones
|
||||
this.cleanupAllClones();
|
||||
|
||||
// 2. Restore original element
|
||||
if (draggedElement) {
|
||||
draggedElement.style.opacity = '';
|
||||
draggedElement.style.cursor = '';
|
||||
}
|
||||
this.originalElement.style.opacity = '';
|
||||
this.originalElement.style.cursor = '';
|
||||
|
||||
// 3. Emit cancellation event
|
||||
this.eventBus.emit('drag:cancelled', {
|
||||
draggedElement: draggedElement,
|
||||
originalElement: this.originalElement,
|
||||
reason: 'mouse-left-grid'
|
||||
});
|
||||
|
||||
// 4. Clean up state
|
||||
this.cleanupDragState();
|
||||
this.stopDragAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated position calculation method using PositionUtils
|
||||
*/
|
||||
private calculateDragPosition(mousePosition: MousePosition): { column: ColumnBounds | null; snappedY: number } {
|
||||
let column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||
let snappedY = 0;
|
||||
if (column) {
|
||||
snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||
return { column, snappedY };
|
||||
}
|
||||
|
||||
return { column, snappedY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized snap position calculation using PositionUtils
|
||||
*/
|
||||
|
|
@ -405,7 +367,8 @@ export class DragDropManager {
|
|||
* Smooth drag animation using requestAnimationFrame
|
||||
* Emits drag:move events with current draggedClone reference on each frame
|
||||
*/
|
||||
private animateDrag(): void {
|
||||
private animateDrag(): void { //TODO: this can be optimized... WAIT !!!
|
||||
|
||||
if (!this.isDragStarted || !this.draggedClone || !this.targetColumn) {
|
||||
this.dragAnimationId = null;
|
||||
return;
|
||||
|
|
@ -421,9 +384,9 @@ export class DragDropManager {
|
|||
|
||||
// Emit drag:move event with current draggedClone reference
|
||||
const dragMovePayload: DragMoveEventPayload = {
|
||||
draggedElement: this.draggedElement!,
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone, // Always uses current reference
|
||||
mousePosition: this.lastMousePosition,
|
||||
mousePosition: this.mouseDownPosition,
|
||||
snappedY: this.currentY,
|
||||
columnBounds: this.targetColumn,
|
||||
mouseOffset: this.mouseOffset
|
||||
|
|
@ -437,9 +400,9 @@ export class DragDropManager {
|
|||
|
||||
// Emit final position
|
||||
const dragMovePayload: DragMoveEventPayload = {
|
||||
draggedElement: this.draggedElement!,
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.lastMousePosition,
|
||||
mousePosition: this.mouseDownPosition,
|
||||
snappedY: this.currentY,
|
||||
columnBounds: this.targetColumn,
|
||||
mouseOffset: this.mouseOffset
|
||||
|
|
@ -465,8 +428,10 @@ export class DragDropManager {
|
|||
* Clean up drag state
|
||||
*/
|
||||
private cleanupDragState(): void {
|
||||
this.draggedElement = null;
|
||||
this.previousColumn = null;
|
||||
this.originalElement = null;
|
||||
this.draggedClone = null;
|
||||
this.currentColumn = null;
|
||||
this.isDragStarted = false;
|
||||
}
|
||||
|
||||
|
|
@ -523,23 +488,20 @@ export class DragDropManager {
|
|||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
|
||||
if (targetColumn) {
|
||||
console.log('🎯 DragDropManager: Mouse entered header', { targetDate: targetColumn });
|
||||
|
||||
// Extract CalendarEvent from the dragged clone
|
||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||
|
||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||
targetColumn: targetColumn,
|
||||
mousePosition: position,
|
||||
originalElement: this.draggedElement,
|
||||
originalElement: this.originalElement,
|
||||
draggedClone: this.draggedClone,
|
||||
calendarEvent: calendarEvent,
|
||||
// Delegate pattern - allows AllDayManager to replace the clone
|
||||
replaceClone: (newClone: HTMLElement) => {
|
||||
this.draggedClone = newClone;
|
||||
this.dragAnimationId === null;
|
||||
}
|
||||
};
|
||||
console.log('DragMouseEnterHeaderEventPayload', dragMouseEnterPayload);
|
||||
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
||||
}
|
||||
}
|
||||
|
|
@ -573,15 +535,13 @@ export class DragDropManager {
|
|||
targetColumn: targetColumn,
|
||||
mousePosition: position,
|
||||
snappedY: snappedY,
|
||||
originalElement: this.draggedElement,
|
||||
originalElement: this.originalElement,
|
||||
draggedClone: this.draggedClone,
|
||||
calendarEvent: calendarEvent,
|
||||
// Delegate pattern - allows EventRenderer to replace the clone
|
||||
replaceClone: (newClone: HTMLElement) => {
|
||||
this.draggedClone = newClone;
|
||||
this.dragAnimationId === null;
|
||||
this.stopDragAnimation();
|
||||
console.log("replacing clone with", newClone)
|
||||
}
|
||||
};
|
||||
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
|
||||
|
|
@ -609,7 +569,7 @@ export class DragDropManager {
|
|||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
||||
targetDate: targetColumn.date,
|
||||
mousePosition: position,
|
||||
originalElement: this.draggedElement,
|
||||
originalElement: this.originalElement,
|
||||
draggedClone: this.draggedClone
|
||||
};
|
||||
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue