2025-09-12 00:36:02 +02:00
|
|
|
// All-day row height management and animations
|
|
|
|
|
|
|
|
|
|
import { eventBus } from '../core/EventBus';
|
2025-11-03 22:04:37 +01:00
|
|
|
import { ALL_DAY_CONSTANTS } from '../configurations/CalendarConfig';
|
2025-09-13 00:39:56 +02:00
|
|
|
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { AllDayLayoutEngine, IEventLayout } from '../utils/AllDayLayoutEngine';
|
|
|
|
|
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
|
|
|
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
2025-10-04 16:20:09 +02:00
|
|
|
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
2025-09-21 15:48:13 +02:00
|
|
|
import {
|
2025-11-03 21:30:50 +01:00
|
|
|
IDragMouseEnterHeaderEventPayload,
|
2025-11-08 14:30:18 +01:00
|
|
|
IDragMouseEnterColumnEventPayload,
|
2025-11-03 21:30:50 +01:00
|
|
|
IDragStartEventPayload,
|
|
|
|
|
IDragMoveEventPayload,
|
|
|
|
|
IDragEndEventPayload,
|
|
|
|
|
IDragColumnChangeEventPayload,
|
|
|
|
|
IHeaderReadyEventPayload
|
2025-09-21 15:48:13 +02:00
|
|
|
} from '../types/EventTypes';
|
2025-11-03 21:30:50 +01:00
|
|
|
import { IDragOffset, IMousePosition } from '../types/DragDropTypes';
|
2025-10-01 21:27:13 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
|
|
|
|
import { EventManager } from './EventManager';
|
2025-10-03 19:09:44 +02:00
|
|
|
import { differenceInCalendarDays } from 'date-fns';
|
2025-10-04 00:32:26 +02:00
|
|
|
import { DateService } from '../utils/DateService';
|
2025-09-12 00:36:02 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* AllDayManager - Handles all-day row height animations and management
|
2025-09-25 23:38:17 +02:00
|
|
|
* Uses AllDayLayoutEngine for all overlap detection and layout calculation
|
2025-09-12 00:36:02 +02:00
|
|
|
*/
|
|
|
|
|
export class AllDayManager {
|
2025-09-13 00:39:56 +02:00
|
|
|
private allDayEventRenderer: AllDayEventRenderer;
|
2025-10-01 21:27:13 +02:00
|
|
|
private eventManager: EventManager;
|
2025-10-04 00:32:26 +02:00
|
|
|
private dateService: DateService;
|
2025-10-01 21:27:13 +02:00
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
private layoutEngine: AllDayLayoutEngine | null = null;
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
// State tracking for layout calculation
|
2025-11-03 21:30:50 +01:00
|
|
|
private currentAllDayEvents: ICalendarEvent[] = [];
|
|
|
|
|
private currentWeekDates: IColumnBounds[] = [];
|
2025-09-12 00:36:02 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
// Expand/collapse state
|
|
|
|
|
private isExpanded: boolean = false;
|
|
|
|
|
private actualRowCount: number = 0;
|
2025-10-02 15:37:01 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
|
2025-10-30 23:47:30 +01:00
|
|
|
constructor(
|
|
|
|
|
eventManager: EventManager,
|
|
|
|
|
allDayEventRenderer: AllDayEventRenderer,
|
|
|
|
|
dateService: DateService
|
|
|
|
|
) {
|
2025-10-01 21:27:13 +02:00
|
|
|
this.eventManager = eventManager;
|
2025-10-30 23:47:30 +01:00
|
|
|
this.allDayEventRenderer = allDayEventRenderer;
|
|
|
|
|
this.dateService = dateService;
|
2025-10-02 15:37:01 +02:00
|
|
|
|
2025-10-02 01:03:35 +02:00
|
|
|
// Sync CSS variable with TypeScript constant to ensure consistency
|
2025-10-02 23:11:26 +02:00
|
|
|
document.documentElement.style.setProperty('--single-row-height', `${ALL_DAY_CONSTANTS.EVENT_HEIGHT}px`);
|
2025-09-13 00:39:56 +02:00
|
|
|
this.setupEventListeners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Setup event listeners for drag conversions
|
|
|
|
|
*/
|
|
|
|
|
private setupEventListeners(): void {
|
2025-09-21 15:48:13 +02:00
|
|
|
eventBus.on('drag:mouseenter-header', (event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
const payload = (event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
|
2025-09-21 21:30:51 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
if (payload.draggedClone.hasAttribute('data-allday'))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-09-21 15:48:13 +02:00
|
|
|
console.log('🔄 AllDayManager: Received drag:mouseenter-header', {
|
2025-09-29 20:50:52 +02:00
|
|
|
targetDate: payload.targetColumn,
|
|
|
|
|
originalElementId: payload.originalElement?.dataset?.eventId,
|
|
|
|
|
originalElementTag: payload.originalElement?.tagName
|
2025-09-18 17:55:52 +02:00
|
|
|
});
|
2025-09-22 23:37:43 +02:00
|
|
|
|
2025-10-01 21:27:13 +02:00
|
|
|
this.handleConvertToAllDay(payload);
|
2025-09-13 00:39:56 +02:00
|
|
|
});
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-21 21:30:51 +02:00
|
|
|
eventBus.on('drag:mouseleave-header', (event) => {
|
|
|
|
|
const { originalElement, cloneElement } = (event as CustomEvent).detail;
|
2025-09-18 17:55:52 +02:00
|
|
|
|
2025-09-21 21:30:51 +02:00
|
|
|
console.log('🚪 AllDayManager: Received drag:mouseleave-header', {
|
|
|
|
|
originalElementId: originalElement?.dataset?.eventId
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-18 19:26:00 +02:00
|
|
|
});
|
2025-09-19 00:20:30 +02:00
|
|
|
|
|
|
|
|
// Listen for drag operations on all-day events
|
|
|
|
|
eventBus.on('drag:start', (event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
let payload: IDragStartEventPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
|
2025-09-21 21:30:51 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
this.allDayEventRenderer.handleDragStart(payload);
|
2025-09-19 00:20:30 +02:00
|
|
|
});
|
|
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
eventBus.on('drag:column-change', (event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
let payload: IDragColumnChangeEventPayload = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
2025-09-26 22:53:49 +02:00
|
|
|
return;
|
2025-09-26 22:11:57 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
this.handleColumnChange(payload);
|
2025-09-19 00:20:30 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eventBus.on('drag:end', (event) => {
|
2025-11-08 14:30:18 +01:00
|
|
|
let dragEndPayload: IDragEndEventPayload = (event as CustomEvent<IDragEndEventPayload>).detail;
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-11-08 14:30:18 +01:00
|
|
|
console.log('🎯 AllDayManager: drag:end received', {
|
|
|
|
|
target: dragEndPayload.target,
|
|
|
|
|
originalElementTag: dragEndPayload.originalElement?.tagName,
|
|
|
|
|
hasAllDayAttribute: dragEndPayload.originalElement?.hasAttribute('data-allday'),
|
|
|
|
|
eventId: dragEndPayload.originalElement?.dataset.eventId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle all-day → all-day drops (within header)
|
|
|
|
|
if (dragEndPayload.target === 'swp-day-header') {
|
|
|
|
|
console.log('✅ AllDayManager: Handling all-day → all-day drop');
|
|
|
|
|
this.handleDragEnd(dragEndPayload);
|
2025-09-20 09:40:56 +02:00
|
|
|
return;
|
2025-11-08 14:30:18 +01:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-11-08 14:30:18 +01:00
|
|
|
// Handle all-day → timed conversion (dropped in column)
|
|
|
|
|
if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) {
|
|
|
|
|
const eventId = dragEndPayload.originalElement.dataset.eventId;
|
|
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
console.log('🔄 AllDayManager: All-day → timed conversion', { eventId });
|
|
|
|
|
|
2025-11-11 20:23:51 +01:00
|
|
|
// Mark for removal (sets data-removing attribute)
|
2025-11-11 20:03:42 +01:00
|
|
|
this.fadeOutAndRemove(dragEndPayload.originalElement);
|
2025-11-11 20:23:51 +01:00
|
|
|
|
|
|
|
|
// Recalculate layout WITHOUT the removed event to compress gaps
|
|
|
|
|
const remainingEvents = this.currentAllDayEvents.filter(e => e.id !== eventId);
|
|
|
|
|
const newLayouts = this.calculateAllDayEventsLayout(remainingEvents, this.currentWeekDates);
|
|
|
|
|
|
|
|
|
|
// Re-render all-day events with compressed layout
|
|
|
|
|
this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts);
|
|
|
|
|
|
|
|
|
|
// NOW animate height with compressed layout
|
2025-11-08 14:30:18 +01:00
|
|
|
this.checkAndAnimateAllDayHeight();
|
|
|
|
|
}
|
2025-09-19 00:20:30 +02:00
|
|
|
});
|
2025-09-22 17:51:24 +02:00
|
|
|
|
|
|
|
|
// Listen for drag cancellation to recalculate height
|
|
|
|
|
eventBus.on('drag:cancelled', (event) => {
|
|
|
|
|
const { draggedElement, reason } = (event as CustomEvent).detail;
|
2025-09-22 23:37:43 +02:00
|
|
|
|
2025-09-22 17:51:24 +02:00
|
|
|
console.log('🚫 AllDayManager: Drag cancelled', {
|
|
|
|
|
eventId: draggedElement?.dataset?.eventId,
|
|
|
|
|
reason
|
|
|
|
|
});
|
2025-09-22 23:37:43 +02:00
|
|
|
|
2025-09-22 17:51:24 +02:00
|
|
|
});
|
2025-09-22 21:53:18 +02:00
|
|
|
|
2025-10-01 21:27:13 +02:00
|
|
|
// Listen for header ready - when dates are populated with period data
|
2025-11-05 00:37:57 +01:00
|
|
|
eventBus.on('header:ready', async (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
let headerReadyEventPayload = (event as CustomEvent<IHeaderReadyEventPayload>).detail;
|
2025-10-01 21:27:13 +02:00
|
|
|
|
2025-10-01 21:47:05 +02:00
|
|
|
let startDate = new Date(headerReadyEventPayload.headerElements.at(0)!.date);
|
|
|
|
|
let endDate = new Date(headerReadyEventPayload.headerElements.at(-1)!.date);
|
2025-10-01 21:27:13 +02:00
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
let events: ICalendarEvent[] = await this.eventManager.getEventsForPeriod(startDate, endDate);
|
2025-10-01 21:27:13 +02:00
|
|
|
// Filter for all-day events
|
|
|
|
|
const allDayEvents = events.filter(event => event.allDay);
|
2025-10-01 21:47:05 +02:00
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements);
|
2025-10-01 21:27:13 +02:00
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts);
|
2025-10-01 21:27:13 +02:00
|
|
|
this.checkAndAnimateAllDayHeight();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eventBus.on(CoreEvents.VIEW_CHANGED, (event: Event) => {
|
|
|
|
|
this.allDayEventRenderer.handleViewChanged(event as CustomEvent);
|
|
|
|
|
});
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getAllDayContainer(): HTMLElement | null {
|
2025-09-22 23:37:43 +02:00
|
|
|
return document.querySelector('swp-calendar-header swp-allday-container');
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getCalendarHeader(): HTMLElement | null {
|
2025-09-22 23:37:43 +02:00
|
|
|
return document.querySelector('swp-calendar-header');
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getHeaderSpacer(): HTMLElement | null {
|
2025-09-22 23:37:43 +02:00
|
|
|
return document.querySelector('swp-header-spacer');
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
/**
|
|
|
|
|
* Read current max row from DOM elements
|
2025-11-11 20:03:42 +01:00
|
|
|
* Excludes events marked as removing (data-removing attribute)
|
2025-11-11 18:08:48 +01:00
|
|
|
*/
|
|
|
|
|
private getMaxRowFromDOM(): number {
|
|
|
|
|
const container = this.getAllDayContainer();
|
|
|
|
|
if (!container) return 0;
|
|
|
|
|
|
|
|
|
|
let maxRow = 0;
|
2025-11-11 20:03:42 +01:00
|
|
|
const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator):not([data-removing])');
|
2025-11-11 18:08:48 +01:00
|
|
|
|
|
|
|
|
allDayEvents.forEach((element: Element) => {
|
|
|
|
|
const htmlElement = element as HTMLElement;
|
|
|
|
|
const row = parseInt(htmlElement.style.gridRow) || 1;
|
|
|
|
|
maxRow = Math.max(maxRow, row);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return maxRow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current gridArea for an event from DOM
|
|
|
|
|
*/
|
|
|
|
|
private getGridAreaFromDOM(eventId: string): string | null {
|
|
|
|
|
const container = this.getAllDayContainer();
|
|
|
|
|
if (!container) return null;
|
|
|
|
|
|
|
|
|
|
const element = container.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement;
|
|
|
|
|
return element?.style.gridArea || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Count events in a specific column by reading DOM
|
|
|
|
|
*/
|
|
|
|
|
private countEventsInColumnFromDOM(columnIndex: number): number {
|
|
|
|
|
const container = this.getAllDayContainer();
|
|
|
|
|
if (!container) return 0;
|
|
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)');
|
|
|
|
|
|
|
|
|
|
allDayEvents.forEach((element: Element) => {
|
|
|
|
|
const htmlElement = element as HTMLElement;
|
|
|
|
|
const gridColumn = htmlElement.style.gridColumn;
|
|
|
|
|
|
|
|
|
|
// Parse "1 / 3" format
|
|
|
|
|
const match = gridColumn.match(/(\d+)\s*\/\s*(\d+)/);
|
|
|
|
|
if (match) {
|
|
|
|
|
const startCol = parseInt(match[1]);
|
|
|
|
|
const endCol = parseInt(match[2]) - 1; // End is exclusive in CSS
|
|
|
|
|
|
|
|
|
|
if (startCol <= columnIndex && endCol >= columnIndex) {
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
/**
|
|
|
|
|
* Calculate all-day height based on number of rows
|
|
|
|
|
*/
|
|
|
|
|
private calculateAllDayHeight(targetRows: number): {
|
|
|
|
|
targetHeight: number;
|
|
|
|
|
currentHeight: number;
|
|
|
|
|
heightDifference: number;
|
|
|
|
|
} {
|
|
|
|
|
const root = document.documentElement;
|
2025-11-01 01:10:10 +01:00
|
|
|
const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT;
|
2025-09-25 23:38:17 +02:00
|
|
|
// Read CSS variable directly from style property or default to 0
|
|
|
|
|
const currentHeightStr = root.style.getPropertyValue('--all-day-row-height') || '0px';
|
|
|
|
|
const currentHeight = parseInt(currentHeightStr) || 0;
|
2025-09-12 00:36:02 +02:00
|
|
|
const heightDifference = targetHeight - currentHeight;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
return { targetHeight, currentHeight, heightDifference };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check current all-day events and animate to correct height
|
2025-11-11 18:08:48 +01:00
|
|
|
* Reads max row directly from DOM elements
|
2025-09-12 00:36:02 +02:00
|
|
|
*/
|
|
|
|
|
public checkAndAnimateAllDayHeight(): void {
|
2025-11-11 18:08:48 +01:00
|
|
|
// Read max row directly from DOM
|
|
|
|
|
const maxRows = this.getMaxRowFromDOM();
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-11-08 14:30:18 +01:00
|
|
|
console.log('📊 AllDayManager: Height calculation', {
|
|
|
|
|
maxRows,
|
|
|
|
|
isExpanded: this.isExpanded
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
// Store actual row count
|
|
|
|
|
this.actualRowCount = maxRows;
|
|
|
|
|
|
|
|
|
|
// Determine what to display
|
|
|
|
|
let displayRows = maxRows;
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-10-02 15:37:01 +02:00
|
|
|
if (maxRows > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) {
|
2025-09-30 00:34:27 +02:00
|
|
|
// Show chevron button
|
|
|
|
|
this.updateChevronButton(true);
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-09-30 15:24:58 +02:00
|
|
|
// Show 4 rows when collapsed (3 events + indicators)
|
2025-09-30 00:34:27 +02:00
|
|
|
if (!this.isExpanded) {
|
2025-09-30 22:49:16 +02:00
|
|
|
|
2025-10-02 15:37:01 +02:00
|
|
|
displayRows = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS;
|
2025-09-30 15:24:58 +02:00
|
|
|
this.updateOverflowIndicators();
|
2025-09-30 22:49:16 +02:00
|
|
|
|
2025-09-30 15:24:58 +02:00
|
|
|
} else {
|
2025-09-30 22:49:16 +02:00
|
|
|
|
2025-09-30 15:24:58 +02:00
|
|
|
this.clearOverflowIndicators();
|
2025-09-30 22:49:16 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-01 18:41:28 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
// Hide chevron - not needed
|
|
|
|
|
this.updateChevronButton(false);
|
2025-09-30 15:24:58 +02:00
|
|
|
this.clearOverflowIndicators();
|
2025-09-30 00:34:27 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-08 14:30:18 +01:00
|
|
|
console.log('🎬 AllDayManager: Will animate to', {
|
|
|
|
|
displayRows,
|
|
|
|
|
maxRows,
|
|
|
|
|
willAnimate: displayRows !== this.actualRowCount
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-11 20:03:42 +01:00
|
|
|
console.log(`🎯 AllDayManager: Animating to ${displayRows} rows`);
|
|
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
// Animate to required rows (0 = collapse, >0 = expand)
|
2025-09-30 00:34:27 +02:00
|
|
|
this.animateToRows(displayRows);
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Animate all-day container to specific number of rows
|
|
|
|
|
*/
|
|
|
|
|
public animateToRows(targetRows: number): void {
|
|
|
|
|
const { targetHeight, currentHeight, heightDifference } = this.calculateAllDayHeight(targetRows);
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
if (targetHeight === currentHeight) return; // No animation needed
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
console.log(`🎬 All-day height animation: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)} → ${targetRows} rows)`);
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
// Get cached elements
|
|
|
|
|
const calendarHeader = this.getCalendarHeader();
|
|
|
|
|
const headerSpacer = this.getHeaderSpacer();
|
|
|
|
|
const allDayContainer = this.getAllDayContainer();
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
if (!calendarHeader || !allDayContainer) return;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
// Get current parent height for animation
|
|
|
|
|
const currentParentHeight = parseFloat(getComputedStyle(calendarHeader).height);
|
|
|
|
|
const targetParentHeight = currentParentHeight + heightDifference;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
const animations = [
|
|
|
|
|
calendarHeader.animate([
|
|
|
|
|
{ height: `${currentParentHeight}px` },
|
|
|
|
|
{ height: `${targetParentHeight}px` }
|
|
|
|
|
], {
|
2025-09-22 23:37:43 +02:00
|
|
|
duration: 150,
|
2025-09-12 00:36:02 +02:00
|
|
|
easing: 'ease-out',
|
|
|
|
|
fill: 'forwards'
|
|
|
|
|
})
|
|
|
|
|
];
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-23 16:30:30 +02:00
|
|
|
// Add spacer animation if spacer exists, but don't use fill: 'forwards'
|
2025-09-12 00:36:02 +02:00
|
|
|
if (headerSpacer) {
|
|
|
|
|
const root = document.documentElement;
|
2025-09-25 23:38:17 +02:00
|
|
|
const headerHeightStr = root.style.getPropertyValue('--header-height');
|
|
|
|
|
const headerHeight = parseInt(headerHeightStr);
|
|
|
|
|
const currentSpacerHeight = headerHeight + currentHeight;
|
|
|
|
|
const targetSpacerHeight = headerHeight + targetHeight;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
animations.push(
|
|
|
|
|
headerSpacer.animate([
|
|
|
|
|
{ height: `${currentSpacerHeight}px` },
|
|
|
|
|
{ height: `${targetSpacerHeight}px` }
|
|
|
|
|
], {
|
2025-09-23 09:46:47 +02:00
|
|
|
duration: 150,
|
2025-09-23 16:30:30 +02:00
|
|
|
easing: 'ease-out'
|
|
|
|
|
// No fill: 'forwards' - let CSS calc() take over after animation
|
2025-09-12 00:36:02 +02:00
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
// Update CSS variable after animation
|
|
|
|
|
Promise.all(animations.map(anim => anim.finished)).then(() => {
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
root.style.setProperty('--all-day-row-height', `${targetHeight}px`);
|
|
|
|
|
eventBus.emit('header:height-changed');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
/**
|
|
|
|
|
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
|
|
|
|
* This is the correct method that processes all events together for proper overlap detection
|
|
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private calculateAllDayEventsLayout(events: ICalendarEvent[], dayHeaders: IColumnBounds[]): IEventLayout[] {
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
// Store current state
|
|
|
|
|
this.currentAllDayEvents = events;
|
2025-10-01 21:47:05 +02:00
|
|
|
this.currentWeekDates = dayHeaders;
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
// Initialize layout engine with provided week dates
|
2025-10-01 21:47:05 +02:00
|
|
|
let layoutEngine = new AllDayLayoutEngine(dayHeaders.map(column => column.date));
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
|
2025-09-30 22:35:07 +02:00
|
|
|
return layoutEngine.calculateLayout(events);
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
private handleConvertToAllDay(payload: IDragMouseEnterHeaderEventPayload): void {
|
2025-09-30 00:13:52 +02:00
|
|
|
|
2025-09-21 21:30:51 +02:00
|
|
|
let allDayContainer = this.getAllDayContainer();
|
2025-10-04 16:20:09 +02:00
|
|
|
if (!allDayContainer) return;
|
2025-09-16 23:09:56 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
// Create SwpAllDayEventElement from ICalendarEvent
|
2025-10-04 16:20:09 +02:00
|
|
|
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
2025-09-21 21:30:51 +02:00
|
|
|
|
2025-10-04 16:20:09 +02:00
|
|
|
// Apply grid positioning
|
|
|
|
|
allDayElement.style.gridRow = '1';
|
|
|
|
|
allDayElement.style.gridColumn = payload.targetColumn.index.toString();
|
2025-10-04 23:10:09 +02:00
|
|
|
|
|
|
|
|
// Remove old swp-event clone
|
2025-10-04 16:20:09 +02:00
|
|
|
payload.draggedClone.remove();
|
2025-10-04 23:10:09 +02:00
|
|
|
|
|
|
|
|
// Call delegate to update DragDropManager's draggedClone reference
|
|
|
|
|
payload.replaceClone(allDayElement);
|
|
|
|
|
|
|
|
|
|
// Append to container
|
2025-10-04 16:20:09 +02:00
|
|
|
allDayContainer.appendChild(allDayElement);
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-09-30 00:13:52 +02:00
|
|
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
2025-09-21 22:17:24 +02:00
|
|
|
|
2025-11-08 14:30:18 +01:00
|
|
|
// Recalculate height after adding all-day event
|
|
|
|
|
this.checkAndAnimateAllDayHeight();
|
|
|
|
|
|
2025-09-21 21:30:51 +02:00
|
|
|
}
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-18 17:55:52 +02:00
|
|
|
|
2025-09-19 00:20:30 +02:00
|
|
|
/**
|
2025-09-26 22:11:57 +02:00
|
|
|
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
|
2025-09-19 00:20:30 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private handleColumnChange(dragColumnChangeEventPayload: IDragColumnChangeEventPayload): void {
|
2025-10-02 23:11:26 +02:00
|
|
|
|
|
|
|
|
let allDayContainer = this.getAllDayContainer();
|
2025-09-26 22:11:57 +02:00
|
|
|
if (!allDayContainer) return;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
let targetColumn = ColumnDetectionUtils.getColumnBounds(dragColumnChangeEventPayload.mousePosition);
|
2025-09-28 13:25:09 +02:00
|
|
|
|
|
|
|
|
if (targetColumn == null)
|
|
|
|
|
return;
|
2025-09-26 22:11:57 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
if (!dragColumnChangeEventPayload.draggedClone)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-10-03 00:37:37 +02:00
|
|
|
// Calculate event span from original grid positioning
|
|
|
|
|
const computedStyle = window.getComputedStyle(dragColumnChangeEventPayload.draggedClone);
|
|
|
|
|
const gridColumnStart = parseInt(computedStyle.gridColumnStart) || targetColumn.index;
|
|
|
|
|
const gridColumnEnd = parseInt(computedStyle.gridColumnEnd) || targetColumn.index + 1;
|
|
|
|
|
const span = gridColumnEnd - gridColumnStart;
|
|
|
|
|
|
|
|
|
|
// Update clone position maintaining the span
|
|
|
|
|
const newStartColumn = targetColumn.index;
|
|
|
|
|
const newEndColumn = newStartColumn + span;
|
|
|
|
|
dragColumnChangeEventPayload.draggedClone.style.gridColumn = `${newStartColumn} / ${newEndColumn}`;
|
2025-09-19 00:20:30 +02:00
|
|
|
|
|
|
|
|
}
|
2025-10-02 23:11:26 +02:00
|
|
|
private fadeOutAndRemove(element: HTMLElement): void {
|
2025-11-11 20:03:42 +01:00
|
|
|
console.log('🗑️ AllDayManager: About to remove all-day event', {
|
|
|
|
|
eventId: element.dataset.eventId,
|
|
|
|
|
element: element.tagName
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mark element as removing so it's excluded from height calculations
|
|
|
|
|
element.setAttribute('data-removing', 'true');
|
|
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
|
|
|
element.style.opacity = '0';
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
|
element.remove();
|
2025-11-11 20:03:42 +01:00
|
|
|
console.log('✅ AllDayManager: All-day event removed from DOM');
|
2025-10-02 23:11:26 +02:00
|
|
|
}, 300);
|
|
|
|
|
}
|
2025-10-03 00:37:37 +02:00
|
|
|
|
|
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
private async handleDragEnd(dragEndEvent: IDragEndEventPayload): Promise<void> {
|
2025-09-26 22:11:57 +02:00
|
|
|
|
2025-10-04 23:10:09 +02:00
|
|
|
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
|
|
|
|
|
|
|
|
|
if (!start || !end)
|
2025-10-03 00:37:37 +02:00
|
|
|
throw new Error('Undefined start or end - date');
|
|
|
|
|
|
|
|
|
|
const startDate = new Date(start);
|
|
|
|
|
const endDate = new Date(end);
|
|
|
|
|
|
|
|
|
|
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
|
|
|
throw new Error('Ugyldig start eller slut-dato i dataset');
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 19:09:44 +02:00
|
|
|
// Use differenceInCalendarDays for proper calendar day calculation
|
|
|
|
|
// This correctly handles timezone differences and DST changes
|
|
|
|
|
return differenceInCalendarDays(endDate, startDate);
|
2025-10-03 00:37:37 +02:00
|
|
|
};
|
|
|
|
|
|
2025-09-28 13:25:09 +02:00
|
|
|
if (dragEndEvent.draggedClone == null)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
// 2. Normalize clone ID
|
2025-10-01 18:41:28 +02:00
|
|
|
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
|
2025-10-08 00:58:38 +02:00
|
|
|
dragEndEvent.draggedClone.style.pointerEvents = ''; // Re-enable pointer events
|
2025-10-02 23:58:03 +02:00
|
|
|
dragEndEvent.originalElement.dataset.eventId += '_';
|
2025-10-02 23:11:26 +02:00
|
|
|
|
2025-09-28 13:25:09 +02:00
|
|
|
let eventId = dragEndEvent.draggedClone.dataset.eventId;
|
|
|
|
|
let eventDate = dragEndEvent.finalPosition.column?.date;
|
|
|
|
|
let eventType = dragEndEvent.draggedClone.dataset.type;
|
|
|
|
|
|
|
|
|
|
if (eventDate == null || eventId == null || eventType == null)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-10-03 00:37:37 +02:00
|
|
|
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-10-03 19:09:44 +02:00
|
|
|
// Get original dates to preserve time
|
|
|
|
|
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!);
|
|
|
|
|
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!);
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-10-03 19:09:44 +02:00
|
|
|
// Create new start date with the new day but preserve original time
|
2025-10-03 00:37:37 +02:00
|
|
|
const newStartDate = new Date(eventDate);
|
2025-10-03 19:09:44 +02:00
|
|
|
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-10-03 19:09:44 +02:00
|
|
|
// Create new end date with the new day + duration, preserving original end time
|
|
|
|
|
const newEndDate = new Date(eventDate);
|
2025-10-03 00:37:37 +02:00
|
|
|
newEndDate.setDate(newEndDate.getDate() + durationDays);
|
2025-10-03 19:09:44 +02:00
|
|
|
newEndDate.setHours(originalEndDate.getHours(), originalEndDate.getMinutes(), originalEndDate.getSeconds(), originalEndDate.getMilliseconds());
|
2025-10-03 00:37:37 +02:00
|
|
|
|
2025-10-04 00:32:26 +02:00
|
|
|
// Update data attributes with new dates (convert to UTC)
|
|
|
|
|
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
|
|
|
|
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
2025-10-02 23:11:26 +02:00
|
|
|
|
2025-11-03 21:30:50 +01:00
|
|
|
const droppedEvent: ICalendarEvent = {
|
2025-09-28 13:25:09 +02:00
|
|
|
id: eventId,
|
2025-10-01 21:27:13 +02:00
|
|
|
title: dragEndEvent.draggedClone.dataset.title || '',
|
2025-10-03 00:37:37 +02:00
|
|
|
start: newStartDate,
|
|
|
|
|
end: newEndDate,
|
2025-09-28 13:25:09 +02:00
|
|
|
type: eventType,
|
2025-09-26 22:11:57 +02:00
|
|
|
allDay: true,
|
|
|
|
|
syncStatus: 'synced'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Use current events + dropped event for calculation
|
2025-10-02 23:58:03 +02:00
|
|
|
const tempEvents = [
|
|
|
|
|
...this.currentAllDayEvents.filter(event => event.id !== eventId),
|
|
|
|
|
droppedEvent
|
|
|
|
|
];
|
2025-09-26 22:11:57 +02:00
|
|
|
|
|
|
|
|
// 4. Calculate new layouts for ALL events
|
2025-11-11 18:08:48 +01:00
|
|
|
const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
|
2025-09-26 22:11:57 +02:00
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
// 5. Apply differential updates - compare with DOM instead of currentLayouts
|
2025-10-02 23:11:26 +02:00
|
|
|
let container = this.getAllDayContainer();
|
2025-11-11 18:08:48 +01:00
|
|
|
newLayouts.forEach((layout) => {
|
|
|
|
|
// Get current gridArea from DOM
|
|
|
|
|
const currentGridArea = this.getGridAreaFromDOM(layout.calenderEvent.id);
|
2025-09-26 22:11:57 +02:00
|
|
|
|
2025-11-11 18:08:48 +01:00
|
|
|
if (currentGridArea !== layout.gridArea) {
|
2025-10-02 23:11:26 +02:00
|
|
|
let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
|
2025-09-26 22:11:57 +02:00
|
|
|
if (element) {
|
2025-10-02 23:11:26 +02:00
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
element.classList.add('transitioning');
|
2025-10-01 18:41:28 +02:00
|
|
|
element.style.gridArea = layout.gridArea;
|
2025-09-26 22:11:57 +02:00
|
|
|
element.style.gridRow = layout.row.toString();
|
|
|
|
|
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
|
|
|
|
|
2025-10-04 23:10:09 +02:00
|
|
|
element.classList.remove('max-event-overflow-hide');
|
|
|
|
|
element.classList.remove('max-event-overflow-show');
|
2025-10-02 23:11:26 +02:00
|
|
|
|
2025-10-02 15:37:01 +02:00
|
|
|
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS)
|
2025-10-02 16:57:43 +02:00
|
|
|
if (!this.isExpanded)
|
|
|
|
|
element.classList.add('max-event-overflow-hide');
|
|
|
|
|
else
|
|
|
|
|
element.classList.add('max-event-overflow-show');
|
2025-10-02 15:37:01 +02:00
|
|
|
|
2025-09-26 22:11:57 +02:00
|
|
|
// Remove transition class after animation
|
|
|
|
|
setTimeout(() => element.classList.remove('transitioning'), 200);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6. Clean up drag styles from the dropped clone
|
2025-09-28 13:25:09 +02:00
|
|
|
dragEndEvent.draggedClone.classList.remove('dragging');
|
|
|
|
|
dragEndEvent.draggedClone.style.zIndex = '';
|
|
|
|
|
dragEndEvent.draggedClone.style.cursor = '';
|
|
|
|
|
dragEndEvent.draggedClone.style.opacity = '';
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-10-09 22:31:49 +02:00
|
|
|
// 7. Apply highlight class to show the dropped event with highlight color
|
|
|
|
|
dragEndEvent.draggedClone.classList.add('highlight');
|
|
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
// 8. CRITICAL FIX: Update event in repository to mark as allDay=true
|
|
|
|
|
// This ensures EventManager's repository has correct state
|
|
|
|
|
// Without this, the event will reappear in grid on re-render
|
|
|
|
|
await this.eventManager.updateEvent(eventId, {
|
|
|
|
|
start: newStartDate,
|
|
|
|
|
end: newEndDate,
|
|
|
|
|
allDay: true
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-02 23:11:26 +02:00
|
|
|
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
2025-09-26 22:11:57 +02:00
|
|
|
|
|
|
|
|
this.checkAndAnimateAllDayHeight();
|
|
|
|
|
|
2025-09-25 23:38:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
/**
|
|
|
|
|
* Update chevron button visibility and state
|
|
|
|
|
*/
|
|
|
|
|
private updateChevronButton(show: boolean): void {
|
|
|
|
|
const headerSpacer = this.getHeaderSpacer();
|
|
|
|
|
if (!headerSpacer) return;
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
let chevron = headerSpacer.querySelector('.allday-chevron') as HTMLElement;
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
if (show && !chevron) {
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
chevron = document.createElement('button');
|
|
|
|
|
chevron.className = 'allday-chevron collapsed';
|
|
|
|
|
chevron.innerHTML = `
|
|
|
|
|
<svg width="12" height="8" viewBox="0 0 12 8">
|
|
|
|
|
<path d="M1 1.5L6 6.5L11 1.5" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
|
|
|
</svg>
|
|
|
|
|
`;
|
|
|
|
|
chevron.onclick = () => this.toggleExpanded();
|
|
|
|
|
headerSpacer.appendChild(chevron);
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
} else if (!show && chevron) {
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
chevron.remove();
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
} else if (chevron) {
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
chevron.classList.toggle('collapsed', !this.isExpanded);
|
|
|
|
|
chevron.classList.toggle('expanded', this.isExpanded);
|
2025-10-04 23:10:09 +02:00
|
|
|
|
2025-09-30 00:34:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggle between expanded and collapsed state
|
|
|
|
|
*/
|
|
|
|
|
private toggleExpanded(): void {
|
|
|
|
|
this.isExpanded = !this.isExpanded;
|
|
|
|
|
this.checkAndAnimateAllDayHeight();
|
2025-10-02 15:57:11 +02:00
|
|
|
|
2025-10-04 23:10:09 +02:00
|
|
|
const elements = document.querySelectorAll('swp-allday-container swp-allday-event.max-event-overflow-hide, swp-allday-container swp-allday-event.max-event-overflow-show');
|
|
|
|
|
|
2025-10-02 16:05:11 +02:00
|
|
|
elements.forEach((element) => {
|
2025-10-04 23:10:09 +02:00
|
|
|
if (this.isExpanded) {
|
|
|
|
|
// ALTID vis når expanded=true
|
2025-10-02 16:05:11 +02:00
|
|
|
element.classList.remove('max-event-overflow-hide');
|
|
|
|
|
element.classList.add('max-event-overflow-show');
|
2025-10-04 23:10:09 +02:00
|
|
|
} else {
|
|
|
|
|
// ALTID skjul når expanded=false
|
2025-10-02 16:05:11 +02:00
|
|
|
element.classList.remove('max-event-overflow-show');
|
|
|
|
|
element.classList.add('max-event-overflow-hide');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-09-30 23:00:34 +02:00
|
|
|
/**
|
2025-11-03 21:30:50 +01:00
|
|
|
* Count number of events in a specific column using IColumnBounds
|
2025-11-11 18:08:48 +01:00
|
|
|
* Reads directly from DOM elements
|
2025-09-30 23:00:34 +02:00
|
|
|
*/
|
2025-11-03 21:30:50 +01:00
|
|
|
private countEventsInColumn(columnBounds: IColumnBounds): number {
|
2025-11-11 18:08:48 +01:00
|
|
|
return this.countEventsInColumnFromDOM(columnBounds.index);
|
2025-10-02 16:57:43 +02:00
|
|
|
}
|
2025-09-30 23:00:34 +02:00
|
|
|
|
2025-09-30 15:24:58 +02:00
|
|
|
/**
|
|
|
|
|
* Update overflow indicators for collapsed state
|
|
|
|
|
*/
|
|
|
|
|
private updateOverflowIndicators(): void {
|
2025-10-02 16:57:43 +02:00
|
|
|
const container = this.getAllDayContainer();
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
// Create overflow indicators for each column that needs them
|
|
|
|
|
let columns = ColumnDetectionUtils.getColumns();
|
|
|
|
|
|
|
|
|
|
columns.forEach((columnBounds) => {
|
|
|
|
|
let totalEventsInColumn = this.countEventsInColumn(columnBounds);
|
|
|
|
|
let overflowCount = totalEventsInColumn - ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS
|
|
|
|
|
|
|
|
|
|
if (overflowCount > 0) {
|
2025-10-02 17:30:48 +02:00
|
|
|
// Check if indicator already exists in this column
|
|
|
|
|
let existingIndicator = container.querySelector(`.max-event-indicator[data-column="${columnBounds.index}"]`) as HTMLElement;
|
|
|
|
|
|
|
|
|
|
if (existingIndicator) {
|
|
|
|
|
// Update existing indicator
|
|
|
|
|
existingIndicator.innerHTML = `<span>+${overflowCount + 1} more</span>`;
|
|
|
|
|
} else {
|
|
|
|
|
// Create new overflow indicator element
|
2025-10-04 23:10:09 +02:00
|
|
|
let overflowElement = document.createElement('swp-allday-event');
|
2025-10-02 17:30:48 +02:00
|
|
|
overflowElement.className = 'max-event-indicator';
|
|
|
|
|
overflowElement.setAttribute('data-column', columnBounds.index.toString());
|
|
|
|
|
overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString();
|
|
|
|
|
overflowElement.style.gridColumn = columnBounds.index.toString();
|
|
|
|
|
overflowElement.innerHTML = `<span>+${overflowCount + 1} more</span>`;
|
|
|
|
|
overflowElement.onclick = (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
this.toggleExpanded();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
container.appendChild(overflowElement);
|
|
|
|
|
}
|
2025-10-02 16:57:43 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-09-30 15:24:58 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear overflow indicators and restore normal state
|
|
|
|
|
*/
|
|
|
|
|
private clearOverflowIndicators(): void {
|
2025-10-02 16:57:43 +02:00
|
|
|
const container = this.getAllDayContainer();
|
|
|
|
|
if (!container) return;
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-10-02 16:57:43 +02:00
|
|
|
// Remove all overflow indicator elements
|
|
|
|
|
container.querySelectorAll('.max-event-indicator').forEach((element) => {
|
|
|
|
|
element.remove();
|
|
|
|
|
});
|
2025-09-30 22:35:07 +02:00
|
|
|
|
2025-10-01 18:41:28 +02:00
|
|
|
|
2025-10-02 16:57:43 +02:00
|
|
|
}
|
2025-09-30 15:24:58 +02:00
|
|
|
|
2025-09-12 00:36:02 +02:00
|
|
|
}
|