2025-08-07 00:15:44 +02:00
|
|
|
// Event rendering strategy interface and implementations
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
2025-11-22 23:38:52 +01:00
|
|
|
import { IColumnInfo } from '../types/ColumnDataSource';
|
2025-11-03 22:04:37 +01:00
|
|
|
import { Configuration } from '../configurations/CalendarConfig';
|
2025-09-21 21:30:51 +02:00
|
|
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
2025-09-13 00:39:56 +02:00
|
|
|
import { PositionUtils } from '../utils/PositionUtils';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
|
|
|
|
|
import { IDragColumnChangeEventPayload, IDragMoveEventPayload, IDragStartEventPayload, IDragMouseEnterColumnEventPayload } from '../types/EventTypes';
|
2025-10-03 16:47:42 +02:00
|
|
|
import { DateService } from '../utils/DateService';
|
2025-10-06 00:24:13 +02:00
|
|
|
import { EventStackManager } from '../managers/EventStackManager';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { EventLayoutCoordinator, IGridGroupLayout, IStackedEventLayout } from '../managers/EventLayoutCoordinator';
|
2025-08-07 00:15:44 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Interface for event rendering strategies
|
2025-11-22 23:38:52 +01:00
|
|
|
*
|
|
|
|
|
* Note: renderEvents now receives columns with pre-filtered events,
|
|
|
|
|
* not a flat array of events. Each column contains its own events.
|
2025-08-07 00:15:44 +02:00
|
|
|
*/
|
2025-11-01 16:28:45 +01:00
|
|
|
export interface IEventRenderer {
|
2025-11-22 23:38:52 +01:00
|
|
|
renderEvents(columns: IColumnInfo[], container: HTMLElement): void;
|
2025-08-16 00:51:12 +02:00
|
|
|
clearEvents(container?: HTMLElement): void;
|
2025-11-06 21:11:22 +01:00
|
|
|
renderSingleColumnEvents?(column: IColumnBounds, events: ICalendarEvent[]): void;
|
2025-11-03 21:30:50 +01:00
|
|
|
handleDragStart?(payload: IDragStartEventPayload): void;
|
|
|
|
|
handleDragMove?(payload: IDragMoveEventPayload): void;
|
2025-09-20 09:40:56 +02:00
|
|
|
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
2025-11-06 21:11:22 +01:00
|
|
|
handleDragEnd?(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void;
|
2025-09-20 09:40:56 +02:00
|
|
|
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
2025-11-03 21:30:50 +01:00
|
|
|
handleColumnChange?(payload: IDragColumnChangeEventPayload): void;
|
2025-09-20 09:40:56 +02:00
|
|
|
handleNavigationCompleted?(): void;
|
2025-11-03 21:30:50 +01:00
|
|
|
handleConvertAllDayToTimed?(payload: IDragMouseEnterColumnEventPayload): void;
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-02 23:11:26 +02:00
|
|
|
* Date-based event renderer
|
2025-08-07 00:15:44 +02:00
|
|
|
*/
|
2025-11-01 16:28:45 +01:00
|
|
|
export class DateEventRenderer implements IEventRenderer {
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-03 20:50:40 +02:00
|
|
|
private dateService: DateService;
|
2025-10-05 23:54:50 +02:00
|
|
|
private stackManager: EventStackManager;
|
2025-10-06 00:24:13 +02:00
|
|
|
private layoutCoordinator: EventLayoutCoordinator;
|
2025-11-03 21:30:50 +01:00
|
|
|
private config: Configuration;
|
2025-10-30 23:47:30 +01:00
|
|
|
private positionUtils: PositionUtils;
|
2025-10-04 14:50:25 +02:00
|
|
|
private draggedClone: HTMLElement | null = null;
|
|
|
|
|
private originalEvent: HTMLElement | null = null;
|
2025-08-20 00:39:31 +02:00
|
|
|
|
2025-10-30 23:47:30 +01:00
|
|
|
constructor(
|
|
|
|
|
dateService: DateService,
|
|
|
|
|
stackManager: EventStackManager,
|
|
|
|
|
layoutCoordinator: EventLayoutCoordinator,
|
2025-11-03 21:30:50 +01:00
|
|
|
config: Configuration,
|
2025-10-30 23:47:30 +01:00
|
|
|
positionUtils: PositionUtils
|
|
|
|
|
) {
|
|
|
|
|
this.dateService = dateService;
|
|
|
|
|
this.stackManager = stackManager;
|
|
|
|
|
this.layoutCoordinator = layoutCoordinator;
|
|
|
|
|
this.config = config;
|
|
|
|
|
this.positionUtils = positionUtils;
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-10-11 01:30:41 +02:00
|
|
|
|
2025-09-10 22:07:40 +02:00
|
|
|
private applyDragStyling(element: HTMLElement): void {
|
2025-09-16 23:09:10 +02:00
|
|
|
element.classList.add('dragging');
|
2025-10-04 00:32:26 +02:00
|
|
|
element.style.removeProperty("margin-left");
|
2025-09-10 22:07:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
/**
|
|
|
|
|
* Handle drag start event
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
public handleDragStart(payload: IDragStartEventPayload): void {
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
this.originalEvent = payload.originalElement;;
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-09-26 22:53:49 +02:00
|
|
|
// Use the clone from the payload instead of creating a new one
|
|
|
|
|
this.draggedClone = payload.draggedClone;
|
|
|
|
|
|
2025-10-08 21:50:41 +02:00
|
|
|
if (this.draggedClone && payload.columnBounds) {
|
|
|
|
|
// Apply drag styling
|
2025-09-26 22:53:49 +02:00
|
|
|
this.applyDragStyling(this.draggedClone);
|
|
|
|
|
|
|
|
|
|
// Add to current column's events layer (not directly to column)
|
2025-10-08 21:50:41 +02:00
|
|
|
const eventsLayer = payload.columnBounds.element.querySelector('swp-events-layer');
|
2025-09-28 13:25:09 +02:00
|
|
|
if (eventsLayer) {
|
|
|
|
|
eventsLayer.appendChild(this.draggedClone);
|
2025-10-08 21:50:41 +02:00
|
|
|
|
|
|
|
|
// 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`;
|
2025-09-04 00:16:35 +02:00
|
|
|
}
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
// Make original semi-transparent
|
2025-09-28 13:25:09 +02:00
|
|
|
this.originalEvent.style.opacity = '0.3';
|
|
|
|
|
this.originalEvent.style.userSelect = 'none';
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
/**
|
|
|
|
|
* Handle drag move event
|
2025-11-22 23:38:52 +01:00
|
|
|
* Only updates visual position and time - date stays the same
|
2025-08-27 22:50:13 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
public handleDragMove(payload: IDragMoveEventPayload): void {
|
2025-10-11 01:30:41 +02:00
|
|
|
const swpEvent = payload.draggedClone as SwpEventElement;
|
2025-11-22 23:38:52 +01:00
|
|
|
swpEvent.updatePosition(payload.snappedY);
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
/**
|
|
|
|
|
* Handle column change during drag
|
2025-11-22 23:38:52 +01:00
|
|
|
* Only moves the element visually - no data updates here
|
|
|
|
|
* Data updates happen on drag:end in EventRenderingService
|
2025-08-27 22:50:13 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
public handleColumnChange(payload: IDragColumnChangeEventPayload): void {
|
2025-10-11 09:19:33 +02:00
|
|
|
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
|
|
|
|
|
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
|
|
|
|
|
eventsLayer.appendChild(payload.draggedClone);
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-10 19:37:10 +02:00
|
|
|
/**
|
|
|
|
|
* Handle conversion of all-day event to timed event
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
public handleConvertAllDayToTimed(payload: IDragMouseEnterColumnEventPayload): void {
|
2025-10-10 19:37:10 +02:00
|
|
|
|
|
|
|
|
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
|
2025-10-11 01:30:41 +02:00
|
|
|
eventId: payload.calendarEvent.id,
|
2025-11-18 16:43:50 +01:00
|
|
|
targetColumn: payload.targetColumn.identifier,
|
2025-10-11 01:30:41 +02:00
|
|
|
snappedY: payload.snappedY
|
2025-10-10 19:37:10 +02:00
|
|
|
});
|
|
|
|
|
|
2025-10-11 01:30:41 +02:00
|
|
|
let timedClone = SwpEventElement.fromCalendarEvent(payload.calendarEvent);
|
|
|
|
|
let position = this.calculateEventPosition(payload.calendarEvent);
|
2025-10-10 19:37:10 +02:00
|
|
|
|
|
|
|
|
// Set position at snapped Y
|
2025-10-11 01:30:41 +02:00
|
|
|
//timedClone.style.top = `${snappedY}px`;
|
2025-10-10 19:37:10 +02:00
|
|
|
|
|
|
|
|
// 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
|
2025-10-11 01:30:41 +02:00
|
|
|
let eventsLayer = payload.targetColumn.element.querySelector('swp-events-layer');
|
2025-10-10 19:37:10 +02:00
|
|
|
|
2025-10-11 01:30:41 +02:00
|
|
|
// Add "clone-" prefix to match clone ID pattern
|
2025-11-06 21:11:22 +01:00
|
|
|
//timedClone.dataset.eventId = `clone-${payload.calendarEvent.id}`;
|
2025-10-10 19:37:10 +02:00
|
|
|
|
2025-10-11 01:30:41 +02:00
|
|
|
// Remove old all-day clone and replace with new timed clone
|
|
|
|
|
payload.draggedClone.remove();
|
|
|
|
|
payload.replaceClone(timedClone);
|
|
|
|
|
eventsLayer!!.appendChild(timedClone);
|
2025-10-10 19:37:10 +02:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
/**
|
|
|
|
|
* Handle drag end event
|
|
|
|
|
*/
|
2025-11-06 21:11:22 +01:00
|
|
|
public handleDragEnd(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void {
|
2025-09-20 09:40:56 +02:00
|
|
|
if (!draggedClone || !originalElement) {
|
|
|
|
|
console.warn('Missing draggedClone or originalElement');
|
2025-08-27 23:56:38 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-11-11 20:03:42 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 23:56:38 +02:00
|
|
|
// Remove clone prefix and normalize clone to be a regular event
|
2025-09-20 09:40:56 +02:00
|
|
|
const cloneId = draggedClone.dataset.eventId;
|
2025-08-27 22:50:13 +02:00
|
|
|
if (cloneId && cloneId.startsWith('clone-')) {
|
2025-09-20 09:40:56 +02:00
|
|
|
draggedClone.dataset.eventId = cloneId.replace('clone-', '');
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 23:56:38 +02:00
|
|
|
// Fully normalize the clone to be a regular event
|
2025-09-20 09:40:56 +02:00
|
|
|
draggedClone.classList.remove('dragging');
|
2025-10-08 00:58:38 +02:00
|
|
|
draggedClone.style.pointerEvents = ''; // Re-enable pointer events
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-04 14:50:25 +02:00
|
|
|
// Clean up instance state
|
2025-08-27 22:50:13 +02:00
|
|
|
this.draggedClone = null;
|
|
|
|
|
this.originalEvent = null;
|
2025-11-06 21:11:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up any remaining day event clones
|
|
|
|
|
const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${cloneId}"]`);
|
|
|
|
|
if (dayEventClone) {
|
|
|
|
|
dayEventClone.remove();
|
|
|
|
|
}
|
2025-08-27 22:50:13 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle navigation completed event
|
|
|
|
|
*/
|
|
|
|
|
public handleNavigationCompleted(): void {
|
|
|
|
|
// Default implementation - can be overridden by subclasses
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
/**
|
2025-09-29 20:50:52 +02:00
|
|
|
* Fade out and remove element
|
2025-08-27 22:50:13 +02:00
|
|
|
*/
|
|
|
|
|
private fadeOutAndRemove(element: HTMLElement): void {
|
|
|
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
|
|
|
element.style.opacity = '0';
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-27 22:50:13 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
|
element.remove();
|
|
|
|
|
}, 300);
|
2025-08-20 00:39:31 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
|
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
renderEvents(columns: IColumnInfo[], container: HTMLElement): void {
|
|
|
|
|
// Find column DOM elements in the container
|
|
|
|
|
const columnElements = this.getColumns(container);
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Render events for each column using pre-filtered events from IColumnInfo
|
|
|
|
|
columns.forEach((columnInfo, index) => {
|
|
|
|
|
const columnElement = columnElements[index];
|
|
|
|
|
if (!columnElement) return;
|
2025-08-13 23:05:58 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
|
|
|
|
const timedEvents = columnInfo.events.filter(event => !event.allDay);
|
2025-10-05 23:54:50 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
const eventsLayer = columnElement.querySelector('swp-events-layer') as HTMLElement;
|
|
|
|
|
if (eventsLayer && timedEvents.length > 0) {
|
|
|
|
|
this.renderColumnEvents(timedEvents, eventsLayer);
|
2025-10-05 23:54:50 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 21:11:22 +01:00
|
|
|
/**
|
|
|
|
|
* Render events for a single column
|
2025-11-22 23:38:52 +01:00
|
|
|
* Note: events are already filtered for this column
|
2025-11-06 21:11:22 +01:00
|
|
|
*/
|
|
|
|
|
public renderSingleColumnEvents(column: IColumnBounds, events: ICalendarEvent[]): void {
|
2025-11-22 23:38:52 +01:00
|
|
|
// Filter out all-day events
|
|
|
|
|
const timedEvents = events.filter(event => !event.allDay);
|
2025-11-06 21:11:22 +01:00
|
|
|
const eventsLayer = column.element.querySelector('swp-events-layer') as HTMLElement;
|
|
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
if (eventsLayer && timedEvents.length > 0) {
|
|
|
|
|
this.renderColumnEvents(timedEvents, eventsLayer);
|
2025-11-06 21:11:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 23:54:50 +02:00
|
|
|
/**
|
|
|
|
|
* Render events in a column using combined stacking + grid algorithm
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private renderColumnEvents(columnEvents: ICalendarEvent[], eventsLayer: HTMLElement): void {
|
2025-10-05 23:54:50 +02:00
|
|
|
if (columnEvents.length === 0) return;
|
|
|
|
|
|
2025-10-06 00:24:13 +02:00
|
|
|
// Get layout from coordinator
|
|
|
|
|
const layout = this.layoutCoordinator.calculateColumnLayout(columnEvents);
|
2025-10-05 23:54:50 +02:00
|
|
|
|
2025-10-06 00:24:13 +02:00
|
|
|
// Render grid groups
|
|
|
|
|
layout.gridGroups.forEach(gridGroup => {
|
|
|
|
|
this.renderGridGroup(gridGroup, eventsLayer);
|
2025-10-05 23:54:50 +02:00
|
|
|
});
|
|
|
|
|
|
2025-10-06 00:24:13 +02:00
|
|
|
// 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);
|
2025-10-05 23:54:50 +02:00
|
|
|
eventsLayer.appendChild(element);
|
2025-08-07 00:15:44 +02:00
|
|
|
});
|
|
|
|
|
}
|
2025-10-05 23:54:50 +02:00
|
|
|
/**
|
2025-10-06 17:05:18 +02:00
|
|
|
* Render events in a grid container (side-by-side with column sharing)
|
2025-10-05 23:54:50 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private renderGridGroup(gridGroup: IGridGroupLayout, eventsLayer: HTMLElement): void {
|
2025-10-05 23:54:50 +02:00
|
|
|
const groupElement = document.createElement('swp-event-group');
|
|
|
|
|
|
2025-10-06 17:05:18 +02:00
|
|
|
// Add grid column class based on number of columns (not events)
|
|
|
|
|
const colCount = gridGroup.columns.length;
|
2025-10-05 23:54:50 +02:00
|
|
|
groupElement.classList.add(`cols-${colCount}`);
|
|
|
|
|
|
|
|
|
|
// Add stack level class for margin-left offset
|
2025-10-06 00:24:13 +02:00
|
|
|
groupElement.classList.add(`stack-level-${gridGroup.stackLevel}`);
|
2025-10-05 23:54:50 +02:00
|
|
|
|
2025-10-06 00:24:13 +02:00
|
|
|
// Position from layout
|
|
|
|
|
groupElement.style.top = `${gridGroup.position.top}px`;
|
2025-10-05 23:54:50 +02:00
|
|
|
|
|
|
|
|
// Add stack-link attribute for drag-drop (group acts as a stacked item)
|
2025-10-06 00:24:13 +02:00
|
|
|
const stackLink = {
|
|
|
|
|
stackLevel: gridGroup.stackLevel
|
2025-10-05 23:54:50 +02:00
|
|
|
};
|
|
|
|
|
this.stackManager.applyStackLinkToElement(groupElement, stackLink);
|
|
|
|
|
|
2025-10-06 21:16:29 +02:00
|
|
|
// Apply visual styling (margin-left and z-index) using StackManager
|
|
|
|
|
this.stackManager.applyVisualStyling(groupElement, gridGroup.stackLevel);
|
|
|
|
|
|
2025-10-06 17:05:18 +02:00
|
|
|
// Render each column
|
2025-10-06 00:24:13 +02:00
|
|
|
const earliestEvent = gridGroup.events[0];
|
2025-11-03 21:30:50 +01:00
|
|
|
gridGroup.columns.forEach((columnEvents: ICalendarEvent[]) => {
|
2025-10-06 17:05:18 +02:00
|
|
|
const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start);
|
|
|
|
|
groupElement.appendChild(columnContainer);
|
2025-10-05 23:54:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eventsLayer.appendChild(groupElement);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-06 17:05:18 +02:00
|
|
|
* Render a single column within a grid group
|
|
|
|
|
* Column may contain multiple events that don't overlap
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private renderGridColumn(columnEvents: ICalendarEvent[], containerStart: Date): HTMLElement {
|
2025-10-06 17:05:18 +02:00
|
|
|
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)
|
2025-10-05 23:54:50 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private renderEventInGrid(event: ICalendarEvent, containerStart: Date): HTMLElement {
|
2025-10-05 23:54:50 +02:00
|
|
|
const element = SwpEventElement.fromCalendarEvent(event);
|
|
|
|
|
|
|
|
|
|
// Calculate event height
|
|
|
|
|
const position = this.calculateEventPosition(event);
|
|
|
|
|
|
2025-10-06 17:05:18 +02:00
|
|
|
// 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);
|
2025-11-03 22:04:37 +01:00
|
|
|
const gridSettings = this.config.gridSettings;
|
2025-10-06 17:05:18 +02:00
|
|
|
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`;
|
2025-10-05 23:54:50 +02:00
|
|
|
element.style.height = `${position.height - 3}px`;
|
2025-10-06 17:05:18 +02:00
|
|
|
element.style.left = '0';
|
|
|
|
|
element.style.right = '0';
|
2025-10-05 23:54:50 +02:00
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
private renderEvent(event: ICalendarEvent): HTMLElement {
|
2025-10-05 21:53:25 +02:00
|
|
|
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;
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
protected calculateEventPosition(event: ICalendarEvent): { top: number; height: number } {
|
2025-09-13 00:39:56 +02:00
|
|
|
// Delegate to PositionUtils for centralized position calculation
|
2025-10-30 23:47:30 +01:00
|
|
|
return this.positionUtils.calculateEventPosition(event.start, event.end);
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
clearEvents(container?: HTMLElement): void {
|
2025-10-06 00:24:13 +02:00
|
|
|
const eventSelector = 'swp-event';
|
|
|
|
|
const groupSelector = 'swp-event-group';
|
|
|
|
|
|
2025-09-04 00:16:35 +02:00
|
|
|
const existingEvents = container
|
2025-10-06 00:24:13 +02:00
|
|
|
? container.querySelectorAll(eventSelector)
|
|
|
|
|
: document.querySelectorAll(eventSelector);
|
|
|
|
|
|
|
|
|
|
const existingGroups = container
|
|
|
|
|
? container.querySelectorAll(groupSelector)
|
|
|
|
|
: document.querySelectorAll(groupSelector);
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-08-07 00:15:44 +02:00
|
|
|
existingEvents.forEach(event => event.remove());
|
2025-10-06 00:24:13 +02:00
|
|
|
existingGroups.forEach(group => group.remove());
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
2025-09-09 14:35:21 +02:00
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
protected getColumns(container: HTMLElement): HTMLElement[] {
|
|
|
|
|
const columns = container.querySelectorAll('swp-day-column');
|
2025-08-13 23:05:58 +02:00
|
|
|
return Array.from(columns) as HTMLElement[];
|
|
|
|
|
}
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|