Enhances drag and drop functionality
Improves drag and drop event handling, including conversion between all-day and timed events. Introduces HeaderManager to handle header-related event logic and centralizes header event handling for better code organization and separation of concerns. Optimizes event listeners and throttles events for improved performance. Removes redundant code and improves the overall drag and drop experience.
This commit is contained in:
parent
d087e333fe
commit
3bd74d6f4e
6 changed files with 418 additions and 170 deletions
|
|
@ -4,6 +4,7 @@ import { calendarConfig } from '../core/CalendarConfig.js';
|
|||
import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js';
|
||||
import { EventManager } from './EventManager.js';
|
||||
import { GridManager } from './GridManager.js';
|
||||
import { HeaderManager } from './HeaderManager.js';
|
||||
import { EventRenderingService } from '../renderers/EventRendererManager.js';
|
||||
import { ScrollManager } from './ScrollManager.js';
|
||||
import { DateCalculator } from '../utils/DateCalculator.js';
|
||||
|
|
@ -17,6 +18,7 @@ export class CalendarManager {
|
|||
private eventBus: IEventBus;
|
||||
private eventManager: EventManager;
|
||||
private gridManager: GridManager;
|
||||
private headerManager: HeaderManager;
|
||||
private eventRenderer: EventRenderingService;
|
||||
private scrollManager: ScrollManager;
|
||||
private eventFilterManager: EventFilterManager;
|
||||
|
|
@ -35,6 +37,7 @@ export class CalendarManager {
|
|||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
this.gridManager = gridManager;
|
||||
this.headerManager = new HeaderManager();
|
||||
this.eventRenderer = eventRenderer;
|
||||
this.scrollManager = scrollManager;
|
||||
this.eventFilterManager = new EventFilterManager();
|
||||
|
|
@ -66,6 +69,9 @@ export class CalendarManager {
|
|||
}
|
||||
await this.gridManager.render();
|
||||
|
||||
// Step 2a: Setup header drag listeners after grid render (when DOM is available)
|
||||
this.headerManager.setupHeaderDragListeners();
|
||||
|
||||
// Step 2b: Trigger event rendering now that data is loaded
|
||||
// Re-emit GRID_RENDERED to trigger EventRendererManager
|
||||
const gridContainer = document.querySelector('swp-calendar-container');
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export class DragDropManager {
|
|||
private eventBus: IEventBus;
|
||||
|
||||
// Mouse tracking with optimized state
|
||||
private isMouseDown = false;
|
||||
private lastMousePosition: Position = { x: 0, y: 0 };
|
||||
private lastLoggedPosition: Position = { x: 0, y: 0 };
|
||||
private currentMouseY = 0;
|
||||
|
|
@ -96,7 +95,7 @@ export class DragDropManager {
|
|||
this.eventBus.on('header:mouseover', (event) => {
|
||||
const { element, targetDate, headerRenderer } = (event as CustomEvent).detail;
|
||||
|
||||
if (this.isMouseDown && this.draggedEventId && targetDate) {
|
||||
if (this.draggedEventId && targetDate) {
|
||||
// Emit event to convert to all-day
|
||||
this.eventBus.emit('drag:convert-to-allday', {
|
||||
eventId: this.draggedEventId,
|
||||
|
|
@ -106,10 +105,48 @@ export class DragDropManager {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for column mouseover events (for all-day to timed conversion)
|
||||
this.eventBus.on('column:mouseover', (event) => {
|
||||
const { targetColumn, targetY } = (event as CustomEvent).detail;
|
||||
|
||||
if ((event as any).buttons === 1 && this.draggedEventId && this.isAllDayEventBeingDragged()) {
|
||||
// Emit event to convert to timed
|
||||
this.eventBus.emit('drag:convert-to-timed', {
|
||||
eventId: this.draggedEventId,
|
||||
targetColumn,
|
||||
targetY
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for header mouseleave events (for all-day to timed conversion when leaving header)
|
||||
this.eventBus.on('header:mouseleave', (event) => {
|
||||
// Check if we're dragging an all-day event
|
||||
if ((event as any).buttons === 1 && this.draggedEventId && this.isAllDayEventBeingDragged()) {
|
||||
// Get current mouse position to determine target column and Y
|
||||
const currentColumn = this.detectColumn(this.lastMousePosition.x, this.lastMousePosition.y);
|
||||
|
||||
if (currentColumn) {
|
||||
// Calculate Y position relative to the column
|
||||
const columnElement = this.getCachedColumnElement(currentColumn);
|
||||
if (columnElement) {
|
||||
const columnRect = columnElement.getBoundingClientRect();
|
||||
const targetY = this.lastMousePosition.y - columnRect.top - this.mouseOffset.y;
|
||||
|
||||
// Emit event to convert to timed
|
||||
this.eventBus.emit('drag:convert-to-timed', {
|
||||
eventId: this.draggedEventId,
|
||||
targetColumn: currentColumn,
|
||||
targetY: Math.max(0, targetY)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleMouseDown(event: MouseEvent): void {
|
||||
this.isMouseDown = true;
|
||||
this.isDragStarted = false;
|
||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
||||
|
|
@ -160,7 +197,7 @@ export class DragDropManager {
|
|||
private handleMouseMove(event: MouseEvent): void {
|
||||
this.currentMouseY = event.clientY;
|
||||
|
||||
if (this.isMouseDown && this.draggedEventId) {
|
||||
if (event.buttons === 1 && this.draggedEventId) {
|
||||
const currentPosition: Position = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Check if we need to start drag (movement threshold)
|
||||
|
|
@ -230,23 +267,27 @@ export class DragDropManager {
|
|||
* Optimized mouse up handler with consolidated cleanup
|
||||
*/
|
||||
private handleMouseUp(event: MouseEvent): void {
|
||||
if (!this.isMouseDown) return;
|
||||
|
||||
this.isMouseDown = false;
|
||||
this.stopAutoScroll();
|
||||
|
||||
if (this.draggedEventId && this.originalElement) {
|
||||
// Store variables locally before cleanup
|
||||
const eventId = this.draggedEventId;
|
||||
const originalElement = this.originalElement;
|
||||
const isDragStarted = this.isDragStarted;
|
||||
|
||||
// Clean up drag state first
|
||||
this.cleanupDragState();
|
||||
|
||||
// Only emit drag:end if drag was actually started
|
||||
if (this.isDragStarted) {
|
||||
if (isDragStarted) {
|
||||
const finalPosition: Position = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Use consolidated position calculation
|
||||
const positionData = this.calculateDragPosition(finalPosition);
|
||||
|
||||
// Emit drag end event
|
||||
this.eventBus.emit('drag:end', {
|
||||
eventId: this.draggedEventId,
|
||||
originalElement: this.originalElement,
|
||||
eventId: eventId,
|
||||
originalElement: originalElement,
|
||||
finalPosition,
|
||||
finalColumn: positionData.column,
|
||||
finalY: positionData.snappedY
|
||||
|
|
@ -254,14 +295,11 @@ export class DragDropManager {
|
|||
} else {
|
||||
// This was just a click - emit click event instead
|
||||
this.eventBus.emit('event:click', {
|
||||
eventId: this.draggedEventId,
|
||||
originalElement: this.originalElement,
|
||||
eventId: eventId,
|
||||
originalElement: originalElement,
|
||||
mousePosition: { x: event.clientX, y: event.clientY }
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up drag state
|
||||
this.cleanupDragState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +447,7 @@ export class DragDropManager {
|
|||
if (this.autoScrollAnimationId !== null) return;
|
||||
|
||||
const scroll = () => {
|
||||
if (!this.cachedElements.scrollContainer || !this.isMouseDown) {
|
||||
if (!this.cachedElements.scrollContainer || !this.draggedEventId) {
|
||||
this.stopAutoScroll();
|
||||
return;
|
||||
}
|
||||
|
|
@ -466,6 +504,14 @@ export class DragDropManager {
|
|||
this.cachedElements.lastColumnDate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an all-day event is currently being dragged
|
||||
*/
|
||||
private isAllDayEventBeingDragged(): boolean {
|
||||
if (!this.originalElement) return false;
|
||||
return this.originalElement.tagName === 'SWP-ALLDAY-EVENT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all resources and event listeners
|
||||
*/
|
||||
|
|
|
|||
139
src/managers/HeaderManager.ts
Normal file
139
src/managers/HeaderManager.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
||||
|
||||
/**
|
||||
* HeaderManager - Handles all header-related event logic
|
||||
* Separates event handling from rendering concerns
|
||||
*/
|
||||
export class HeaderManager {
|
||||
private headerEventListener: ((event: Event) => void) | null = null;
|
||||
private headerMouseLeaveListener: ((event: Event) => void) | null = null;
|
||||
private cachedCalendarHeader: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
// Bind methods for event listeners
|
||||
this.setupHeaderDragListeners = this.setupHeaderDragListeners.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached calendar header element
|
||||
*/
|
||||
private getCalendarHeader(): HTMLElement | null {
|
||||
if (!this.cachedCalendarHeader) {
|
||||
this.cachedCalendarHeader = document.querySelector('swp-calendar-header');
|
||||
}
|
||||
return this.cachedCalendarHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup header drag event listeners
|
||||
*/
|
||||
public setupHeaderDragListeners(): void {
|
||||
const calendarHeader = this.getCalendarHeader();
|
||||
if (!calendarHeader) return;
|
||||
|
||||
// Clean up existing listeners first
|
||||
this.removeEventListeners();
|
||||
|
||||
// Throttle for better performance
|
||||
let lastEmitTime = 0;
|
||||
const throttleDelay = 16; // ~60fps
|
||||
|
||||
this.headerEventListener = (event: Event) => {
|
||||
const now = Date.now();
|
||||
if (now - lastEmitTime < throttleDelay) {
|
||||
return; // Throttle events for better performance
|
||||
}
|
||||
lastEmitTime = now;
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// Optimized element detection
|
||||
const dayHeader = target.closest('swp-day-header');
|
||||
const allDayContainer = target.closest('swp-allday-container');
|
||||
|
||||
if (dayHeader || allDayContainer) {
|
||||
let hoveredElement: HTMLElement;
|
||||
let targetDate: string | undefined;
|
||||
|
||||
if (dayHeader) {
|
||||
hoveredElement = dayHeader as HTMLElement;
|
||||
targetDate = hoveredElement.dataset.date;
|
||||
} else if (allDayContainer) {
|
||||
hoveredElement = allDayContainer as HTMLElement;
|
||||
|
||||
// Optimized day calculation using cached header rect
|
||||
const headerRect = calendarHeader.getBoundingClientRect();
|
||||
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
||||
const mouseX = (event as MouseEvent).clientX - headerRect.left;
|
||||
const dayWidth = headerRect.width / dayHeaders.length;
|
||||
const dayIndex = Math.max(0, Math.min(dayHeaders.length - 1, Math.floor(mouseX / dayWidth)));
|
||||
|
||||
const targetDayHeader = dayHeaders[dayIndex] as HTMLElement;
|
||||
targetDate = targetDayHeader?.dataset.date;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get header renderer for coordination
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
|
||||
|
||||
eventBus.emit('header:mouseover', {
|
||||
element: hoveredElement,
|
||||
targetDate,
|
||||
headerRenderer
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Header mouseleave listener
|
||||
this.headerMouseLeaveListener = (event: Event) => {
|
||||
eventBus.emit('header:mouseleave', {
|
||||
element: event.target as HTMLElement
|
||||
});
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
calendarHeader.addEventListener('mouseover', this.headerEventListener);
|
||||
calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listeners from header
|
||||
*/
|
||||
private removeEventListeners(): void {
|
||||
const calendarHeader = this.getCalendarHeader();
|
||||
if (!calendarHeader) return;
|
||||
|
||||
if (this.headerEventListener) {
|
||||
calendarHeader.removeEventListener('mouseover', this.headerEventListener);
|
||||
}
|
||||
|
||||
if (this.headerMouseLeaveListener) {
|
||||
calendarHeader.removeEventListener('mouseleave', this.headerMouseLeaveListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached header reference
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this.cachedCalendarHeader = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources and event listeners
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.removeEventListeners();
|
||||
|
||||
// Clear references
|
||||
this.headerEventListener = null;
|
||||
this.headerMouseLeaveListener = null;
|
||||
|
||||
this.clearCache();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue