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;
|
private eventBus: IEventBus;
|
||||||
|
|
||||||
// Mouse tracking with optimized state
|
// Mouse tracking with optimized state
|
||||||
private lastMousePosition: MousePosition = { x: 0, y: 0 };
|
private mouseDownPosition: MousePosition = { x: 0, y: 0 };
|
||||||
private currentMouseY = 0;
|
|
||||||
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
||||||
private initialMousePosition: MousePosition = { x: 0, y: 0 };
|
|
||||||
|
|
||||||
// Drag state
|
// Drag state
|
||||||
private draggedElement!: HTMLElement | null;
|
private originalElement!: HTMLElement | null;
|
||||||
private draggedClone!: HTMLElement | null;
|
private draggedClone!: HTMLElement | null;
|
||||||
private currentColumnBounds: ColumnBounds | null = null;
|
private currentColumn: ColumnBounds | null = null;
|
||||||
private initialColumnBounds: ColumnBounds | null = null; // Track source column
|
private previousColumn: ColumnBounds | null = null;
|
||||||
private isDragStarted = false;
|
private isDragStarted = false;
|
||||||
|
|
||||||
// Hover state
|
// Hover state
|
||||||
|
|
@ -70,7 +68,7 @@ export class DragDropManager {
|
||||||
|
|
||||||
if (calendarContainer) {
|
if (calendarContainer) {
|
||||||
calendarContainer.addEventListener('mouseleave', () => {
|
calendarContainer.addEventListener('mouseleave', () => {
|
||||||
if (this.draggedElement && this.isDragStarted) {
|
if (this.originalElement && this.isDragStarted) {
|
||||||
this.cancelDrag();
|
this.cancelDrag();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -116,11 +114,13 @@ export class DragDropManager {
|
||||||
// Clean up drag state first
|
// Clean up drag state first
|
||||||
this.cleanupDragState();
|
this.cleanupDragState();
|
||||||
ColumnDetectionUtils.updateColumnBoundsCache();
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
//this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
//this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Check if mousedown is on an event
|
// Check if mousedown is on an event
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
|
if (target.closest('swp-resize-handle')) return;
|
||||||
|
|
||||||
let eventElement = target;
|
let eventElement = target;
|
||||||
|
|
||||||
while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') {
|
while (eventElement && eventElement.tagName !== 'SWP-GRID-CONTAINER') {
|
||||||
|
|
@ -131,21 +131,18 @@ export class DragDropManager {
|
||||||
if (!eventElement) return;
|
if (!eventElement) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Found an event - check if clicking on resize handle first
|
|
||||||
if (eventElement) {
|
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
|
// Normal drag - prepare for potential dragging
|
||||||
this.draggedElement = eventElement;
|
this.originalElement = eventElement;
|
||||||
// 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
|
||||||
};
|
};
|
||||||
|
this.mouseDownPosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,8 +150,8 @@ export class DragDropManager {
|
||||||
* Optimized mouse move handler with consolidated position calculations
|
* Optimized mouse move handler with consolidated position calculations
|
||||||
*/
|
*/
|
||||||
private handleMouseMove(event: MouseEvent): void {
|
private handleMouseMove(event: MouseEvent): void {
|
||||||
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 event hover (coordinate-based) - only when mouse button is up
|
// Check for event hover (coordinate-based) - only when mouse button is up
|
||||||
if (this.isHoverTrackingActive && event.buttons === 0) {
|
if (this.isHoverTrackingActive && event.buttons === 0) {
|
||||||
|
|
@ -165,17 +162,17 @@ export class DragDropManager {
|
||||||
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Try to initialize drag if not started
|
// Try to initialize drag if not started
|
||||||
if (!this.isDragStarted && this.draggedElement) {
|
if (!this.isDragStarted && this.originalElement) {
|
||||||
if (!this.tryInitializeDrag(currentPosition)) {
|
if (!this.initializeDrag(currentPosition)) {
|
||||||
return; // Not enough movement yet
|
return; // Not enough movement yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue drag if started
|
// 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);
|
console.log("Continue drag if started", this.draggedClone);
|
||||||
this.continueDrag(currentPosition);
|
this.continueDrag(currentPosition);
|
||||||
this.detectAndEmitColumnChange(currentPosition);
|
this.detectColumnChange(currentPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,9 +181,9 @@ export class DragDropManager {
|
||||||
* Try to initialize drag based on movement threshold
|
* Try to initialize drag based on movement threshold
|
||||||
* Returns true if drag was initialized, false if not enough movement
|
* Returns true if drag was initialized, false if not enough movement
|
||||||
*/
|
*/
|
||||||
private tryInitializeDrag(currentPosition: MousePosition): boolean {
|
private initializeDrag(currentPosition: MousePosition): boolean {
|
||||||
const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x);
|
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
||||||
const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y);
|
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
||||||
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
if (totalMovement < this.dragThreshold) {
|
if (totalMovement < this.dragThreshold) {
|
||||||
|
|
@ -197,27 +194,23 @@ export class DragDropManager {
|
||||||
this.isDragStarted = true;
|
this.isDragStarted = true;
|
||||||
|
|
||||||
// Set high z-index on event-group if exists, otherwise on event itself
|
// 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) {
|
if (eventGroup) {
|
||||||
eventGroup.style.zIndex = '9999';
|
eventGroup.style.zIndex = '9999';
|
||||||
} else {
|
} else {
|
||||||
this.draggedElement!.style.zIndex = '9999';
|
this.originalElement!.style.zIndex = '9999';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect current column and save as initial source column
|
const originalElement = this.originalElement as BaseSwpEventElement;
|
||||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
this.initialColumnBounds = this.currentColumnBounds;
|
|
||||||
|
|
||||||
// Cast to BaseSwpEventElement and create clone
|
|
||||||
const originalElement = this.draggedElement as BaseSwpEventElement;
|
|
||||||
this.draggedClone = originalElement.createClone();
|
this.draggedClone = originalElement.createClone();
|
||||||
|
|
||||||
const dragStartPayload: DragStartEventPayload = {
|
const dragStartPayload: DragStartEventPayload = {
|
||||||
draggedElement: this.draggedElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition: this.initialMousePosition,
|
mousePosition: this.mouseDownPosition,
|
||||||
mouseOffset: this.mouseOffset,
|
mouseOffset: this.mouseOffset,
|
||||||
columnBounds: this.currentColumnBounds
|
columnBounds: this.currentColumn
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:start', dragStartPayload);
|
this.eventBus.emit('drag:start', dragStartPayload);
|
||||||
|
|
||||||
|
|
@ -250,18 +243,18 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Detect column change and emit event
|
* Detect column change and emit event
|
||||||
*/
|
*/
|
||||||
private detectAndEmitColumnChange(currentPosition: MousePosition): void {
|
private detectColumnChange(currentPosition: MousePosition): void {
|
||||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
if (newColumn == null) return;
|
if (newColumn == null) return;
|
||||||
|
|
||||||
if (newColumn.index !== this.currentColumnBounds?.index) {
|
if (newColumn.index !== this.currentColumn?.index) {
|
||||||
const previousColumn = this.currentColumnBounds;
|
this.previousColumn = this.currentColumn;
|
||||||
this.currentColumnBounds = newColumn;
|
this.currentColumn = newColumn;
|
||||||
|
|
||||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||||
originalElement: this.draggedElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone!,
|
draggedClone: this.draggedClone!,
|
||||||
previousColumn,
|
previousColumn: this.previousColumn,
|
||||||
newColumn,
|
newColumn,
|
||||||
mousePosition: currentPosition
|
mousePosition: currentPosition
|
||||||
};
|
};
|
||||||
|
|
@ -275,7 +268,7 @@ export class DragDropManager {
|
||||||
private handleMouseUp(event: MouseEvent): void {
|
private handleMouseUp(event: MouseEvent): void {
|
||||||
this.stopDragAnimation();
|
this.stopDragAnimation();
|
||||||
|
|
||||||
if (this.draggedElement) {
|
if (this.originalElement) {
|
||||||
|
|
||||||
// Only emit drag:end if drag was actually started
|
// Only emit drag:end if drag was actually started
|
||||||
if (this.isDragStarted) {
|
if (this.isDragStarted) {
|
||||||
|
|
@ -284,13 +277,9 @@ export class DragDropManager {
|
||||||
// Snap to grid on mouse up (like ResizeHandleManager)
|
// Snap to grid on mouse up (like ResizeHandleManager)
|
||||||
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||||
|
|
||||||
if (!column) {
|
if (!column) return;
|
||||||
console.warn('No column detected on mouseUp');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current position and snap it to grid
|
// Get current position and snap it to grid
|
||||||
const currentY = parseFloat(this.draggedClone?.style.top || '0');
|
|
||||||
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
const snappedY = this.calculateSnapPosition(mousePosition.y, column);
|
||||||
|
|
||||||
// Update clone to snapped position immediately
|
// Update clone to snapped position immediately
|
||||||
|
|
@ -304,22 +293,17 @@ export class DragDropManager {
|
||||||
if (!dropTarget)
|
if (!dropTarget)
|
||||||
throw "dropTarget is null";
|
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 = {
|
const dragEndPayload: DragEndEventPayload = {
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.originalElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
sourceColumn: this.initialColumnBounds, // Where drag started
|
|
||||||
mousePosition,
|
mousePosition,
|
||||||
|
sourceColumn: this.previousColumn!!,
|
||||||
finalPosition: { column, snappedY }, // Where drag ended
|
finalPosition: { column, snappedY }, // Where drag ended
|
||||||
target: dropTarget
|
target: dropTarget
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('DragEndEventPayload', dragEndPayload);
|
||||||
|
|
||||||
this.eventBus.emit('drag:end', dragEndPayload);
|
this.eventBus.emit('drag:end', dragEndPayload);
|
||||||
|
|
||||||
this.cleanupDragState();
|
this.cleanupDragState();
|
||||||
|
|
@ -327,7 +311,7 @@ export class DragDropManager {
|
||||||
} else {
|
} else {
|
||||||
// This was just a click - emit click event instead
|
// This was just a click - emit click event instead
|
||||||
this.eventBus.emit('event:click', {
|
this.eventBus.emit('event:click', {
|
||||||
draggedElement: this.draggedElement,
|
clickedElement: this.originalElement,
|
||||||
mousePosition: { x: event.clientX, y: event.clientY }
|
mousePosition: { x: event.clientX, y: event.clientY }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -348,46 +332,24 @@ export class DragDropManager {
|
||||||
* Cancel drag operation when mouse leaves grid container
|
* Cancel drag operation when mouse leaves grid container
|
||||||
*/
|
*/
|
||||||
private cancelDrag(): void {
|
private cancelDrag(): void {
|
||||||
if (!this.draggedElement) return;
|
if (!this.originalElement) return;
|
||||||
|
|
||||||
console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container');
|
console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container');
|
||||||
|
|
||||||
const draggedElement = this.draggedElement;
|
|
||||||
|
|
||||||
// 1. Remove all clones
|
|
||||||
this.cleanupAllClones();
|
this.cleanupAllClones();
|
||||||
|
|
||||||
// 2. Restore original element
|
this.originalElement.style.opacity = '';
|
||||||
if (draggedElement) {
|
this.originalElement.style.cursor = '';
|
||||||
draggedElement.style.opacity = '';
|
|
||||||
draggedElement.style.cursor = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Emit cancellation event
|
|
||||||
this.eventBus.emit('drag:cancelled', {
|
this.eventBus.emit('drag:cancelled', {
|
||||||
draggedElement: draggedElement,
|
originalElement: this.originalElement,
|
||||||
reason: 'mouse-left-grid'
|
reason: 'mouse-left-grid'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Clean up state
|
|
||||||
this.cleanupDragState();
|
this.cleanupDragState();
|
||||||
this.stopDragAnimation();
|
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
|
* Optimized snap position calculation using PositionUtils
|
||||||
*/
|
*/
|
||||||
|
|
@ -405,7 +367,8 @@ export class DragDropManager {
|
||||||
* Smooth drag animation using requestAnimationFrame
|
* Smooth drag animation using requestAnimationFrame
|
||||||
* Emits drag:move events with current draggedClone reference on each frame
|
* 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) {
|
if (!this.isDragStarted || !this.draggedClone || !this.targetColumn) {
|
||||||
this.dragAnimationId = null;
|
this.dragAnimationId = null;
|
||||||
return;
|
return;
|
||||||
|
|
@ -421,9 +384,9 @@ export class DragDropManager {
|
||||||
|
|
||||||
// Emit drag:move event with current draggedClone reference
|
// Emit drag:move event with current draggedClone reference
|
||||||
const dragMovePayload: DragMoveEventPayload = {
|
const dragMovePayload: DragMoveEventPayload = {
|
||||||
draggedElement: this.draggedElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone, // Always uses current reference
|
draggedClone: this.draggedClone, // Always uses current reference
|
||||||
mousePosition: this.lastMousePosition,
|
mousePosition: this.mouseDownPosition,
|
||||||
snappedY: this.currentY,
|
snappedY: this.currentY,
|
||||||
columnBounds: this.targetColumn,
|
columnBounds: this.targetColumn,
|
||||||
mouseOffset: this.mouseOffset
|
mouseOffset: this.mouseOffset
|
||||||
|
|
@ -437,9 +400,9 @@ export class DragDropManager {
|
||||||
|
|
||||||
// Emit final position
|
// Emit final position
|
||||||
const dragMovePayload: DragMoveEventPayload = {
|
const dragMovePayload: DragMoveEventPayload = {
|
||||||
draggedElement: this.draggedElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition: this.lastMousePosition,
|
mousePosition: this.mouseDownPosition,
|
||||||
snappedY: this.currentY,
|
snappedY: this.currentY,
|
||||||
columnBounds: this.targetColumn,
|
columnBounds: this.targetColumn,
|
||||||
mouseOffset: this.mouseOffset
|
mouseOffset: this.mouseOffset
|
||||||
|
|
@ -465,8 +428,10 @@ export class DragDropManager {
|
||||||
* Clean up drag state
|
* Clean up drag state
|
||||||
*/
|
*/
|
||||||
private cleanupDragState(): void {
|
private cleanupDragState(): void {
|
||||||
this.draggedElement = null;
|
this.previousColumn = null;
|
||||||
|
this.originalElement = null;
|
||||||
this.draggedClone = null;
|
this.draggedClone = null;
|
||||||
|
this.currentColumn = null;
|
||||||
this.isDragStarted = false;
|
this.isDragStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,23 +488,20 @@ export class DragDropManager {
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||||
|
|
||||||
if (targetColumn) {
|
if (targetColumn) {
|
||||||
console.log('🎯 DragDropManager: Mouse entered header', { targetDate: targetColumn });
|
|
||||||
|
|
||||||
// Extract CalendarEvent from the dragged clone
|
|
||||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||||
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||||
targetColumn: targetColumn,
|
targetColumn: targetColumn,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.originalElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
calendarEvent: calendarEvent,
|
calendarEvent: calendarEvent,
|
||||||
// Delegate pattern - allows AllDayManager to replace the clone
|
|
||||||
replaceClone: (newClone: HTMLElement) => {
|
replaceClone: (newClone: HTMLElement) => {
|
||||||
this.draggedClone = newClone;
|
this.draggedClone = newClone;
|
||||||
this.dragAnimationId === null;
|
this.dragAnimationId === null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
console.log('DragMouseEnterHeaderEventPayload', dragMouseEnterPayload);
|
||||||
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -573,15 +535,13 @@ export class DragDropManager {
|
||||||
targetColumn: targetColumn,
|
targetColumn: targetColumn,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
snappedY: snappedY,
|
snappedY: snappedY,
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.originalElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
calendarEvent: calendarEvent,
|
calendarEvent: calendarEvent,
|
||||||
// Delegate pattern - allows EventRenderer to replace the clone
|
|
||||||
replaceClone: (newClone: HTMLElement) => {
|
replaceClone: (newClone: HTMLElement) => {
|
||||||
this.draggedClone = newClone;
|
this.draggedClone = newClone;
|
||||||
this.dragAnimationId === null;
|
this.dragAnimationId === null;
|
||||||
this.stopDragAnimation();
|
this.stopDragAnimation();
|
||||||
console.log("replacing clone with", newClone)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
|
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
|
||||||
|
|
@ -609,7 +569,7 @@ export class DragDropManager {
|
||||||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
||||||
targetDate: targetColumn.date,
|
targetDate: targetColumn.date,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.originalElement,
|
||||||
draggedClone: this.draggedClone
|
draggedClone: this.draggedClone
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
|
this.eventBus.emit('drag:mouseleave-header', dragMouseLeavePayload);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { DragMoveEventPayload } from '../types/EventTypes';
|
import { DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
export class EdgeScrollManager {
|
export class EdgeScrollManager {
|
||||||
private scrollableContent: HTMLElement | null = null;
|
private scrollableContent: HTMLElement | null = null;
|
||||||
|
|
@ -13,6 +13,10 @@ export class EdgeScrollManager {
|
||||||
private isDragging = false;
|
private isDragging = false;
|
||||||
private lastTs = 0;
|
private lastTs = 0;
|
||||||
private rect: DOMRect | null = null;
|
private rect: DOMRect | null = null;
|
||||||
|
private draggedClone: HTMLElement | null = null;
|
||||||
|
private initialScrollTop = 0;
|
||||||
|
private initialCloneTop = 0;
|
||||||
|
private scrollListener: ((e: Event) => void) | null = null;
|
||||||
|
|
||||||
// Constants - fixed values as per requirements
|
// Constants - fixed values as per requirements
|
||||||
private readonly OUTER_ZONE = 100; // px from edge (slow zone)
|
private readonly OUTER_ZONE = 100; // px from edge (slow zone)
|
||||||
|
|
@ -31,6 +35,10 @@ export class EdgeScrollManager {
|
||||||
if (this.scrollableContent) {
|
if (this.scrollableContent) {
|
||||||
// Disable smooth scroll for instant auto-scroll
|
// Disable smooth scroll for instant auto-scroll
|
||||||
this.scrollableContent.style.scrollBehavior = 'auto';
|
this.scrollableContent.style.scrollBehavior = 'auto';
|
||||||
|
|
||||||
|
// Add scroll listener
|
||||||
|
this.scrollListener = this.handleScroll.bind(this);
|
||||||
|
this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true });
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
|
@ -38,12 +46,20 @@ export class EdgeScrollManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribeToEvents(): void {
|
private subscribeToEvents(): void {
|
||||||
|
|
||||||
// Listen to drag events from DragDropManager
|
// Listen to drag events from DragDropManager
|
||||||
this.eventBus.on('drag:start', () => this.startDrag());
|
this.eventBus.on('drag:start', (event: Event) => {
|
||||||
|
let customEvent = event as CustomEvent<DragStartEventPayload>;
|
||||||
|
this.draggedClone = customEvent.detail.draggedClone;
|
||||||
|
this.startDrag();
|
||||||
|
});
|
||||||
|
|
||||||
this.eventBus.on('drag:move', (event: Event) => {
|
this.eventBus.on('drag:move', (event: Event) => {
|
||||||
const customEvent = event as CustomEvent<DragMoveEventPayload>;
|
let customEvent = event as CustomEvent<DragMoveEventPayload>;
|
||||||
|
this.draggedClone = customEvent.detail.draggedClone;
|
||||||
this.updateMouseY(customEvent.detail.mousePosition.y);
|
this.updateMouseY(customEvent.detail.mousePosition.y);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventBus.on('drag:end', () => this.stopDrag());
|
this.eventBus.on('drag:end', () => this.stopDrag());
|
||||||
this.eventBus.on('drag:cancelled', () => this.stopDrag());
|
this.eventBus.on('drag:cancelled', () => this.stopDrag());
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +68,16 @@ export class EdgeScrollManager {
|
||||||
console.log('🎬 EdgeScrollManager: Starting drag');
|
console.log('🎬 EdgeScrollManager: Starting drag');
|
||||||
this.isDragging = true;
|
this.isDragging = true;
|
||||||
this.lastTs = performance.now();
|
this.lastTs = performance.now();
|
||||||
|
|
||||||
|
// Gem initial scroll position OG clone position
|
||||||
|
this.initialScrollTop = this.scrollableContent?.scrollTop || 0;
|
||||||
|
this.initialCloneTop = parseFloat(this.draggedClone?.style.top || '0');
|
||||||
|
|
||||||
|
console.log('💾 EdgeScrollManager: Saved initial state', {
|
||||||
|
initialScrollTop: this.initialScrollTop,
|
||||||
|
initialCloneTop: this.initialCloneTop
|
||||||
|
});
|
||||||
|
|
||||||
if (this.scrollRAF === null) {
|
if (this.scrollRAF === null) {
|
||||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +100,28 @@ export class EdgeScrollManager {
|
||||||
}
|
}
|
||||||
this.rect = null;
|
this.rect = null;
|
||||||
this.lastTs = 0;
|
this.lastTs = 0;
|
||||||
|
this.draggedClone = null;
|
||||||
|
this.initialScrollTop = 0;
|
||||||
|
this.initialCloneTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleScroll(): void {
|
||||||
|
if (!this.isDragging || !this.draggedClone || !this.scrollableContent) return;
|
||||||
|
|
||||||
|
const currentScrollTop = this.scrollableContent.scrollTop;
|
||||||
|
const totalScrollDelta = currentScrollTop - this.initialScrollTop;
|
||||||
|
|
||||||
|
// Beregn ny position baseret på initial position + total scroll delta
|
||||||
|
const newTop = this.initialCloneTop + totalScrollDelta;
|
||||||
|
this.draggedClone.style.top = `${newTop}px`;
|
||||||
|
|
||||||
|
console.log('📜 EdgeScrollManager: Scroll event - updated clone', {
|
||||||
|
initialScrollTop: this.initialScrollTop,
|
||||||
|
currentScrollTop,
|
||||||
|
totalScrollDelta,
|
||||||
|
initialCloneTop: this.initialCloneTop,
|
||||||
|
newTop
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private scrollTick(ts: number): void {
|
private scrollTick(ts: number): void {
|
||||||
|
|
@ -97,23 +145,15 @@ export class EdgeScrollManager {
|
||||||
|
|
||||||
// Check top edge
|
// Check top edge
|
||||||
if (distTop < this.INNER_ZONE) {
|
if (distTop < this.INNER_ZONE) {
|
||||||
// Inner zone (0-50px) - fast speed
|
|
||||||
vy = -this.FAST_SPEED_PXS;
|
vy = -this.FAST_SPEED_PXS;
|
||||||
console.log('⬆️ EdgeScrollManager: Top FAST', { distTop, vy });
|
|
||||||
} else if (distTop < this.OUTER_ZONE) {
|
} else if (distTop < this.OUTER_ZONE) {
|
||||||
// Outer zone (50-100px) - slow speed
|
|
||||||
vy = -this.SLOW_SPEED_PXS;
|
vy = -this.SLOW_SPEED_PXS;
|
||||||
console.log('⬆️ EdgeScrollManager: Top SLOW', { distTop, vy });
|
|
||||||
}
|
}
|
||||||
// Check bottom edge
|
// Check bottom edge
|
||||||
else if (distBot < this.INNER_ZONE) {
|
else if (distBot < this.INNER_ZONE) {
|
||||||
// Inner zone (0-50px) - fast speed
|
|
||||||
vy = this.FAST_SPEED_PXS;
|
vy = this.FAST_SPEED_PXS;
|
||||||
console.log('⬇️ EdgeScrollManager: Bottom FAST', { distBot, vy });
|
|
||||||
} else if (distBot < this.OUTER_ZONE) {
|
} else if (distBot < this.OUTER_ZONE) {
|
||||||
// Outer zone (50-100px) - slow speed
|
|
||||||
vy = this.SLOW_SPEED_PXS;
|
vy = this.SLOW_SPEED_PXS;
|
||||||
console.log('⬇️ EdgeScrollManager: Bottom SLOW', { distBot, vy });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export class AllDayEventRenderer {
|
||||||
*/
|
*/
|
||||||
public handleDragStart(payload: DragStartEventPayload): void {
|
public handleDragStart(payload: DragStartEventPayload): void {
|
||||||
|
|
||||||
this.originalEvent = payload.draggedElement;;
|
this.originalEvent = payload.originalElement;;
|
||||||
this.draggedClone = payload.draggedClone;
|
this.draggedClone = payload.draggedClone;
|
||||||
|
|
||||||
if (this.draggedClone) {
|
if (this.draggedClone) {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export class DateEventRenderer implements EventRendererStrategy {
|
||||||
*/
|
*/
|
||||||
public handleDragStart(payload: DragStartEventPayload): void {
|
public handleDragStart(payload: DragStartEventPayload): void {
|
||||||
|
|
||||||
this.originalEvent = payload.draggedElement;;
|
this.originalEvent = payload.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;
|
||||||
|
|
|
||||||
|
|
@ -138,11 +138,11 @@ 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;
|
||||||
|
|
||||||
if (dragStartPayload.draggedElement.hasAttribute('data-allday')) {
|
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
if (dragStartPayload.originalElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
||||||
this.strategy.handleDragStart(dragStartPayload);
|
this.strategy.handleDragStart(dragStartPayload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -163,6 +163,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupDragEndListener(): void {
|
private setupDragEndListener(): void {
|
||||||
this.eventBus.on('drag:end', (event: Event) => {
|
this.eventBus.on('drag:end', (event: Event) => {
|
||||||
|
|
||||||
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||||
const finalColumn = finalPosition.column;
|
const finalColumn = finalPosition.column;
|
||||||
const finalY = finalPosition.snappedY;
|
const finalY = finalPosition.snappedY;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export interface MousePosition {
|
||||||
|
|
||||||
// Drag start event payload
|
// Drag start event payload
|
||||||
export interface DragStartEventPayload {
|
export interface DragStartEventPayload {
|
||||||
draggedElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: MousePosition;
|
||||||
|
|
@ -27,7 +27,7 @@ export interface DragStartEventPayload {
|
||||||
|
|
||||||
// Drag move event payload
|
// Drag move event payload
|
||||||
export interface DragMoveEventPayload {
|
export interface DragMoveEventPayload {
|
||||||
draggedElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: MousePosition;
|
||||||
|
|
@ -39,8 +39,8 @@ export interface DragMoveEventPayload {
|
||||||
export interface DragEndEventPayload {
|
export interface DragEndEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
sourceColumn: ColumnBounds | null; // Where drag started
|
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
|
sourceColumn: ColumnBounds;
|
||||||
finalPosition: {
|
finalPosition: {
|
||||||
column: ColumnBounds | null; // Where drag ended
|
column: ColumnBounds | null; // Where drag ended
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
|
|
@ -55,7 +55,6 @@ export interface DragMouseEnterHeaderEventPayload {
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
calendarEvent: CalendarEvent;
|
calendarEvent: CalendarEvent;
|
||||||
// Delegate pattern - allows subscriber to replace the dragged clone
|
|
||||||
replaceClone: (newClone: HTMLElement) => void;
|
replaceClone: (newClone: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +74,6 @@ export interface DragMouseEnterColumnEventPayload {
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
calendarEvent: CalendarEvent;
|
calendarEvent: CalendarEvent;
|
||||||
// Delegate pattern - allows subscriber to replace the dragged clone
|
|
||||||
replaceClone: (newClone: HTMLElement) => void;
|
replaceClone: (newClone: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue