From 5d406201b8e5489f1923ea0ce4dc030928c7fa59 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Wed, 8 Oct 2025 23:29:56 +0200 Subject: [PATCH] Refactors drag and drop logic Improves drag and drop initialization and handling by extracting methods for clarity. This enhances code readability and maintainability. --- src/managers/DragDropManager.ts | 161 +++++++++++++++----------- src/renderers/EventRendererManager.ts | 60 +++++----- 2 files changed, 125 insertions(+), 96 deletions(-) diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 6eda62e..14c0dfa 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -214,88 +214,111 @@ export class DragDropManager { if (event.buttons === 1) { const currentPosition: MousePosition = { x: event.clientX, y: event.clientY }; - // Check if we need to start drag (movement threshold) + // Try to initialize drag if not started if (!this.isDragStarted && this.draggedElement) { - const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x); - const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y); - const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - - if (totalMovement >= this.dragThreshold) { - // Start drag - emit drag:start event - this.isDragStarted = true; - - // Set high z-index on event-group if exists, otherwise on event itself - const eventGroup = this.draggedElement.closest('swp-event-group'); - if (eventGroup) { - eventGroup.style.zIndex = '9999'; - } else { - this.draggedElement.style.zIndex = '9999'; - } - - // Detect current column and save as initial source column - this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition); - this.initialColumnBounds = this.currentColumnBounds; // Save source column - - // Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement) - const originalElement = this.draggedElement as BaseSwpEventElement; - this.draggedClone = originalElement.createClone(); - - const dragStartPayload: DragStartEventPayload = { - draggedElement: this.draggedElement, - draggedClone: this.draggedClone, - mousePosition: this.initialMousePosition, - mouseOffset: this.mouseOffset, - columnBounds: this.currentColumnBounds - }; - this.eventBus.emit('drag:start', dragStartPayload); - } else { - // Not enough movement yet - don't start drag - return; + if (!this.tryInitializeDrag(currentPosition)) { + return; // Not enough movement yet } } - // Continue with normal drag behavior only if drag has started + // Continue drag if started if (this.isDragStarted && this.draggedElement && this.draggedClone) { - if (!this.draggedElement.hasAttribute("data-allday")) { - // Calculate raw position from mouse (no snapping) - const column = ColumnDetectionUtils.getColumnBounds(currentPosition); + this.continueDrag(currentPosition); + this.detectAndEmitColumnChange(currentPosition); + } + } + } - if (column) { - // Calculate raw Y position relative to column (accounting for mouse offset) - const columnRect = column.boundingClientRect; - const eventTopY = currentPosition.y - columnRect.top - this.mouseOffset.y; - this.targetY = Math.max(0, eventTopY); // Store raw Y as target (no snapping) - this.targetColumn = column; + /** + * 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); + const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - // Start animation loop if not already running - if (this.dragAnimationId === null) { - this.currentY = parseFloat(this.draggedClone.style.top) || 0; - this.animateDrag(); - } - } + if (totalMovement < this.dragThreshold) { + return false; // Not enough movement + } - // Check for auto-scroll - this.checkAutoScroll(currentPosition); - } + // Start drag + this.isDragStarted = true; - const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition); - if (newColumn == null) - return; + // Set high z-index on event-group if exists, otherwise on event itself + const eventGroup = this.draggedElement!.closest('swp-event-group'); + if (eventGroup) { + eventGroup.style.zIndex = '9999'; + } else { + this.draggedElement!.style.zIndex = '9999'; + } - if (newColumn?.index !== this.currentColumnBounds?.index) { - const previousColumn = this.currentColumnBounds; - this.currentColumnBounds = newColumn; + // Detect current column and save as initial source column + this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition); + this.initialColumnBounds = this.currentColumnBounds; - const dragColumnChangePayload: DragColumnChangeEventPayload = { - originalElement: this.draggedElement, - draggedClone: this.draggedClone, - previousColumn, - newColumn, - mousePosition: currentPosition - }; - this.eventBus.emit('drag:column-change', dragColumnChangePayload); + // Cast to BaseSwpEventElement and create clone + const originalElement = this.draggedElement as BaseSwpEventElement; + this.draggedClone = originalElement.createClone(); + + const dragStartPayload: DragStartEventPayload = { + draggedElement: this.draggedElement!, + draggedClone: this.draggedClone, + mousePosition: this.initialMousePosition, + mouseOffset: this.mouseOffset, + columnBounds: this.currentColumnBounds + }; + this.eventBus.emit('drag:start', dragStartPayload); + + return true; + } + + /** + * Continue drag movement - update position and auto-scroll + */ + private continueDrag(currentPosition: MousePosition): void { + if (!this.draggedElement!.hasAttribute("data-allday")) { + // Calculate raw position from mouse (no snapping) + const column = ColumnDetectionUtils.getColumnBounds(currentPosition); + + if (column) { + // Calculate raw Y position relative to column (accounting for mouse offset) + const columnRect = column.boundingClientRect; + const eventTopY = currentPosition.y - columnRect.top - this.mouseOffset.y; + this.targetY = Math.max(0, eventTopY); + this.targetColumn = column; + + // Start animation loop if not already running + if (this.dragAnimationId === null) { + this.currentY = parseFloat(this.draggedClone!.style.top) || 0; + this.animateDrag(); } } + + // Check for auto-scroll + this.checkAutoScroll(currentPosition); + } + } + + /** + * Detect column change and emit event + */ + private detectAndEmitColumnChange(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; + + const dragColumnChangePayload: DragColumnChangeEventPayload = { + originalElement: this.draggedElement!, + draggedClone: this.draggedClone!, + previousColumn, + newColumn, + mousePosition: currentPosition + }; + this.eventBus.emit('drag:column-change', dragColumnChangePayload); } } diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts index 8866b0f..e214b70 100644 --- a/src/renderers/EventRendererManager.ts +++ b/src/renderers/EventRendererManager.ts @@ -106,30 +106,16 @@ export class EventRenderingService { * Handle GRID_RENDERED event - render events in the current grid */ private handleGridRendered(event: CustomEvent): void { - const { container, startDate, endDate, currentDate, isNavigation } = event.detail; + const { container, startDate, endDate } = event.detail; - if (!container) { - return; - } - - - let periodStart: Date; - let periodEnd: Date; - - if (startDate && endDate) { - // Direct date format - use as provided - periodStart = startDate; - periodEnd = endDate; - } else if (currentDate) { - return; - } else { + if (!container || !startDate || !endDate) { return; } this.renderEvents({ - container: container, - startDate: periodStart, - endDate: periodEnd + container, + startDate, + endDate }); } @@ -149,29 +135,44 @@ export class EventRenderingService { * Setup all drag event listeners - moved from EventRenderer for better separation of concerns */ private setupDragEventListeners(): void { + this.setupDragStartListener(); + this.setupDragMoveListener(); + this.setupDragAutoScrollListener(); + this.setupDragEndListener(); + this.setupDragColumnChangeListener(); + this.setupDragMouseLeaveHeaderListener(); + this.setupResizeEndListener(); + this.setupNavigationCompletedListener(); + } + + private setupDragStartListener(): void { this.eventBus.on('drag:start', (event: Event) => { const dragStartPayload = (event as CustomEvent).detail; - + if (dragStartPayload.draggedElement.hasAttribute('data-allday')) { - return; + return; } if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) { this.strategy.handleDragStart(dragStartPayload); } }); + } + private setupDragMoveListener(): void { this.eventBus.on('drag:move', (event: Event) => { let dragEvent = (event as CustomEvent).detail; if (dragEvent.draggedElement.hasAttribute('data-allday')) { - return; + return; } if (this.strategy.handleDragMove) { this.strategy.handleDragMove(dragEvent); } }); + } + private setupDragAutoScrollListener(): void { this.eventBus.on('drag:auto-scroll', (event: Event) => { const { draggedElement, snappedY } = (event as CustomEvent).detail; if (this.strategy.handleDragAutoScroll) { @@ -179,8 +180,9 @@ export class EventRenderingService { this.strategy.handleDragAutoScroll(eventId, snappedY); } }); + } - // Handle drag end events and delegate to appropriate renderer + private setupDragEndListener(): void { this.eventBus.on('drag:end', (event: Event) => { const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent).detail; const finalColumn = finalPosition.column; @@ -224,8 +226,9 @@ export class EventRenderingService { dayEventClone.remove(); } }); + } - // Handle column change + private setupDragColumnChangeListener(): void { this.eventBus.on('drag:column-change', (event: Event) => { let columnChangeEvent = (event as CustomEvent).detail; @@ -238,8 +241,9 @@ export class EventRenderingService { this.strategy.handleColumnChange(columnChangeEvent); } }); + } - + private setupDragMouseLeaveHeaderListener(): void { this.dragMouseLeaveHeaderListener = (event: Event) => { const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent).detail; @@ -255,8 +259,9 @@ export class EventRenderingService { }; this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); + } - // Handle resize end events + private setupResizeEndListener(): void { this.eventBus.on('resize:end', (event: Event) => { const { eventId, element } = (event as CustomEvent).detail; @@ -286,8 +291,9 @@ export class EventRenderingService { } } }); + } - // Handle navigation period change + private setupNavigationCompletedListener(): void { this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => { // Delegate to strategy if it handles navigation if (this.strategy.handleNavigationCompleted) {