From 4e47df2e5cbd7183c724bef8c6429dddec5be55d Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Wed, 10 Dec 2025 19:22:13 +0100 Subject: [PATCH] Refactor edge scroll management for drag operations Simplifies EdgeScrollManager implementation with more focused and efficient auto-scrolling logic Removes unnecessary complexity and scroll tracking Optimizes scroll velocity calculation and boundary detection Improves performance by using requestAnimationFrame for smooth scrolling --- src/v2/managers/DragDropManager.ts | 3 - src/v2/managers/EdgeScrollManager.ts | 168 ++++++++++----------------- 2 files changed, 61 insertions(+), 110 deletions(-) diff --git a/src/v2/managers/DragDropManager.ts b/src/v2/managers/DragDropManager.ts index 55675ca..2ef32a3 100644 --- a/src/v2/managers/DragDropManager.ts +++ b/src/v2/managers/DragDropManager.ts @@ -180,9 +180,6 @@ export class DragDropManager { const columnRect = columnElement.getBoundingClientRect(); const targetY = e.clientY - columnRect.top - mouseOffset.y; - // Reset scroll compensation - this.scrollDeltaY = 0; - // Initialize drag state this.dragState = { eventId, diff --git a/src/v2/managers/EdgeScrollManager.ts b/src/v2/managers/EdgeScrollManager.ts index 5d4e668..d1b5584 100644 --- a/src/v2/managers/EdgeScrollManager.ts +++ b/src/v2/managers/EdgeScrollManager.ts @@ -1,8 +1,9 @@ /** - * EdgeScrollManager - Auto-scroll when dragging near edges - * Uses time-based scrolling with 2-zone system for variable speed + * EdgeScrollManager - Auto-scroll when dragging near viewport edges * - * Copied from V1 with minor adaptations for V2 event names. + * 2-zone system: + * - Inner zone (0-50px): Fast scroll (640 px/sec) + * - Outer zone (50-100px): Slow scroll (140 px/sec) */ import { IEventBus } from '../types/CalendarTypes'; @@ -19,13 +20,11 @@ export class EdgeScrollManager { private lastTs = 0; private rect: DOMRect | null = null; private initialScrollTop = 0; - private scrollListener: ((e: Event) => void) | null = null; - // Constants private readonly OUTER_ZONE = 100; private readonly INNER_ZONE = 50; - private readonly SLOW_SPEED_PXS = 140; - private readonly FAST_SPEED_PXS = 640; + private readonly SLOW_SPEED = 140; + private readonly FAST_SPEED = 640; constructor(private eventBus: IEventBus) { this.subscribeToEvents(); @@ -35,13 +34,7 @@ export class EdgeScrollManager { init(scrollableContent: HTMLElement): void { this.scrollableContent = scrollableContent; this.timeGrid = scrollableContent.querySelector('swp-time-grid'); - - // Disable smooth scroll for instant auto-scroll this.scrollableContent.style.scrollBehavior = 'auto'; - - // Add scroll listener to detect actual scrolling - this.scrollListener = this.handleScroll.bind(this); - this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true }); } private trackMouse = (e: PointerEvent): void => { @@ -64,123 +57,84 @@ export class EdgeScrollManager { private startDrag(): void { this.isDragging = true; this.isScrolling = false; - this.lastTs = performance.now(); - - if (this.scrollableContent) { - this.initialScrollTop = this.scrollableContent.scrollTop; - } + this.lastTs = 0; + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; if (this.scrollRAF === null) { - this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); + this.scrollRAF = requestAnimationFrame(this.scrollTick); } } private stopDrag(): void { this.isDragging = false; - - if (this.isScrolling) { - this.isScrolling = false; - this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); - } + this.setScrollingState(false); if (this.scrollRAF !== null) { cancelAnimationFrame(this.scrollRAF); this.scrollRAF = null; } + this.rect = null; this.lastTs = 0; this.initialScrollTop = 0; } - private handleScroll(): void { + private calculateVelocity(): number { + if (!this.rect) return 0; + + const distTop = this.mouseY - this.rect.top; + const distBot = this.rect.bottom - this.mouseY; + + if (distTop < this.INNER_ZONE) return -this.FAST_SPEED; + if (distTop < this.OUTER_ZONE) return -this.SLOW_SPEED; + if (distBot < this.INNER_ZONE) return this.FAST_SPEED; + if (distBot < this.OUTER_ZONE) return this.SLOW_SPEED; + + return 0; + } + + private isAtBoundary(velocity: number): boolean { + if (!this.scrollableContent || !this.timeGrid || !this.draggedElement) return false; + + const atTop = this.scrollableContent.scrollTop <= 0 && velocity < 0; + const atBottom = velocity > 0 && + this.draggedElement.getBoundingClientRect().bottom >= + this.timeGrid.getBoundingClientRect().bottom; + + return atTop || atBottom; + } + + private setScrollingState(scrolling: boolean): void { + if (this.isScrolling === scrolling) return; + + this.isScrolling = scrolling; + if (scrolling) { + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); + } else { + this.initialScrollTop = this.scrollableContent?.scrollTop ?? 0; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); + } + } + + private scrollTick = (ts: number): void => { if (!this.isDragging || !this.scrollableContent) return; - const currentScrollTop = this.scrollableContent.scrollTop; - const scrollDelta = Math.abs(currentScrollTop - this.initialScrollTop); - - if (scrollDelta > 1 && !this.isScrolling) { - this.isScrolling = true; - this.eventBus.emit(CoreEvents.EDGE_SCROLL_STARTED, {}); - } - } - - private scrollTick(ts: number): void { const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0; this.lastTs = ts; + this.rect ??= this.scrollableContent.getBoundingClientRect(); - if (!this.scrollableContent) { - this.stopDrag(); - return; - } + const velocity = this.calculateVelocity(); - // Cache rect for performance - if (!this.rect) { - this.rect = this.scrollableContent.getBoundingClientRect(); - } - - let vy = 0; - if (this.isDragging) { - const distTop = this.mouseY - this.rect.top; - const distBot = this.rect.bottom - this.mouseY; - - // Check top edge - if (distTop < this.INNER_ZONE) { - vy = -this.FAST_SPEED_PXS; - } else if (distTop < this.OUTER_ZONE) { - vy = -this.SLOW_SPEED_PXS; - } - // Check bottom edge - else if (distBot < this.INNER_ZONE) { - vy = this.FAST_SPEED_PXS; - } else if (distBot < this.OUTER_ZONE) { - vy = this.SLOW_SPEED_PXS; - } - } - - if (vy !== 0 && this.isDragging && this.timeGrid && this.draggedElement) { - const currentScrollTop = this.scrollableContent.scrollTop; - const cloneRect = this.draggedElement.getBoundingClientRect(); - const cloneBottom = cloneRect.bottom; - const timeGridRect = this.timeGrid.getBoundingClientRect(); - const timeGridBottom = timeGridRect.bottom; - - // Check boundaries - const atTop = currentScrollTop <= 0 && vy < 0; - const atBottom = (cloneBottom >= timeGridBottom) && vy > 0; - - if (atTop || atBottom) { - if (this.isScrolling) { - this.isScrolling = false; - this.initialScrollTop = this.scrollableContent.scrollTop; - this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); - } - - if (this.isDragging) { - this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); - } - } else { - // Apply scroll - const scrollDelta = vy * dt; - this.scrollableContent.scrollTop += scrollDelta; - this.rect = null; - - // Emit tick for DragDropManager compensation - this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); - - this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); - } + if (velocity !== 0 && !this.isAtBoundary(velocity)) { + const scrollDelta = velocity * dt; + this.scrollableContent.scrollTop += scrollDelta; + this.rect = null; + this.eventBus.emit(CoreEvents.EDGE_SCROLL_TICK, { scrollDelta }); + this.setScrollingState(true); } else { - if (this.isScrolling) { - this.isScrolling = false; - this.initialScrollTop = this.scrollableContent.scrollTop; - this.eventBus.emit(CoreEvents.EDGE_SCROLL_STOPPED, {}); - } - - if (this.isDragging) { - this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); - } else { - this.stopDrag(); - } + this.setScrollingState(false); } - } + + this.scrollRAF = requestAnimationFrame(this.scrollTick); + }; }