Improves drag-drop event system with type safety
Introduces dedicated TypeScript interfaces for all drag-and-drop event payloads, enhancing type safety and developer experience. Centralizes drag event detection and emission within `DragDropManager`. Refactors `AllDayManager`, `HeaderManager`, and `EventRendererManager` to subscribe to these typed events, improving decoupling and clarifying responsibilities. Resolves known inconsistencies in drag event payloads, especially for all-day event conversions. Adds a comprehensive analysis document (`docs/EventSystem-Analysis.md`) detailing the event system and planned improvements.
This commit is contained in:
parent
b4f5b29da3
commit
c7dcfbbaed
7 changed files with 583 additions and 410 deletions
|
|
@ -6,7 +6,7 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
|||
import { EventManager } from '../managers/EventManager';
|
||||
import { EventRendererStrategy } from './EventRenderer';
|
||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||
|
||||
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload } from '../types/EventTypes';
|
||||
/**
|
||||
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
||||
* Håndterer event positioning og overlap detection
|
||||
|
|
@ -16,14 +16,16 @@ export class EventRenderingService {
|
|||
private eventManager: EventManager;
|
||||
private strategy: EventRendererStrategy;
|
||||
|
||||
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
|
||||
|
||||
constructor(eventBus: IEventBus, eventManager: EventManager) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
|
||||
|
||||
// Cache strategy at initialization
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
|
||||
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
|
|
@ -31,24 +33,24 @@ export class EventRenderingService {
|
|||
* Render events in a specific container for a given period
|
||||
*/
|
||||
public renderEvents(context: RenderContext): void {
|
||||
|
||||
|
||||
// Clear existing events in the specific container first
|
||||
this.strategy.clearEvents(context.container);
|
||||
|
||||
|
||||
// Get events from EventManager for the period
|
||||
const events = this.eventManager.getEventsForPeriod(
|
||||
context.startDate,
|
||||
context.endDate
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
if (events.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Use cached strategy to render events in the specific container
|
||||
this.strategy.renderEvents(events, context.container);
|
||||
|
||||
|
||||
// Emit EVENTS_RENDERED event for filtering system
|
||||
this.eventBus.emit(CoreEvents.EVENTS_RENDERED, {
|
||||
events: events,
|
||||
|
|
@ -57,16 +59,11 @@ export class EventRenderingService {
|
|||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
// Event-driven rendering: React to grid and container events
|
||||
|
||||
this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => {
|
||||
this.handleGridRendered(event as CustomEvent);
|
||||
});
|
||||
|
||||
// CONTAINER_READY_FOR_EVENTS removed - events are now pre-rendered synchronously
|
||||
// this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => {
|
||||
// this.handleContainerReady(event as CustomEvent);
|
||||
// });
|
||||
|
||||
this.eventBus.on(CoreEvents.VIEW_CHANGED, (event: Event) => {
|
||||
this.handleViewChanged(event as CustomEvent);
|
||||
});
|
||||
|
|
@ -93,7 +90,7 @@ export class EventRenderingService {
|
|||
*/
|
||||
private handleGridRendered(event: CustomEvent): void {
|
||||
const { container, startDate, endDate, currentDate } = event.detail;
|
||||
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -110,7 +107,7 @@ export class EventRenderingService {
|
|||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.renderEvents({
|
||||
container: container,
|
||||
startDate: periodStart,
|
||||
|
|
@ -123,7 +120,7 @@ export class EventRenderingService {
|
|||
*/
|
||||
private handleContainerReady(event: CustomEvent): void {
|
||||
const { container, startDate, endDate } = event.detail;
|
||||
|
||||
|
||||
if (!container || !startDate || !endDate) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -141,7 +138,7 @@ export class EventRenderingService {
|
|||
private handleViewChanged(event: CustomEvent): void {
|
||||
// Clear all existing events since view structure may have changed
|
||||
this.clearEvents();
|
||||
|
||||
|
||||
// New rendering will be triggered by subsequent GRID_RENDERED event
|
||||
}
|
||||
|
||||
|
|
@ -152,45 +149,49 @@ export class EventRenderingService {
|
|||
private setupDragEventListeners(): void {
|
||||
// Handle drag start
|
||||
this.eventBus.on('drag:start', (event: Event) => {
|
||||
const { eventId, mouseOffset, column } = (event as CustomEvent).detail;
|
||||
// Find element dynamically
|
||||
const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
|
||||
if (originalElement && this.strategy.handleDragStart) {
|
||||
this.strategy.handleDragStart(originalElement, eventId, mouseOffset, column);
|
||||
const { draggedElement, mouseOffset, column } = (event as CustomEvent<DragStartEventPayload>).detail;
|
||||
// Use the draggedElement directly - no need for DOM query
|
||||
if (draggedElement && this.strategy.handleDragStart && column) {
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
this.strategy.handleDragStart(draggedElement, eventId, mouseOffset, column);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag move
|
||||
this.eventBus.on('drag:move', (event: Event) => {
|
||||
const { eventId, snappedY, column, mouseOffset } = (event as CustomEvent).detail;
|
||||
if (this.strategy.handleDragMove) {
|
||||
const { draggedElement, snappedY, column, mouseOffset } = (event as CustomEvent<DragMoveEventPayload>).detail;
|
||||
if (this.strategy.handleDragMove && column) {
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
this.strategy.handleDragMove(eventId, snappedY, column, mouseOffset);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag auto-scroll
|
||||
this.eventBus.on('drag:auto-scroll', (event: Event) => {
|
||||
const { eventId, snappedY } = (event as CustomEvent).detail;
|
||||
const { draggedElement, snappedY } = (event as CustomEvent).detail;
|
||||
if (this.strategy.handleDragAutoScroll) {
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
this.strategy.handleDragAutoScroll(eventId, snappedY);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag end events and delegate to appropriate renderer
|
||||
this.eventBus.on('drag:end', (event: Event) => {
|
||||
const { eventId, finalColumn, finalY, target } = (event as CustomEvent).detail;
|
||||
|
||||
const { draggedElement, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||
const finalColumn = finalPosition.column;
|
||||
const finalY = finalPosition.snappedY;
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
|
||||
// Only handle day column drops for EventRenderer
|
||||
if (target === 'swp-day-column') {
|
||||
// Find both original element and dragged clone
|
||||
const originalElement = document.querySelector(`swp-day-column swp-event[data-event-id="${eventId}"]`) as HTMLElement;
|
||||
if (target === 'swp-day-column' && finalColumn) {
|
||||
// Find dragged clone - use draggedElement as original
|
||||
const draggedClone = document.querySelector(`swp-day-column swp-event[data-event-id="clone-${eventId}"]`) as HTMLElement;
|
||||
|
||||
if (originalElement && draggedClone && this.strategy.handleDragEnd) {
|
||||
this.strategy.handleDragEnd(eventId, originalElement, draggedClone, finalColumn, finalY);
|
||||
|
||||
if (draggedElement && draggedClone && this.strategy.handleDragEnd) {
|
||||
this.strategy.handleDragEnd(eventId, draggedElement, draggedClone, finalColumn, finalY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Clean up any remaining day event clones
|
||||
const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`);
|
||||
if (dayEventClone) {
|
||||
|
|
@ -200,22 +201,40 @@ export class EventRenderingService {
|
|||
|
||||
// Handle click (when drag threshold not reached)
|
||||
this.eventBus.on('event:click', (event: Event) => {
|
||||
const { eventId } = (event as CustomEvent).detail;
|
||||
// Find element dynamically
|
||||
const originalElement = document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement;
|
||||
if (originalElement && this.strategy.handleEventClick) {
|
||||
this.strategy.handleEventClick(eventId, originalElement);
|
||||
const { draggedElement } = (event as CustomEvent).detail;
|
||||
// Use draggedElement directly - no need for DOM query
|
||||
if (draggedElement && this.strategy.handleEventClick) {
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
this.strategy.handleEventClick(eventId, draggedElement);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle column change
|
||||
this.eventBus.on('drag:column-change', (event: Event) => {
|
||||
const { eventId, newColumn } = (event as CustomEvent).detail;
|
||||
const { draggedElement, newColumn } = (event as CustomEvent).detail;
|
||||
if (this.strategy.handleColumnChange) {
|
||||
const eventId = draggedElement.dataset.eventId || '';
|
||||
this.strategy.handleColumnChange(eventId, newColumn);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
||||
const { targetDate, mousePosition, originalElement, cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
||||
|
||||
if (cloneElement)
|
||||
cloneElement.style.display = '';
|
||||
|
||||
console.log('🚪 EventRendererManager: Received drag:mouseleave-header', {
|
||||
targetDate,
|
||||
originalElement: originalElement,
|
||||
cloneElement: cloneElement
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
||||
|
||||
// Handle navigation period change
|
||||
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
||||
// Delegate to strategy if it handles navigation
|
||||
|
|
@ -232,46 +251,46 @@ export class EventRenderingService {
|
|||
// Use the provided draggedElement directly
|
||||
const allDayClone = draggedElement;
|
||||
const draggedEventId = draggedElement?.dataset.eventId?.replace('clone-', '') || '';
|
||||
|
||||
|
||||
|
||||
|
||||
// Use SwpEventElement factory to create day event from all-day event
|
||||
const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement);
|
||||
const dayElement = dayEventElement.getElement();
|
||||
|
||||
|
||||
// Remove the all-day clone - it's no longer needed since we're converting to day event
|
||||
allDayClone.remove();
|
||||
|
||||
|
||||
// Set clone ID
|
||||
dayElement.dataset.eventId = `clone-${draggedEventId}`;
|
||||
|
||||
|
||||
// Find target column
|
||||
const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`);
|
||||
if (!columnElement) {
|
||||
console.warn('EventRendererManager: Target column not found', { column });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Find events layer in the column
|
||||
const eventsLayer = columnElement.querySelector('swp-events-layer');
|
||||
if (!eventsLayer) {
|
||||
console.warn('EventRendererManager: Events layer not found in column');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Add to events layer
|
||||
eventsLayer.appendChild(dayElement);
|
||||
|
||||
|
||||
// Position based on mouse Y coordinate
|
||||
const columnRect = columnElement.getBoundingClientRect();
|
||||
const relativeY = Math.max(0, mousePosition.y - columnRect.top);
|
||||
dayElement.style.top = `${relativeY}px`;
|
||||
|
||||
|
||||
// Set drag styling
|
||||
dayElement.style.zIndex = '1000';
|
||||
dayElement.style.cursor = 'grabbing';
|
||||
dayElement.style.opacity = '';
|
||||
dayElement.style.transform = '';
|
||||
|
||||
|
||||
console.log('✅ EventRendererManager: Converted all-day event to time event', {
|
||||
draggedEventId,
|
||||
column,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue