mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
fix: allow vertical scroll for new pan handler
This commit is contained in:
parent
5dfbbd80be
commit
ea4a0afcec
1 changed files with 103 additions and 10 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue