fix: allow vertical scroll for new pan handler

This commit is contained in:
Johannes Millan 2025-08-10 15:03:29 +02:00
parent 5dfbbd80be
commit ea4a0afcec

View file

@ -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<PanEvent>();
readonly panend = output<void>();
readonly panleft = output<PanEvent>();
@ -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(