Implements edge scrolling functionality
Adds edge scrolling to automatically scroll the calendar when dragging an event near the edges of the view. This improves the drag-and-drop experience by allowing users to move events beyond the visible area. Removes auto-scroll logic from the event renderer, centralizing the scrolling behavior within the new edge scroll manager.
This commit is contained in:
parent
40b19a092c
commit
8df1f6c4f1
5 changed files with 139 additions and 25 deletions
|
|
@ -9,6 +9,7 @@ import { CalendarManager } from '../managers/CalendarManager';
|
||||||
import { DragDropManager } from '../managers/DragDropManager';
|
import { DragDropManager } from '../managers/DragDropManager';
|
||||||
import { AllDayManager } from '../managers/AllDayManager';
|
import { AllDayManager } from '../managers/AllDayManager';
|
||||||
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
|
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
|
||||||
|
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
|
||||||
import { CalendarManagers } from '../types/ManagerTypes';
|
import { CalendarManagers } from '../types/ManagerTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,6 +42,7 @@ export class ManagerFactory {
|
||||||
const dragDropManager = new DragDropManager(eventBus);
|
const dragDropManager = new DragDropManager(eventBus);
|
||||||
const allDayManager = new AllDayManager(eventManager);
|
const allDayManager = new AllDayManager(eventManager);
|
||||||
const resizeHandleManager = new ResizeHandleManager();
|
const resizeHandleManager = new ResizeHandleManager();
|
||||||
|
const edgeScrollManager = new EdgeScrollManager(eventBus);
|
||||||
|
|
||||||
// CalendarManager depends on all other managers
|
// CalendarManager depends on all other managers
|
||||||
const calendarManager = new CalendarManager(
|
const calendarManager = new CalendarManager(
|
||||||
|
|
@ -62,7 +64,8 @@ export class ManagerFactory {
|
||||||
calendarManager,
|
calendarManager,
|
||||||
dragDropManager,
|
dragDropManager,
|
||||||
allDayManager,
|
allDayManager,
|
||||||
resizeHandleManager
|
resizeHandleManager,
|
||||||
|
edgeScrollManager
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
134
src/managers/EdgeScrollManager.ts
Normal file
134
src/managers/EdgeScrollManager.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/**
|
||||||
|
* EdgeScrollManager - Auto-scroll when dragging near edges
|
||||||
|
* Uses time-based scrolling with 2-zone system for variable speed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
|
import { DragMoveEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
|
export class EdgeScrollManager {
|
||||||
|
private scrollableContent: HTMLElement | null = null;
|
||||||
|
private scrollRAF: number | null = null;
|
||||||
|
private mouseY = 0;
|
||||||
|
private isDragging = false;
|
||||||
|
private lastTs = 0;
|
||||||
|
private rect: DOMRect | null = null;
|
||||||
|
|
||||||
|
// Constants - fixed values as per requirements
|
||||||
|
private readonly OUTER_ZONE = 100; // px from edge (slow zone)
|
||||||
|
private readonly INNER_ZONE = 50; // px from edge (fast zone)
|
||||||
|
private readonly SLOW_SPEED_PXS = 800; // px/sec in outer zone
|
||||||
|
private readonly FAST_SPEED_PXS = 2400; // px/sec in inner zone
|
||||||
|
|
||||||
|
constructor(private eventBus: IEventBus) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(): void {
|
||||||
|
// Wait for DOM to be ready
|
||||||
|
setTimeout(() => {
|
||||||
|
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
||||||
|
if (this.scrollableContent) {
|
||||||
|
// Disable smooth scroll for instant auto-scroll
|
||||||
|
this.scrollableContent.style.scrollBehavior = 'auto';
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
this.subscribeToEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribeToEvents(): void {
|
||||||
|
// Listen to drag events from DragDropManager
|
||||||
|
this.eventBus.on('drag:start', () => this.startDrag());
|
||||||
|
this.eventBus.on('drag:move', (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent<DragMoveEventPayload>;
|
||||||
|
this.updateMouseY(customEvent.detail.mousePosition.y);
|
||||||
|
});
|
||||||
|
this.eventBus.on('drag:end', () => this.stopDrag());
|
||||||
|
this.eventBus.on('drag:cancelled', () => this.stopDrag());
|
||||||
|
}
|
||||||
|
|
||||||
|
private startDrag(): void {
|
||||||
|
console.log('🎬 EdgeScrollManager: Starting drag');
|
||||||
|
this.isDragging = true;
|
||||||
|
this.lastTs = performance.now();
|
||||||
|
if (this.scrollRAF === null) {
|
||||||
|
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateMouseY(y: number): void {
|
||||||
|
this.mouseY = y;
|
||||||
|
// Ensure RAF loop is running during drag
|
||||||
|
if (this.isDragging && this.scrollRAF === null) {
|
||||||
|
this.lastTs = performance.now();
|
||||||
|
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopDrag(): void {
|
||||||
|
this.isDragging = false;
|
||||||
|
if (this.scrollRAF !== null) {
|
||||||
|
cancelAnimationFrame(this.scrollRAF);
|
||||||
|
this.scrollRAF = null;
|
||||||
|
}
|
||||||
|
this.rect = null;
|
||||||
|
this.lastTs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scrollTick(ts: number): void {
|
||||||
|
const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0;
|
||||||
|
this.lastTs = ts;
|
||||||
|
|
||||||
|
if (!this.scrollableContent) {
|
||||||
|
this.stopDrag();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache rect for performance (only measure once per frame)
|
||||||
|
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) {
|
||||||
|
// Inner zone (0-50px) - fast speed
|
||||||
|
vy = -this.FAST_SPEED_PXS;
|
||||||
|
console.log('⬆️ EdgeScrollManager: Top FAST', { distTop, vy });
|
||||||
|
} else if (distTop < this.OUTER_ZONE) {
|
||||||
|
// Outer zone (50-100px) - slow speed
|
||||||
|
vy = -this.SLOW_SPEED_PXS;
|
||||||
|
console.log('⬆️ EdgeScrollManager: Top SLOW', { distTop, vy });
|
||||||
|
}
|
||||||
|
// Check bottom edge
|
||||||
|
else if (distBot < this.INNER_ZONE) {
|
||||||
|
// Inner zone (0-50px) - fast speed
|
||||||
|
vy = this.FAST_SPEED_PXS;
|
||||||
|
console.log('⬇️ EdgeScrollManager: Bottom FAST', { distBot, vy });
|
||||||
|
} else if (distBot < this.OUTER_ZONE) {
|
||||||
|
// Outer zone (50-100px) - slow speed
|
||||||
|
vy = this.SLOW_SPEED_PXS;
|
||||||
|
console.log('⬇️ EdgeScrollManager: Bottom SLOW', { distBot, vy });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vy !== 0 && this.isDragging) {
|
||||||
|
// Time-based scrolling for frame-rate independence
|
||||||
|
this.scrollableContent.scrollTop += vy * dt;
|
||||||
|
this.rect = null; // Invalidate cache for next frame
|
||||||
|
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||||
|
} else {
|
||||||
|
// Continue RAF loop even if not scrolling, to detect edge entry
|
||||||
|
if (this.isDragging) {
|
||||||
|
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||||
|
} else {
|
||||||
|
this.stopDrag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -97,19 +97,6 @@ export class DateEventRenderer implements EventRendererStrategy {
|
||||||
swpEvent.updatePosition(columnDate, payload.snappedY);
|
swpEvent.updatePosition(columnDate, payload.snappedY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle drag auto-scroll event
|
|
||||||
*/
|
|
||||||
public handleDragAutoScroll(eventId: string, snappedY: number): void {
|
|
||||||
if (!this.draggedClone) return;
|
|
||||||
|
|
||||||
// Update position directly using the calculated snapped position
|
|
||||||
this.draggedClone.style.top = snappedY + 'px';
|
|
||||||
|
|
||||||
// Update timestamp display
|
|
||||||
//this.updateCloneTimestamp(this.draggedClone, snappedY); //TODO: Commented as, we need to move all this scroll logic til scroll manager away from eventrenderer
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle column change during drag
|
* Handle column change during drag
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ export class EventRenderingService {
|
||||||
private setupDragEventListeners(): void {
|
private setupDragEventListeners(): void {
|
||||||
this.setupDragStartListener();
|
this.setupDragStartListener();
|
||||||
this.setupDragMoveListener();
|
this.setupDragMoveListener();
|
||||||
this.setupDragAutoScrollListener();
|
|
||||||
this.setupDragEndListener();
|
this.setupDragEndListener();
|
||||||
this.setupDragColumnChangeListener();
|
this.setupDragColumnChangeListener();
|
||||||
this.setupDragMouseLeaveHeaderListener();
|
this.setupDragMouseLeaveHeaderListener();
|
||||||
|
|
@ -162,16 +161,6 @@ export class EventRenderingService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupDragAutoScrollListener(): void {
|
|
||||||
this.eventBus.on('drag:auto-scroll', (event: Event) => {
|
|
||||||
const { draggedElement, snappedY } = (event as CustomEvent).detail;
|
|
||||||
if (this.strategy.handleDragAutoScroll) {
|
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
|
||||||
this.strategy.handleDragAutoScroll(eventId, snappedY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupDragEndListener(): void {
|
private setupDragEndListener(): void {
|
||||||
this.eventBus.on('drag:end', (event: Event) => {
|
this.eventBus.on('drag:end', (event: Event) => {
|
||||||
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export interface CalendarManagers {
|
||||||
dragDropManager: unknown; // Avoid interface conflicts
|
dragDropManager: unknown; // Avoid interface conflicts
|
||||||
allDayManager: unknown; // Avoid interface conflicts
|
allDayManager: unknown; // Avoid interface conflicts
|
||||||
resizeHandleManager: ResizeHandleManager;
|
resizeHandleManager: ResizeHandleManager;
|
||||||
|
edgeScrollManager: unknown; // Avoid interface conflicts
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue