Back to a single swp-allday-container
This commit is contained in:
parent
07402a07d9
commit
f9b7686b22
7 changed files with 252 additions and 182 deletions
|
|
@ -71,7 +71,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
* Render all-day events in the header row 2
|
||||
*/
|
||||
protected renderAllDayEvents(allDayEvents: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void {
|
||||
console.log(`BaseEventRenderer: Rendering ${allDayEvents.length} all-day events`);
|
||||
console.log(`BaseEventRenderer: Rendering ${allDayEvents.length} all-day events using nested grid`);
|
||||
|
||||
// Find the calendar header
|
||||
const calendarHeader = container.querySelector('swp-calendar-header');
|
||||
|
|
@ -79,131 +79,97 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
console.warn('BaseEventRenderer: No calendar header found for all-day events');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear content of existing all-day containers (but keep the containers)
|
||||
const existingContainers = calendarHeader.querySelectorAll('swp-allday-container');
|
||||
existingContainers.forEach(container => {
|
||||
container.innerHTML = ''; // Clear content, keep container
|
||||
});
|
||||
|
||||
// Track maximum number of stacked events to calculate row height
|
||||
let maxStackHeight = 0;
|
||||
|
||||
// Get day headers to build date map
|
||||
// Find the all-day container
|
||||
const allDayContainer = calendarHeader.querySelector('swp-allday-container') as HTMLElement;
|
||||
if (!allDayContainer) {
|
||||
console.warn('BaseEventRenderer: No swp-allday-container found - HeaderRenderer should create this');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing events
|
||||
allDayContainer.innerHTML = '';
|
||||
|
||||
if (allDayEvents.length === 0) {
|
||||
// No events - just return
|
||||
this.updateAllDayHeight(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build date to column mapping
|
||||
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
||||
const dateToColumnMap = new Map<string, number>();
|
||||
const visibleDates: string[] = [];
|
||||
|
||||
dayHeaders.forEach((header, index) => {
|
||||
const dateStr = (header as any).dataset.date;
|
||||
if (dateStr) {
|
||||
dateToColumnMap.set(dateStr, index + 1); // 1-based column index
|
||||
visibleDates.push(dateStr);
|
||||
}
|
||||
});
|
||||
|
||||
// Group events by their start column for container creation
|
||||
const eventsByStartColumn = new Map<number, CalendarEvent[]>();
|
||||
// Calculate grid spans for all events
|
||||
const eventSpans = allDayEvents.map(event => ({
|
||||
event,
|
||||
span: this.calculateEventGridSpan(event, dateToColumnMap)
|
||||
})).filter(item => item.span.columnSpan > 0); // Remove events outside visible range
|
||||
|
||||
// Simple row assignment using overlap detection
|
||||
const eventPlacements: Array<{ event: CalendarEvent, span: { startColumn: number, columnSpan: number }, row: number }> = [];
|
||||
|
||||
allDayEvents.forEach(event => {
|
||||
const startDate = new Date(event.start);
|
||||
const startDateKey = this.dateCalculator.formatISODate(startDate);
|
||||
const startColumn = dateToColumnMap.get(startDateKey);
|
||||
eventSpans.forEach(eventItem => {
|
||||
let assignedRow = 1;
|
||||
|
||||
if (!startColumn) {
|
||||
console.log(`BaseEventRenderer: Event "${event.title}" starts outside visible week`);
|
||||
return;
|
||||
// Find first row where this event doesn't overlap with any existing event
|
||||
while (true) {
|
||||
const rowEvents = eventPlacements.filter(item => item.row === assignedRow);
|
||||
const hasOverlap = rowEvents.some(rowEvent =>
|
||||
this.eventsOverlap(eventItem.span, rowEvent.span)
|
||||
);
|
||||
|
||||
if (!hasOverlap) {
|
||||
break; // Found available row
|
||||
}
|
||||
assignedRow++;
|
||||
}
|
||||
|
||||
// Store event with its start column
|
||||
if (!eventsByStartColumn.has(startColumn)) {
|
||||
eventsByStartColumn.set(startColumn, []);
|
||||
}
|
||||
eventsByStartColumn.get(startColumn)!.push(event);
|
||||
});
|
||||
|
||||
// Create containers and render events
|
||||
eventsByStartColumn.forEach((events, startColumn) => {
|
||||
events.forEach(event => {
|
||||
const startDate = new Date(event.start);
|
||||
const endDate = new Date(event.end);
|
||||
|
||||
// Calculate span
|
||||
let endColumn = startColumn;
|
||||
const currentDate = new Date(startDate);
|
||||
|
||||
while (currentDate <= endDate) {
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
const dateKey = this.dateCalculator.formatISODate(currentDate);
|
||||
const col = dateToColumnMap.get(dateKey);
|
||||
if (col) {
|
||||
endColumn = col;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const columnSpan = endColumn - startColumn + 1;
|
||||
let allDayContainer: Element | null = null;
|
||||
|
||||
if (columnSpan === 1) {
|
||||
// Single day event - use existing single-day container
|
||||
const containerKey = `${startColumn}-1`;
|
||||
allDayContainer = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
|
||||
} else {
|
||||
// Multi-day event - create or find spanning container
|
||||
const containerKey = `${startColumn}-${columnSpan}`;
|
||||
allDayContainer = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
|
||||
|
||||
if (!allDayContainer) {
|
||||
// Create container that spans the appropriate columns
|
||||
allDayContainer = document.createElement('swp-allday-container');
|
||||
allDayContainer.setAttribute('data-container-key', containerKey);
|
||||
allDayContainer.setAttribute('data-date', this.dateCalculator.formatISODate(startDate));
|
||||
(allDayContainer as HTMLElement).style.gridColumn = `${startColumn} / span ${columnSpan}`;
|
||||
(allDayContainer as HTMLElement).style.gridRow = '2';
|
||||
calendarHeader.appendChild(allDayContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allDayContainer) {
|
||||
console.warn(`BaseEventRenderer: No container found for event "${event.title}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the all-day event element inside container
|
||||
const allDayEvent = document.createElement('swp-allday-event');
|
||||
allDayEvent.textContent = event.title;
|
||||
allDayEvent.setAttribute('data-event-id', event.id);
|
||||
allDayEvent.setAttribute('data-type', event.type || 'work');
|
||||
|
||||
// Use event metadata for color if available
|
||||
if (event.metadata?.color) {
|
||||
(allDayEvent as HTMLElement).style.backgroundColor = event.metadata.color;
|
||||
}
|
||||
|
||||
console.log(`BaseEventRenderer: All-day event "${event.title}" in container spanning columns ${startColumn} to ${endColumn}`);
|
||||
|
||||
allDayContainer.appendChild(allDayEvent);
|
||||
|
||||
// Track max stack height
|
||||
const containerEventCount = allDayContainer.querySelectorAll('swp-allday-event').length;
|
||||
if (containerEventCount > maxStackHeight) {
|
||||
maxStackHeight = containerEventCount;
|
||||
}
|
||||
eventPlacements.push({
|
||||
event: eventItem.event,
|
||||
span: eventItem.span,
|
||||
row: assignedRow
|
||||
});
|
||||
});
|
||||
|
||||
// Get max row needed
|
||||
const maxRow = Math.max(...eventPlacements.map(item => item.row), 1);
|
||||
|
||||
// Place events directly in the single container
|
||||
eventPlacements.forEach(({ event, span, row }) => {
|
||||
// Create the all-day event element
|
||||
const allDayEvent = document.createElement('swp-allday-event');
|
||||
allDayEvent.textContent = event.title;
|
||||
allDayEvent.setAttribute('data-event-id', event.id);
|
||||
allDayEvent.setAttribute('data-type', event.type || 'work');
|
||||
|
||||
// Set grid position (column and row)
|
||||
(allDayEvent as HTMLElement).style.gridColumn = span.columnSpan > 1
|
||||
? `${span.startColumn} / span ${span.columnSpan}`
|
||||
: `${span.startColumn}`;
|
||||
(allDayEvent as HTMLElement).style.gridRow = row.toString();
|
||||
|
||||
// Use event metadata for color if available
|
||||
if (event.metadata?.color) {
|
||||
(allDayEvent as HTMLElement).style.backgroundColor = event.metadata.color;
|
||||
}
|
||||
|
||||
allDayContainer.appendChild(allDayEvent);
|
||||
|
||||
console.log(`BaseEventRenderer: Placed "${event.title}" in row ${row}, columns ${span.startColumn} to ${span.startColumn + span.columnSpan - 1}`);
|
||||
});
|
||||
|
||||
// Update height based on max row
|
||||
this.updateAllDayHeight(maxRow);
|
||||
|
||||
// Calculate and set the all-day row height based on max stack
|
||||
const calculatedHeight = maxStackHeight > 0
|
||||
? (maxStackHeight * ALL_DAY_CONSTANTS.EVENT_HEIGHT) + ((maxStackHeight - 1) * ALL_DAY_CONSTANTS.EVENT_GAP) + ALL_DAY_CONSTANTS.CONTAINER_PADDING
|
||||
: 0; // No height if no events
|
||||
|
||||
// Only set CSS variable - header-spacer height is handled by CSS calc()
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--all-day-row-height', `${calculatedHeight}px`);
|
||||
|
||||
console.log(`BaseEventRenderer: Set all-day row height to ${calculatedHeight}px (max stack: ${maxStackHeight})`);
|
||||
console.log(`BaseEventRenderer: Created ${maxRow} rows with auto-expanding grid`);
|
||||
}
|
||||
|
||||
protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void {
|
||||
|
|
@ -284,6 +250,60 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all-day row height based on number of rows
|
||||
*/
|
||||
private updateAllDayHeight(maxRows: number): void {
|
||||
const root = document.documentElement;
|
||||
const eventHeight = parseInt(getComputedStyle(root).getPropertyValue('--allday-event-height') || '26');
|
||||
const calculatedHeight = maxRows * eventHeight;
|
||||
root.style.setProperty('--all-day-row-height', `${calculatedHeight}px`);
|
||||
|
||||
console.log(`BaseEventRenderer: Set all-day height to ${calculatedHeight}px for ${maxRows} rows`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate grid column span for event
|
||||
*/
|
||||
private calculateEventGridSpan(event: CalendarEvent, dateToColumnMap: Map<string, number>): { startColumn: number, columnSpan: number } {
|
||||
const startDate = new Date(event.start);
|
||||
const endDate = new Date(event.end);
|
||||
const startDateKey = this.dateCalculator.formatISODate(startDate);
|
||||
const startColumn = dateToColumnMap.get(startDateKey);
|
||||
|
||||
if (!startColumn) {
|
||||
return { startColumn: 0, columnSpan: 0 }; // Event outside visible range
|
||||
}
|
||||
|
||||
// Calculate span by checking each day
|
||||
let endColumn = startColumn;
|
||||
const currentDate = new Date(startDate);
|
||||
|
||||
while (currentDate <= endDate) {
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
const dateKey = this.dateCalculator.formatISODate(currentDate);
|
||||
const col = dateToColumnMap.get(dateKey);
|
||||
if (col) {
|
||||
endColumn = col;
|
||||
} else {
|
||||
break; // Event extends beyond visible range
|
||||
}
|
||||
}
|
||||
|
||||
const columnSpan = endColumn - startColumn + 1;
|
||||
return { startColumn, columnSpan };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two events overlap in columns
|
||||
*/
|
||||
private eventsOverlap(event1Span: { startColumn: number, columnSpan: number }, event2Span: { startColumn: number, columnSpan: number }): boolean {
|
||||
const event1End = event1Span.startColumn + event1Span.columnSpan - 1;
|
||||
const event2End = event2Span.startColumn + event2Span.columnSpan - 1;
|
||||
|
||||
return !(event1End < event2Span.startColumn || event2End < event1Span.startColumn);
|
||||
}
|
||||
|
||||
clearEvents(container?: HTMLElement): void {
|
||||
const selector = 'swp-event';
|
||||
const existingEvents = container
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue