// Event rendering strategy interface and implementations import { SwpEventElement } from '../elements/SwpEventElement'; /** * Date-based event renderer */ export class DateEventRenderer { constructor(dateService, stackManager, layoutCoordinator, config, positionUtils) { this.draggedClone = null; this.originalEvent = null; this.dateService = dateService; this.stackManager = stackManager; this.layoutCoordinator = layoutCoordinator; this.config = config; this.positionUtils = positionUtils; } applyDragStyling(element) { element.classList.add('dragging'); element.style.removeProperty("margin-left"); } /** * Handle drag start event */ handleDragStart(payload) { this.originalEvent = payload.originalElement; ; // Use the clone from the payload instead of creating a new one this.draggedClone = payload.draggedClone; if (this.draggedClone && payload.columnBounds) { // Apply drag styling this.applyDragStyling(this.draggedClone); // Add to current column's events layer (not directly to column) const eventsLayer = payload.columnBounds.element.querySelector('swp-events-layer'); if (eventsLayer) { eventsLayer.appendChild(this.draggedClone); // Set initial position to prevent "jump to top" effect // Calculate absolute Y position from original element const originalRect = this.originalEvent.getBoundingClientRect(); const columnRect = payload.columnBounds.boundingClientRect; const initialTop = originalRect.top - columnRect.top; this.draggedClone.style.top = `${initialTop}px`; } } // Make original semi-transparent this.originalEvent.style.opacity = '0.3'; this.originalEvent.style.userSelect = 'none'; } /** * Handle drag move event */ handleDragMove(payload) { const swpEvent = payload.draggedClone; const columnDate = this.dateService.parseISO(payload.columnBounds.date); swpEvent.updatePosition(columnDate, payload.snappedY); } /** * Handle column change during drag */ handleColumnChange(payload) { const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer'); if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) { eventsLayer.appendChild(payload.draggedClone); // Recalculate timestamps with new column date const currentTop = parseFloat(payload.draggedClone.style.top) || 0; const swpEvent = payload.draggedClone; const columnDate = this.dateService.parseISO(payload.newColumn.date); swpEvent.updatePosition(columnDate, currentTop); } } /** * Handle conversion of all-day event to timed event */ handleConvertAllDayToTimed(payload) { console.log('🎯 DateEventRenderer: Converting all-day to timed event', { eventId: payload.calendarEvent.id, targetColumn: payload.targetColumn.date, snappedY: payload.snappedY }); let timedClone = SwpEventElement.fromCalendarEvent(payload.calendarEvent); let position = this.calculateEventPosition(payload.calendarEvent); // Set position at snapped Y //timedClone.style.top = `${snappedY}px`; // Set complete styling for dragged clone (matching normal event rendering) timedClone.style.height = `${position.height - 3}px`; timedClone.style.left = '2px'; timedClone.style.right = '2px'; timedClone.style.width = 'auto'; timedClone.style.pointerEvents = 'none'; // Apply drag styling this.applyDragStyling(timedClone); // Find the events layer in the target column let eventsLayer = payload.targetColumn.element.querySelector('swp-events-layer'); // Add "clone-" prefix to match clone ID pattern //timedClone.dataset.eventId = `clone-${payload.calendarEvent.id}`; // Remove old all-day clone and replace with new timed clone payload.draggedClone.remove(); payload.replaceClone(timedClone); eventsLayer.appendChild(timedClone); } /** * Handle drag end event */ handleDragEnd(originalElement, draggedClone, finalColumn, finalY) { if (!draggedClone || !originalElement) { console.warn('Missing draggedClone or originalElement'); return; } // Only fade out and remove if it's a swp-event (not swp-allday-event) // AllDayManager handles removal of swp-allday-event elements if (originalElement.tagName === 'SWP-EVENT') { this.fadeOutAndRemove(originalElement); } // Remove clone prefix and normalize clone to be a regular event const cloneId = draggedClone.dataset.eventId; if (cloneId && cloneId.startsWith('clone-')) { draggedClone.dataset.eventId = cloneId.replace('clone-', ''); } // Fully normalize the clone to be a regular event draggedClone.classList.remove('dragging'); draggedClone.style.pointerEvents = ''; // Re-enable pointer events // Clean up instance state this.draggedClone = null; this.originalEvent = null; // Clean up any remaining day event clones const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${cloneId}"]`); if (dayEventClone) { dayEventClone.remove(); } } /** * Handle navigation completed event */ handleNavigationCompleted() { // Default implementation - can be overridden by subclasses } /** * Fade out and remove element */ fadeOutAndRemove(element) { element.style.transition = 'opacity 0.3s ease-out'; element.style.opacity = '0'; setTimeout(() => { element.remove(); }, 300); } renderEvents(events, container) { // Filter out all-day events - they should be handled by AllDayEventRenderer const timedEvents = events.filter(event => !event.allDay); // Find columns in the specific container for regular events const columns = this.getColumns(container); columns.forEach(column => { const columnEvents = this.getEventsForColumn(column, timedEvents); const eventsLayer = column.querySelector('swp-events-layer'); if (eventsLayer) { this.renderColumnEvents(columnEvents, eventsLayer); } }); } /** * Render events for a single column */ renderSingleColumnEvents(column, events) { const columnEvents = this.getEventsForColumn(column.element, events); const eventsLayer = column.element.querySelector('swp-events-layer'); if (eventsLayer) { this.renderColumnEvents(columnEvents, eventsLayer); } } /** * Render events in a column using combined stacking + grid algorithm */ renderColumnEvents(columnEvents, eventsLayer) { if (columnEvents.length === 0) return; // Get layout from coordinator const layout = this.layoutCoordinator.calculateColumnLayout(columnEvents); // Render grid groups layout.gridGroups.forEach(gridGroup => { this.renderGridGroup(gridGroup, eventsLayer); }); // Render stacked events layout.stackedEvents.forEach(stackedEvent => { const element = this.renderEvent(stackedEvent.event); this.stackManager.applyStackLinkToElement(element, stackedEvent.stackLink); this.stackManager.applyVisualStyling(element, stackedEvent.stackLink.stackLevel); eventsLayer.appendChild(element); }); } /** * Render events in a grid container (side-by-side with column sharing) */ renderGridGroup(gridGroup, eventsLayer) { const groupElement = document.createElement('swp-event-group'); // Add grid column class based on number of columns (not events) const colCount = gridGroup.columns.length; groupElement.classList.add(`cols-${colCount}`); // Add stack level class for margin-left offset groupElement.classList.add(`stack-level-${gridGroup.stackLevel}`); // Position from layout groupElement.style.top = `${gridGroup.position.top}px`; // Add stack-link attribute for drag-drop (group acts as a stacked item) const stackLink = { stackLevel: gridGroup.stackLevel }; this.stackManager.applyStackLinkToElement(groupElement, stackLink); // Apply visual styling (margin-left and z-index) using StackManager this.stackManager.applyVisualStyling(groupElement, gridGroup.stackLevel); // Render each column const earliestEvent = gridGroup.events[0]; gridGroup.columns.forEach((columnEvents) => { const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start); groupElement.appendChild(columnContainer); }); eventsLayer.appendChild(groupElement); } /** * Render a single column within a grid group * Column may contain multiple events that don't overlap */ renderGridColumn(columnEvents, containerStart) { const columnContainer = document.createElement('div'); columnContainer.style.position = 'relative'; columnEvents.forEach(event => { const element = this.renderEventInGrid(event, containerStart); columnContainer.appendChild(element); }); return columnContainer; } /** * Render event within a grid container (absolute positioning within column) */ renderEventInGrid(event, containerStart) { const element = SwpEventElement.fromCalendarEvent(event); // Calculate event height const position = this.calculateEventPosition(event); // Calculate relative top offset if event starts after container start // (e.g., if container starts at 07:00 and event starts at 08:15, offset = 75 min) const timeDiffMs = event.start.getTime() - containerStart.getTime(); const timeDiffMinutes = timeDiffMs / (1000 * 60); const gridSettings = this.config.gridSettings; const relativeTop = timeDiffMinutes > 0 ? (timeDiffMinutes / 60) * gridSettings.hourHeight : 0; // Events in grid columns are positioned absolutely within their column container element.style.position = 'absolute'; element.style.top = `${relativeTop}px`; element.style.height = `${position.height - 3}px`; element.style.left = '0'; element.style.right = '0'; return element; } renderEvent(event) { const element = SwpEventElement.fromCalendarEvent(event); // Apply positioning (moved from SwpEventElement.applyPositioning) const position = this.calculateEventPosition(event); element.style.position = 'absolute'; element.style.top = `${position.top + 1}px`; element.style.height = `${position.height - 3}px`; element.style.left = '2px'; element.style.right = '2px'; return element; } calculateEventPosition(event) { // Delegate to PositionUtils for centralized position calculation return this.positionUtils.calculateEventPosition(event.start, event.end); } clearEvents(container) { const eventSelector = 'swp-event'; const groupSelector = 'swp-event-group'; const existingEvents = container ? container.querySelectorAll(eventSelector) : document.querySelectorAll(eventSelector); const existingGroups = container ? container.querySelectorAll(groupSelector) : document.querySelectorAll(groupSelector); existingEvents.forEach(event => event.remove()); existingGroups.forEach(group => group.remove()); } getColumns(container) { const columns = container.querySelectorAll('swp-day-column'); return Array.from(columns); } getEventsForColumn(column, events) { const columnDate = column.dataset.date; if (!columnDate) { return []; } // Create start and end of day for interval overlap check const columnStart = this.dateService.parseISO(`${columnDate}T00:00:00`); const columnEnd = this.dateService.parseISO(`${columnDate}T23:59:59.999`); const columnEvents = events.filter(event => { // Interval overlap: event overlaps with column day if event.start < columnEnd AND event.end > columnStart const overlaps = event.start < columnEnd && event.end > columnStart; return overlaps; }); return columnEvents; } } //# sourceMappingURL=EventRenderer.js.map