diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 5e25058..ae6619b 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -47,7 +47,7 @@ export class DragDropManager { // Snap configuration private snapIntervalMinutes = 15; // Default 15 minutes - private hourHeightPx = 60; // From CSS --hour-height + private hourHeightPx: number; // Will be set from config // Event listener references for proper cleanup private boundHandlers = { @@ -66,6 +66,7 @@ export class DragDropManager { // Get config values const gridSettings = calendarConfig.getGridSettings(); this.hourHeightPx = gridSettings.hourHeight; + this.snapIntervalMinutes = gridSettings.snapInterval; this.init(); } @@ -164,14 +165,14 @@ export class DragDropManager { const currentPosition: Position = { x: event.clientX, y: event.clientY }; const deltaY = Math.abs(currentPosition.y - this.lastLoggedPosition.y); - // Check for snap interval vertical movement + // Check for snap interval vertical movement (normal drag behavior) if (deltaY >= this.snapDistancePx) { this.lastLoggedPosition = currentPosition; - // Consolidated position calculations + // Consolidated position calculations with snapping for normal drag const positionData = this.calculateDragPosition(currentPosition); - // Emit drag move event with consolidated data + // Emit drag move event with snapped position (normal behavior) this.eventBus.emit('drag:move', { eventId: this.draggedEventId, mousePosition: currentPosition, @@ -240,7 +241,24 @@ export class DragDropManager { } /** - * Optimized snap position calculation with caching + * Calculate free position (follows mouse exactly) + */ + private calculateFreePosition(mouseY: number, column: string | null = null): number { + const targetColumn = column || this.currentColumn; + + // Use cached column element if available + const columnElement = this.getCachedColumnElement(targetColumn); + if (!columnElement) return mouseY; + + const columnRect = columnElement.getBoundingClientRect(); + const relativeY = mouseY - columnRect.top - this.mouseOffset.y; + + // Return free position (no snapping) + return Math.max(0, relativeY); + } + + /** + * Optimized snap position calculation with caching (used only on drop) */ private calculateSnapPosition(mouseY: number, column: string | null = null): number { const targetColumn = column || this.currentColumn; @@ -371,14 +389,13 @@ export class DragDropManager { const columnElement = this.getCachedColumnElement(this.currentColumn); if (columnElement) { const columnRect = columnElement.getBoundingClientRect(); - // Calculate position relative to column, accounting for scroll movement + // Calculate free position relative to column, accounting for scroll movement (no snapping during scroll) const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y; - const snappedY = Math.round(relativeY / this.snapDistancePx) * this.snapDistancePx; - const finalSnappedY = Math.max(0, snappedY); + const freeY = Math.max(0, relativeY); this.eventBus.emit('drag:auto-scroll', { eventId: this.draggedEventId, - snappedY: finalSnappedY, + snappedY: freeY, // Actually free position during scroll scrollTop: this.cachedElements.scrollContainer.scrollTop }); } diff --git a/src/managers/GridManager.ts b/src/managers/GridManager.ts index 1b2c92f..f75bf75 100644 --- a/src/managers/GridManager.ts +++ b/src/managers/GridManager.ts @@ -8,6 +8,7 @@ import { calendarConfig } from '../core/CalendarConfig'; import { CoreEvents } from '../constants/CoreEvents'; import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes'; import { GridRenderer } from '../renderers/GridRenderer'; +import { GridStyleManager } from '../renderers/GridStyleManager'; import { DateCalculator } from '../utils/DateCalculator'; /** @@ -19,11 +20,13 @@ export class GridManager { private resourceData: ResourceCalendarData | null = null; private currentView: CalendarView = 'week'; private gridRenderer: GridRenderer; + private styleManager: GridStyleManager; private eventCleanup: (() => void)[] = []; constructor() { - // Initialize GridRenderer with config + // Initialize GridRenderer and StyleManager with config this.gridRenderer = new GridRenderer(); + this.styleManager = new GridStyleManager(); this.init(); } @@ -85,6 +88,9 @@ export class GridManager { return; } + // Update CSS variables first + this.styleManager.updateGridStyles(this.resourceData); + // Delegate to GridRenderer with current view context this.gridRenderer.renderGrid( this.container, diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index 494082b..eba6553 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -159,25 +159,26 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { const gridSettings = calendarConfig.getGridSettings(); const hourHeight = gridSettings.hourHeight; const dayStartHour = gridSettings.dayStartHour; - const snapInterval = 15; // TODO: Get from config + const snapInterval = gridSettings.snapInterval; - // Calculate total minutes from top - const totalMinutesFromTop = (snappedY / hourHeight) * 60; - const startTotalMinutes = Math.max( - dayStartHour * 60, - Math.round((dayStartHour * 60 + totalMinutesFromTop) / snapInterval) * snapInterval - ); + // Calculate minutes from grid start (not from midnight) + const minutesFromGridStart = (snappedY / hourHeight) * 60; + + // Add dayStartHour offset to get actual time + const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart; + + // Snap to interval + const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval; // Use cached original duration (no recalculation) const cachedDuration = parseInt(clone.dataset.originalDuration || '60'); - const endTotalMinutes = startTotalMinutes + cachedDuration; + const endTotalMinutes = snappedStartMinutes + cachedDuration; // Update display const timeElement = clone.querySelector('swp-event-time'); if (timeElement) { - const newTimeText = `${this.formatTime(startTotalMinutes)} - ${this.formatTime(endTotalMinutes)}`; + const newTimeText = `${this.formatTime(snappedStartMinutes)} - ${this.formatTime(endTotalMinutes)}`; timeElement.textContent = newTimeText; - } } @@ -619,19 +620,20 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { const dayStartHour = gridSettings.dayStartHour; const hourHeight = gridSettings.hourHeight; - // Calculate minutes from visible day start + // Calculate minutes from midnight const startMinutes = startDate.getHours() * 60 + startDate.getMinutes(); const endMinutes = endDate.getHours() * 60 + endDate.getMinutes(); const dayStartMinutes = dayStartHour * 60; - // Calculate top position (subtract day start to align with time axis) + // Calculate top position relative to visible grid start + // If dayStartHour=6 and event starts at 09:00 (540 min), then: + // top = ((540 - 360) / 60) * hourHeight = 3 * hourHeight (3 hours from grid start) const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight; - // Calculate height + // Calculate height based on event duration const durationMinutes = endMinutes - startMinutes; const height = (durationMinutes / 60) * hourHeight; - return { top, height }; } diff --git a/wwwroot/css/calendar-layout-css.css b/wwwroot/css/calendar-layout-css.css index e2708a5..f55cf55 100644 --- a/wwwroot/css/calendar-layout-css.css +++ b/wwwroot/css/calendar-layout-css.css @@ -110,7 +110,7 @@ swp-hour-marker { swp-hour-marker::before { content: ''; position: absolute; - top: 0px; + top: -1px; left: 50px; width: calc(100vw - 60px); /* Full viewport width minus time-axis width */ height: 1px;