diff --git a/src/app/ui/swipe-gesture/pan.directive.ts b/src/app/ui/swipe-gesture/pan.directive.ts index 4ccec2a88b..f100d9ceb0 100644 --- a/src/app/ui/swipe-gesture/pan.directive.ts +++ b/src/app/ui/swipe-gesture/pan.directive.ts @@ -1,4 +1,12 @@ -import { Directive, HostListener, output, input, NgZone, inject } from '@angular/core'; +import { + Directive, + HostListener, + output, + input, + NgZone, + inject, + OnDestroy, +} from '@angular/core'; export interface PanEvent { deltaX: number; @@ -18,7 +26,7 @@ export interface PanEvent { selector: '[panGesture]', standalone: true, }) -export class PanDirective { +export class PanDirective implements OnDestroy { readonly panstart = output(); readonly panend = output(); readonly panleft = output(); @@ -32,9 +40,79 @@ export class PanDirective { private startTime = 0; private isPanning = false; private touchIdentifier: number | null = null; + private isScrolling = false; + private scrollTimeout: any = null; + private scrollListener: (() => void) | null = null; + private scrollableParent: HTMLElement | null = null; private readonly ngZone = inject(NgZone); + ngOnDestroy(): void { + this.cleanup(); + } + + private cleanup(): void { + // Clean up any pending timeout when directive is destroyed + if (this.scrollTimeout) { + clearTimeout(this.scrollTimeout); + this.scrollTimeout = null; + } + + // Remove scroll listener if attached + if (this.scrollListener && this.scrollableParent) { + this.scrollableParent.removeEventListener('scroll', this.scrollListener); + this.scrollListener = null; + this.scrollableParent = null; + } + } + + private findScrollableParent(element: HTMLElement): HTMLElement | null { + let parent = element.parentElement; + while (parent) { + const style = window.getComputedStyle(parent); + if ( + style.overflow === 'auto' || + style.overflow === 'scroll' || + style.overflowY === 'auto' || + style.overflowY === 'scroll' + ) { + return parent; + } + parent = parent.parentElement; + } + return document.documentElement; // Fallback to document scroll + } + + private attachScrollListener(element: HTMLElement): void { + // Only attach if we haven't already + if (this.scrollListener) { + return; + } + + this.scrollableParent = this.findScrollableParent(element); + if (!this.scrollableParent) { + return; + } + + this.scrollListener = () => { + this.isScrolling = true; + + // Clear any existing timeout + if (this.scrollTimeout) { + clearTimeout(this.scrollTimeout); + } + + // Set scrolling to false after scroll stops + this.scrollTimeout = setTimeout(() => { + this.isScrolling = false; + }, 150); + }; + + this.scrollableParent.addEventListener('scroll', this.scrollListener, { + passive: true, + }); + } + @HostListener('touchstart', ['$event']) onTouchStart(event: TouchEvent): void { // Reset if we have a stale touchIdentifier @@ -47,6 +125,9 @@ export class PanDirective { return; } + // Attach scroll listener on demand when touch starts + this.attachScrollListener(event.target as HTMLElement); + this.touchIdentifier = touch.identifier; this.startX = touch.clientX; this.startY = touch.clientY; @@ -77,12 +158,24 @@ export class PanDirective { const currentY = touch.clientY; const deltaX = currentX - this.startX; const deltaY = currentY - this.startY; - const xSquared = deltaX * deltaX; - const ySquared = deltaY * deltaY; - const distance = Math.sqrt(xSquared + ySquared); + const absX = Math.abs(deltaX); + const absY = Math.abs(deltaY); + + // Don't start panning if scrolling is detected + if (this.isScrolling && !this.isPanning) { + return; + } // Check if we should start panning - if (!this.isPanning && distance >= this.panThreshold()) { + // Only start panning for primarily horizontal movements + // Use a higher threshold for horizontal movement and ensure it's more horizontal than vertical + if (!this.isPanning && absX >= this.panThreshold() && absX > absY * 1.5) { + // Additional check: if vertical movement is significant, likely scrolling + if (absY > 20) { + this.isScrolling = true; + return; + } + this.isPanning = true; // Emit panstart when we first start panning @@ -98,10 +191,6 @@ export class PanDirective { const panEvent = this.createPanEvent(event, deltaX, deltaY, false, 2); // eventType: 2 = move - // Determine direction and emit events for horizontal pan - const absX = Math.abs(deltaX); - const absY = Math.abs(deltaY); - // Emit continuous pan events // Run in Angular zone for event emission this.ngZone.run(() => { @@ -163,6 +252,10 @@ export class PanDirective { this.startTime = 0; this.isPanning = false; this.touchIdentifier = null; + this.isScrolling = false; + + // Clean up scroll listener when touch interaction ends + this.cleanup(); } private createPanEvent(