Improves drag and drop functionality
Enhances drag and drop behavior by introducing free positioning during auto-scroll and snapping to grid intervals. - Introduces a `calculateFreePosition` method to allow events to follow the mouse exactly during auto-scroll. - Modifies the drag move event to emit the snapped position during normal drag behavior. - Updates event rendering to use grid settings for snap intervals. - Updates grid styles to configure CSS variables dynamically.
This commit is contained in:
parent
b4d758b6d9
commit
7a1c776bc1
4 changed files with 50 additions and 25 deletions
|
|
@ -47,7 +47,7 @@ export class DragDropManager {
|
||||||
|
|
||||||
// Snap configuration
|
// Snap configuration
|
||||||
private snapIntervalMinutes = 15; // Default 15 minutes
|
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
|
// Event listener references for proper cleanup
|
||||||
private boundHandlers = {
|
private boundHandlers = {
|
||||||
|
|
@ -66,6 +66,7 @@ export class DragDropManager {
|
||||||
// Get config values
|
// Get config values
|
||||||
const gridSettings = calendarConfig.getGridSettings();
|
const gridSettings = calendarConfig.getGridSettings();
|
||||||
this.hourHeightPx = gridSettings.hourHeight;
|
this.hourHeightPx = gridSettings.hourHeight;
|
||||||
|
this.snapIntervalMinutes = gridSettings.snapInterval;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
@ -164,14 +165,14 @@ export class DragDropManager {
|
||||||
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
||||||
const deltaY = Math.abs(currentPosition.y - this.lastLoggedPosition.y);
|
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) {
|
if (deltaY >= this.snapDistancePx) {
|
||||||
this.lastLoggedPosition = currentPosition;
|
this.lastLoggedPosition = currentPosition;
|
||||||
|
|
||||||
// Consolidated position calculations
|
// Consolidated position calculations with snapping for normal drag
|
||||||
const positionData = this.calculateDragPosition(currentPosition);
|
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', {
|
this.eventBus.emit('drag:move', {
|
||||||
eventId: this.draggedEventId,
|
eventId: this.draggedEventId,
|
||||||
mousePosition: currentPosition,
|
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 {
|
private calculateSnapPosition(mouseY: number, column: string | null = null): number {
|
||||||
const targetColumn = column || this.currentColumn;
|
const targetColumn = column || this.currentColumn;
|
||||||
|
|
@ -371,14 +389,13 @@ export class DragDropManager {
|
||||||
const columnElement = this.getCachedColumnElement(this.currentColumn);
|
const columnElement = this.getCachedColumnElement(this.currentColumn);
|
||||||
if (columnElement) {
|
if (columnElement) {
|
||||||
const columnRect = columnElement.getBoundingClientRect();
|
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 relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
||||||
const snappedY = Math.round(relativeY / this.snapDistancePx) * this.snapDistancePx;
|
const freeY = Math.max(0, relativeY);
|
||||||
const finalSnappedY = Math.max(0, snappedY);
|
|
||||||
|
|
||||||
this.eventBus.emit('drag:auto-scroll', {
|
this.eventBus.emit('drag:auto-scroll', {
|
||||||
eventId: this.draggedEventId,
|
eventId: this.draggedEventId,
|
||||||
snappedY: finalSnappedY,
|
snappedY: freeY, // Actually free position during scroll
|
||||||
scrollTop: this.cachedElements.scrollContainer.scrollTop
|
scrollTop: this.cachedElements.scrollContainer.scrollTop
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
||||||
import { GridRenderer } from '../renderers/GridRenderer';
|
import { GridRenderer } from '../renderers/GridRenderer';
|
||||||
|
import { GridStyleManager } from '../renderers/GridStyleManager';
|
||||||
import { DateCalculator } from '../utils/DateCalculator';
|
import { DateCalculator } from '../utils/DateCalculator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,11 +20,13 @@ export class GridManager {
|
||||||
private resourceData: ResourceCalendarData | null = null;
|
private resourceData: ResourceCalendarData | null = null;
|
||||||
private currentView: CalendarView = 'week';
|
private currentView: CalendarView = 'week';
|
||||||
private gridRenderer: GridRenderer;
|
private gridRenderer: GridRenderer;
|
||||||
|
private styleManager: GridStyleManager;
|
||||||
private eventCleanup: (() => void)[] = [];
|
private eventCleanup: (() => void)[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Initialize GridRenderer with config
|
// Initialize GridRenderer and StyleManager with config
|
||||||
this.gridRenderer = new GridRenderer();
|
this.gridRenderer = new GridRenderer();
|
||||||
|
this.styleManager = new GridStyleManager();
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +88,9 @@ export class GridManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update CSS variables first
|
||||||
|
this.styleManager.updateGridStyles(this.resourceData);
|
||||||
|
|
||||||
// Delegate to GridRenderer with current view context
|
// Delegate to GridRenderer with current view context
|
||||||
this.gridRenderer.renderGrid(
|
this.gridRenderer.renderGrid(
|
||||||
this.container,
|
this.container,
|
||||||
|
|
|
||||||
|
|
@ -159,25 +159,26 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
const gridSettings = calendarConfig.getGridSettings();
|
const gridSettings = calendarConfig.getGridSettings();
|
||||||
const hourHeight = gridSettings.hourHeight;
|
const hourHeight = gridSettings.hourHeight;
|
||||||
const dayStartHour = gridSettings.dayStartHour;
|
const dayStartHour = gridSettings.dayStartHour;
|
||||||
const snapInterval = 15; // TODO: Get from config
|
const snapInterval = gridSettings.snapInterval;
|
||||||
|
|
||||||
// Calculate total minutes from top
|
// Calculate minutes from grid start (not from midnight)
|
||||||
const totalMinutesFromTop = (snappedY / hourHeight) * 60;
|
const minutesFromGridStart = (snappedY / hourHeight) * 60;
|
||||||
const startTotalMinutes = Math.max(
|
|
||||||
dayStartHour * 60,
|
// Add dayStartHour offset to get actual time
|
||||||
Math.round((dayStartHour * 60 + totalMinutesFromTop) / snapInterval) * snapInterval
|
const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart;
|
||||||
);
|
|
||||||
|
// Snap to interval
|
||||||
|
const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval;
|
||||||
|
|
||||||
// Use cached original duration (no recalculation)
|
// Use cached original duration (no recalculation)
|
||||||
const cachedDuration = parseInt(clone.dataset.originalDuration || '60');
|
const cachedDuration = parseInt(clone.dataset.originalDuration || '60');
|
||||||
const endTotalMinutes = startTotalMinutes + cachedDuration;
|
const endTotalMinutes = snappedStartMinutes + cachedDuration;
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
const timeElement = clone.querySelector('swp-event-time');
|
const timeElement = clone.querySelector('swp-event-time');
|
||||||
if (timeElement) {
|
if (timeElement) {
|
||||||
const newTimeText = `${this.formatTime(startTotalMinutes)} - ${this.formatTime(endTotalMinutes)}`;
|
const newTimeText = `${this.formatTime(snappedStartMinutes)} - ${this.formatTime(endTotalMinutes)}`;
|
||||||
timeElement.textContent = newTimeText;
|
timeElement.textContent = newTimeText;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,19 +620,20 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
const dayStartHour = gridSettings.dayStartHour;
|
const dayStartHour = gridSettings.dayStartHour;
|
||||||
const hourHeight = gridSettings.hourHeight;
|
const hourHeight = gridSettings.hourHeight;
|
||||||
|
|
||||||
// Calculate minutes from visible day start
|
// Calculate minutes from midnight
|
||||||
const startMinutes = startDate.getHours() * 60 + startDate.getMinutes();
|
const startMinutes = startDate.getHours() * 60 + startDate.getMinutes();
|
||||||
const endMinutes = endDate.getHours() * 60 + endDate.getMinutes();
|
const endMinutes = endDate.getHours() * 60 + endDate.getMinutes();
|
||||||
const dayStartMinutes = dayStartHour * 60;
|
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;
|
const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight;
|
||||||
|
|
||||||
// Calculate height
|
// Calculate height based on event duration
|
||||||
const durationMinutes = endMinutes - startMinutes;
|
const durationMinutes = endMinutes - startMinutes;
|
||||||
const height = (durationMinutes / 60) * hourHeight;
|
const height = (durationMinutes / 60) * hourHeight;
|
||||||
|
|
||||||
|
|
||||||
return { top, height };
|
return { top, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ swp-hour-marker {
|
||||||
swp-hour-marker::before {
|
swp-hour-marker::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: -1px;
|
||||||
left: 50px;
|
left: 50px;
|
||||||
width: calc(100vw - 60px); /* Full viewport width minus time-axis width */
|
width: calc(100vw - 60px); /* Full viewport width minus time-axis width */
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue