diff --git a/src/data/mock-events.json b/src/data/mock-events.json index 4eb18d7..a04b946 100644 --- a/src/data/mock-events.json +++ b/src/data/mock-events.json @@ -2675,5 +2675,135 @@ "duration": 60, "color": "#dda15e" } + }, + { + "id": "169", + "title": "Morgen Standup", + "start": "2025-10-13T05:00:00Z", + "end": "2025-10-13T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "170", + "title": "Produktvejledning", + "start": "2025-10-13T07:00:00Z", + "end": "2025-10-13T08:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 90, + "color": "#9c27b0" + } + }, + { + "id": "171", + "title": "Team Standup", + "start": "2025-10-14T05:00:00Z", + "end": "2025-10-14T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "172", + "title": "Udviklingssession", + "start": "2025-10-14T06:00:00Z", + "end": "2025-10-14T09:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#2196f3" + } + }, + { + "id": "173", + "title": "Klient Gennemgang", + "start": "2025-10-15T11:00:00Z", + "end": "2025-10-15T12:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#795548" + } + }, + { + "id": "174", + "title": "Team Standup", + "start": "2025-10-16T05:00:00Z", + "end": "2025-10-16T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "175", + "title": "Arkitektur Workshop", + "start": "2025-10-16T10:00:00Z", + "end": "2025-10-16T13:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 180, + "color": "#009688" + } + }, + { + "id": "176", + "title": "Team Standup", + "start": "2025-10-17T05:00:00Z", + "end": "2025-10-17T05:30:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 30, + "color": "#ff5722" + } + }, + { + "id": "177", + "title": "Sprint Review", + "start": "2025-10-17T10:00:00Z", + "end": "2025-10-17T11:00:00Z", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 60, + "color": "#607d8b" + } + }, + { + "id": "178", + "title": "Weekend Kodning", + "start": "2025-10-18T06:00:00Z", + "end": "2025-10-18T10:00:00Z", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { + "duration": 240, + "color": "#3f51b5" + } } ] \ No newline at end of file diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index a3e6024..42b9708 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -18,6 +18,7 @@ import { DragColumnChangeEventPayload } from '../types/EventTypes'; import { MousePosition } from '../types/DragDropTypes'; +import { CoreEvents } from '../constants/CoreEvents'; export class DragDropManager { private eventBus: IEventBus; @@ -41,6 +42,12 @@ export class DragDropManager { // Movement threshold to distinguish click from drag private readonly dragThreshold = 5; // pixels + // Scroll compensation + private scrollableContent: HTMLElement | null = null; + private initialScrollTop = 0; + private initialCloneTop = 0; + private isScrollCompensating = false; // Track if scroll compensation is active + private scrollListener: ((e: Event) => void) | null = null; // Smooth drag animation private dragAnimationId: number | null = null; @@ -53,6 +60,8 @@ export class DragDropManager { // Get config values const gridSettings = calendarConfig.getGridSettings(); + + this.init(); } @@ -98,6 +107,9 @@ export class DragDropManager { // Initialize column bounds cache ColumnDetectionUtils.updateColumnBoundsCache(); + + + // Listen to resize events to update cache window.addEventListener('resize', () => { ColumnDetectionUtils.updateColumnBoundsCache(); @@ -108,6 +120,25 @@ export class DragDropManager { ColumnDetectionUtils.updateColumnBoundsCache(); }); + this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => { + this.handleGridRendered(event as CustomEvent); + }); + + // Listen to edge-scroll events to control scroll compensation + this.eventBus.on('edgescroll:started', () => { + this.isScrollCompensating = true; + console.log('šŸŽ¬ DragDropManager: Edge-scroll started - disabling continueDrag()'); + }); + + this.eventBus.on('edgescroll:stopped', () => { + this.isScrollCompensating = false; + console.log('šŸ›‘ DragDropManager: Edge-scroll stopped - enabling continueDrag()'); + }); + + } + private handleGridRendered(event: CustomEvent) { + this.scrollableContent = document.querySelector('swp-scrollable-content'); + this.scrollableContent!.addEventListener('scroll', this.handleScroll.bind(this), { passive: true }); } private handleMouseDown(event: MouseEvent): void { @@ -151,6 +182,8 @@ export class DragDropManager { * Optimized mouse move handler with consolidated position calculations */ private handleMouseMove(event: MouseEvent): void { + + if (this.isScrollCompensating) return; //this.currentMouseY = event.clientY; // this.lastMousePosition = { x: event.clientX, y: event.clientY }; @@ -195,6 +228,8 @@ export class DragDropManager { // Start drag this.isDragStarted = true; + + // Set high z-index on event-group if exists, otherwise on event itself const eventGroup = this.originalElement!.closest('swp-event-group'); if (eventGroup) { @@ -209,7 +244,7 @@ export class DragDropManager { const dragStartPayload: DragStartEventPayload = { originalElement: this.originalElement!, - draggedClone: this.draggedClone, + draggedClone: this.draggedClone, mousePosition: this.mouseDownPosition, mouseOffset: this.mouseOffset, columnBounds: this.currentColumn @@ -221,6 +256,7 @@ export class DragDropManager { private continueDrag(currentPosition: MousePosition): void { + if (!this.draggedClone!.hasAttribute("data-allday")) { // Calculate raw position from mouse (no snapping) const column = ColumnDetectionUtils.getColumnBounds(currentPosition); @@ -269,6 +305,7 @@ export class DragDropManager { */ private handleMouseUp(event: MouseEvent): void { this.stopDragAnimation(); + this.removeScrollListener(); if (this.originalElement) { @@ -339,6 +376,7 @@ export class DragDropManager { console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container'); this.cleanupAllClones(); + this.removeScrollListener(); this.originalElement.style.opacity = ''; this.originalElement.style.cursor = ''; @@ -415,6 +453,51 @@ export class DragDropManager { } } + /** + * Handle scroll during drag - compensate clone position + */ + private handleScroll(): void { + if (!this.isDragStarted || !this.draggedClone || !this.scrollableContent || !this.isScrollCompensating) return; + + // First time scrolling - save initial positions NOW! + + this.initialScrollTop = this.scrollableContent.scrollTop; + this.initialCloneTop = parseFloat(this.draggedClone.style.top || '0'); + + console.log('šŸ’¾ DragDropManager: Scroll compensation started', { + initialScrollTop: this.initialScrollTop, + initialCloneTop: this.initialCloneTop + }); + + + const currentScrollTop = this.scrollableContent.scrollTop; + const totalScrollDelta = currentScrollTop - this.initialScrollTop; + + // Beregn ny position baseret pĆ„ initial position + total scroll delta + const newTop = this.initialCloneTop + totalScrollDelta; + this.draggedClone.style.top = `${newTop}px`; + + console.log('šŸ“œ DragDropManager: Scroll compensation', { + initialScrollTop: this.initialScrollTop, + currentScrollTop, + totalScrollDelta, + initialCloneTop: this.initialCloneTop, + newTop + }); + } + + /** + * Remove scroll listener + */ + private removeScrollListener(): void { + if (this.scrollListener && this.scrollableContent) { + this.scrollableContent.removeEventListener('scroll', this.scrollListener); + this.scrollListener = null; + } + this.isScrollCompensating = false; + this.initialScrollTop = 0; + this.initialCloneTop = 0; + } /** * Stop drag animation diff --git a/src/managers/EdgeScrollManager.ts b/src/managers/EdgeScrollManager.ts index be043e7..eea8b0f 100644 --- a/src/managers/EdgeScrollManager.ts +++ b/src/managers/EdgeScrollManager.ts @@ -11,18 +11,15 @@ export class EdgeScrollManager { private scrollRAF: number | null = null; private mouseY = 0; private isDragging = false; + private isScrolling = false; // Track if edge-scroll is active private lastTs = 0; private rect: DOMRect | null = null; - private draggedClone: HTMLElement | null = null; - private initialScrollTop = 0; - private initialCloneTop = 0; - private scrollListener: ((e: Event) => void) | 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 + private readonly SLOW_SPEED_PXS = 80; // px/sec in outer zone + private readonly FAST_SPEED_PXS = 240; // px/sec in inner zone constructor(private eventBus: IEventBus) { this.init(); @@ -35,31 +32,26 @@ export class EdgeScrollManager { if (this.scrollableContent) { // Disable smooth scroll for instant auto-scroll this.scrollableContent.style.scrollBehavior = 'auto'; - - // Add scroll listener - this.scrollListener = this.handleScroll.bind(this); - this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true }); } }, 100); + // Listen to mousemove directly from document to always get mouse coords + document.body.addEventListener('mousemove', (e: MouseEvent) => { + if (this.isDragging) { + this.mouseY = e.clientY; + } + }); + this.subscribeToEvents(); } private subscribeToEvents(): void { // Listen to drag events from DragDropManager - this.eventBus.on('drag:start', (event: Event) => { - let customEvent = event as CustomEvent; - this.draggedClone = customEvent.detail.draggedClone; + this.eventBus.on('drag:start', () => { this.startDrag(); }); - this.eventBus.on('drag:move', (event: Event) => { - let customEvent = event as CustomEvent; - this.draggedClone = customEvent.detail.draggedClone; - this.updateMouseY(customEvent.detail.mousePosition.y); - }); - this.eventBus.on('drag:end', () => this.stopDrag()); this.eventBus.on('drag:cancelled', () => this.stopDrag()); } @@ -67,62 +59,25 @@ export class EdgeScrollManager { private startDrag(): void { console.log('šŸŽ¬ EdgeScrollManager: Starting drag'); this.isDragging = true; + this.isScrolling = false; // Reset scroll state this.lastTs = performance.now(); - // Gem initial scroll position OG clone position - this.initialScrollTop = this.scrollableContent?.scrollTop || 0; - this.initialCloneTop = parseFloat(this.draggedClone?.style.top || '0'); - - console.log('šŸ’¾ EdgeScrollManager: Saved initial state', { - initialScrollTop: this.initialScrollTop, - initialCloneTop: this.initialCloneTop - }); + // Don't save initial positions here - wait until scrolling actually starts! if (this.scrollRAF === null) { this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts)); } } - private updateMouseY(y: number): void { - // console.log('šŸ–±ļø EdgeScrollManager: updateMouseY called', { oldMouseY: this.mouseY, newMouseY: y }); - 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; + this.isScrolling = false; if (this.scrollRAF !== null) { cancelAnimationFrame(this.scrollRAF); this.scrollRAF = null; } this.rect = null; this.lastTs = 0; - this.draggedClone = null; - this.initialScrollTop = 0; - this.initialCloneTop = 0; - } - - private handleScroll(): void { - if (!this.isDragging || !this.draggedClone || !this.scrollableContent) return; - - const currentScrollTop = this.scrollableContent.scrollTop; - const totalScrollDelta = currentScrollTop - this.initialScrollTop; - - // Beregn ny position baseret pĆ„ initial position + total scroll delta - const newTop = this.initialCloneTop + totalScrollDelta; - //this.draggedClone.style.top = `${newTop}px`; - - console.log('šŸ“œ EdgeScrollManager: Scroll event - updated clone', { - initialScrollTop: this.initialScrollTop, - currentScrollTop, - totalScrollDelta, - initialCloneTop: this.initialCloneTop, - newTop - }); } private scrollTick(ts: number): void { @@ -159,11 +114,27 @@ export class EdgeScrollManager { } if (vy !== 0 && this.isDragging) { + // Mark that scrolling is active + if (!this.isScrolling) { + this.isScrolling = true; + console.log('šŸ’¾ EdgeScrollManager: Edge-scroll started'); + // Notify DragDropManager that scroll compensation should start + this.eventBus.emit('edgescroll:started', {}); + } + // 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 { + // Mouse moved away from edge - stop scrolling + if (this.isScrolling) { + this.isScrolling = false; + console.log('šŸ›‘ EdgeScrollManager: Edge-scroll stopped'); + // Notify DragDropManager that scroll compensation should stop + this.eventBus.emit('edgescroll:stopped', {}); + } + // Continue RAF loop even if not scrolling, to detect edge entry if (this.isDragging) { this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));