diff --git a/src/managers/ColumnDetector.ts b/src/managers/ColumnDetector.ts index 9b71ac6..9255dab 100644 --- a/src/managers/ColumnDetector.ts +++ b/src/managers/ColumnDetector.ts @@ -120,8 +120,8 @@ export class ColumnDetector { }); this.lastLoggedPosition = { x: event.clientX, y: event.clientY }; - // Snap klonens position til nærmeste 15-min interval - if (this.draggedClone && this.draggedClone.parentElement) { + // Snap klonens position til nærmeste 15-min interval (only for timed events) + if (this.draggedClone && this.draggedClone.parentElement && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') { const columnRect = this.draggedClone.parentElement.getBoundingClientRect(); const rawRelativeY = event.clientY - columnRect.top - this.mouseOffset.y; @@ -131,8 +131,8 @@ export class ColumnDetector { } } - // Kontinuerlig opdatering under auto-scroll for at sikre klonen følger musen - if (this.draggedClone && this.draggedClone.parentElement && this.autoScrollAnimationId !== null) { + // Kontinuerlig opdatering under auto-scroll for at sikre klonen følger musen (only for timed events) + if (this.draggedClone && this.draggedClone.parentElement && this.autoScrollAnimationId !== null && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') { const columnRect = this.draggedClone.parentElement.getBoundingClientRect(); const relativeY = event.clientY - columnRect.top - this.mouseOffset.y; this.draggedClone.style.top = relativeY + 'px'; @@ -176,8 +176,8 @@ export class ColumnDetector { console.log('Entered column:', date); this.currentColumn = date; - // Flyt klonen til ny kolonne ved kolonneskift - if (this.draggedClone && this.isMouseDown) { + // Flyt klonen til ny kolonne ved kolonneskift (only for timed events) + if (this.draggedClone && this.isMouseDown && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') { // Flyt klonen til den nye kolonne const newColumnElement = document.querySelector(`swp-day-column[data-date="${date}"]`); if (newColumnElement) { @@ -411,8 +411,8 @@ export class ColumnDetector { const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed; this.scrollContainer.scrollTop += scrollAmount; - // Update clone position based on current mouse position after scroll - if (this.draggedClone && this.draggedClone.parentElement) { + // Update clone position based on current mouse position after scroll (only for timed events) + if (this.draggedClone && this.draggedClone.parentElement && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') { const columnRect = this.draggedClone.parentElement.getBoundingClientRect(); const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y; this.draggedClone.style.top = relativeY + 'px'; @@ -467,12 +467,33 @@ export class ColumnDetector { const titleElement = clone.querySelector('swp-event-title'); const eventTitle = titleElement ? titleElement.textContent || 'Untitled Event' : 'Untitled Event'; + // Calculate which column this date corresponds to + const dayHeaders = document.querySelectorAll('swp-day-header'); + let columnIndex = 1; // Default to first column + + dayHeaders.forEach((header, index) => { + if ((header as HTMLElement).dataset.date === targetDate) { + columnIndex = index + 1; // 1-based grid index + } + }); + // Create new all-day event element const allDayEvent = document.createElement('swp-allday-event'); allDayEvent.setAttribute('data-event-id', clone.dataset.eventId || ''); allDayEvent.setAttribute('data-type', clone.dataset.type || 'work'); allDayEvent.textContent = eventTitle; + // Position event in correct column and find available row + (allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString(); + + // Find first available row (simple assignment to row 1 for dropped events) + (allDayEvent as HTMLElement).style.gridRow = '1'; + + // Clear any positioning styles from the original timed event (top, left, position, etc.) + (allDayEvent as HTMLElement).style.top = ''; + (allDayEvent as HTMLElement).style.left = ''; + (allDayEvent as HTMLElement).style.position = ''; + // Remove the original clone from its current parent if (clone.parentElement) { clone.parentElement.removeChild(clone); @@ -530,19 +551,11 @@ export class ColumnDetector { return null; } - // Look for existing container for this single column - const containerKey = `${columnIndex}-1`; // single column span - let container = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`); - + // Find the all-day container + const container = calendarHeader.querySelector('swp-allday-container'); if (!container) { - // Create new container - container = document.createElement('swp-allday-container'); - container.setAttribute('data-container-key', containerKey); - container.setAttribute('data-date', targetDate); - (container as HTMLElement).style.gridColumn = `${columnIndex}`; - (container as HTMLElement).style.gridRow = '2'; // All-day row - calendarHeader.appendChild(container); - + console.warn('ColumnDetector: No swp-allday-container found - HeaderRenderer should create this'); + return null; } return container as HTMLElement; @@ -550,33 +563,39 @@ export class ColumnDetector { /** - * Recalculate all-day row height based on maximum events in any container + * Recalculate all-day row height based on number of rows in use */ private recalculateAllDayHeight(): void { const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement; if (!calendarHeader) return; - // Find all all-day containers - const allDayContainers = calendarHeader.querySelectorAll('swp-allday-container'); - let maxStackHeight = 0; + // Find all-day container + const allDayContainer = calendarHeader.querySelector('swp-allday-container'); + if (!allDayContainer) { + console.warn('ColumnDetector: No swp-allday-container found for height recalculation'); + return; + } - // Count events in each container to find maximum - allDayContainers.forEach(container => { - const eventCount = container.querySelectorAll('swp-allday-event').length; - if (eventCount > maxStackHeight) { - maxStackHeight = eventCount; + // Count highest row used by any event + const events = allDayContainer.querySelectorAll('swp-allday-event'); + let maxRow = 1; + + events.forEach(event => { + const gridRow = (event as HTMLElement).style.gridRow; + if (gridRow) { + const rowNum = parseInt(gridRow); + if (rowNum > maxRow) { + maxRow = rowNum; + } } }); - // Calculate new height using same formula as EventRenderer - const calculatedHeight = maxStackHeight > 0 - ? (maxStackHeight * ALL_DAY_CONSTANTS.EVENT_HEIGHT) + - ((maxStackHeight - 1) * ALL_DAY_CONSTANTS.EVENT_GAP) + - ALL_DAY_CONSTANTS.CONTAINER_PADDING - : ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; // Keep minimum height + // Calculate new height + const root = document.documentElement; + const eventHeight = parseInt(getComputedStyle(root).getPropertyValue('--allday-event-height') || '26'); + const calculatedHeight = maxRow * eventHeight; // Get current heights for animation - const root = document.documentElement; const headerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')); const currentAllDayHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0'); const currentTotalHeight = headerHeight + currentAllDayHeight; @@ -620,7 +639,7 @@ export class ColumnDetector { eventBus.emit('header:height-changed'); }); - console.log(`Animated all-day height: ${currentAllDayHeight}px → ${calculatedHeight}px (max stack: ${maxStackHeight})`); + console.log(`Animated all-day height: ${currentAllDayHeight}px → ${calculatedHeight}px (max stack: ${maxRow})`); } } diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index f3ff195..e2fce03 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -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(); - 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(); + // 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): { 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 diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts index 7bffc23..7805472 100644 --- a/src/renderers/GridRenderer.ts +++ b/src/renderers/GridRenderer.ts @@ -200,8 +200,28 @@ export class GridRenderer { const allDayContainer = target.closest('swp-allday-container'); if (dayHeader || allDayContainer) { - const hoveredElement = dayHeader || allDayContainer; - const targetDate = (hoveredElement as HTMLElement).dataset.date; + let hoveredElement: HTMLElement; + let targetDate: string | undefined; + + if (dayHeader) { + hoveredElement = dayHeader as HTMLElement; + targetDate = hoveredElement.dataset.date; + } else if (allDayContainer) { + // For all-day areas, we need to determine which day column we're over + hoveredElement = allDayContainer as HTMLElement; + + // Calculate which day we're hovering over based on mouse position + const headerRect = calendarHeader.getBoundingClientRect(); + const dayHeaders = calendarHeader.querySelectorAll('swp-day-header'); + const mouseX = (event as MouseEvent).clientX - headerRect.left; + const dayWidth = headerRect.width / dayHeaders.length; + const dayIndex = Math.floor(mouseX / dayWidth); + + const targetDayHeader = dayHeaders[dayIndex] as HTMLElement; + targetDate = targetDayHeader?.dataset.date; + } else { + return; // No valid element found + } console.log('GridRenderer: Detected hover over:', { elementType: dayHeader ? 'day-header' : 'all-day-container', diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index 469e797..2428d06 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -38,7 +38,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { } /** - * Ensure all-day containers exist for all days (called automatically after header render) + * Ensure all-day main container and initial stack level exist (called automatically after header render) */ ensureAllDayContainers(calendarHeader: HTMLElement): void { const root = document.documentElement; @@ -49,10 +49,10 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`); } - // Always ensure containers exist for all days - this.createEmptyAllDayContainers(calendarHeader); + // Always ensure main container and initial stack level exist + this.createAllDayMainStructure(calendarHeader); - console.log('BaseHeaderRenderer: Ensured all-day containers exist for all days'); + console.log('BaseHeaderRenderer: Ensured all-day main structure exists'); } private animateHeaderExpansion(calendarHeader: HTMLElement): void { @@ -93,39 +93,25 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { // Set the CSS variable after animation root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`); - // Create empty all-day containers after animation - this.createEmptyAllDayContainers(calendarHeader); + // Create all-day main structure after animation + this.createAllDayMainStructure(calendarHeader); // Notify ScrollManager about header height change eventBus.emit('header:height-changed'); }); } - private createEmptyAllDayContainers(calendarHeader: HTMLElement): void { - const dayHeaders = calendarHeader.querySelectorAll('swp-day-header'); + private createAllDayMainStructure(calendarHeader: HTMLElement): void { + // Check if container already exists + let container = calendarHeader.querySelector('swp-allday-container'); - dayHeaders.forEach((dayHeader, index) => { - const date = (dayHeader as HTMLElement).dataset.date; - if (!date) return; - - const columnIndex = index + 1; // 1-based grid index - const containerKey = `${columnIndex}-1`; - - // Check if container already exists - let container = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`); - - if (!container) { - // Create empty container - container = document.createElement('swp-allday-container'); - container.setAttribute('data-container-key', containerKey); - container.setAttribute('data-date', date); - (container as HTMLElement).style.gridColumn = `${columnIndex}`; - (container as HTMLElement).style.gridRow = '2'; - calendarHeader.appendChild(container); - } - }); + if (!container) { + // Create simple all-day container + container = document.createElement('swp-allday-container'); + calendarHeader.appendChild(container); + } - console.log('Created empty all-day containers for all days'); + console.log('Created simple all-day container'); } } diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index 3810af1..77e864b 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -204,8 +204,28 @@ export class NavigationRenderer { const allDayContainer = target.closest('swp-allday-container'); if (dayHeader || allDayContainer) { - const hoveredElement = dayHeader || allDayContainer; - const targetDate = (hoveredElement as HTMLElement).dataset.date; + let hoveredElement: HTMLElement; + let targetDate: string | undefined; + + if (dayHeader) { + hoveredElement = dayHeader as HTMLElement; + targetDate = hoveredElement.dataset.date; + } else if (allDayContainer) { + // For all-day areas, we need to determine which day column we're over + hoveredElement = allDayContainer as HTMLElement; + + // Calculate which day we're hovering over based on mouse position + const headerRect = calendarHeader.getBoundingClientRect(); + const dayHeaders = calendarHeader.querySelectorAll('swp-day-header'); + const mouseX = (event as MouseEvent).clientX - headerRect.left; + const dayWidth = headerRect.width / dayHeaders.length; + const dayIndex = Math.floor(mouseX / dayWidth); + + const targetDayHeader = dayHeaders[dayIndex] as HTMLElement; + targetDate = targetDayHeader?.dataset.date; + } else { + return; // No valid element found + } console.log('NavigationRenderer: Detected hover over:', { elementType: dayHeader ? 'day-header' : 'all-day-container', diff --git a/wwwroot/css/calendar-base-css.css b/wwwroot/css/calendar-base-css.css index b033710..28b605c 100644 --- a/wwwroot/css/calendar-base-css.css +++ b/wwwroot/css/calendar-base-css.css @@ -17,6 +17,8 @@ --week-days: 7; --header-height: 80px; --all-day-row-height: 0px; /* Default height for all-day events row */ + --allday-event-height: 26px; /* Height of single all-day event including gaps */ + --stack-levels: 1; /* Number of stack levels for all-day events */ /* Time boundaries - Default fallback values */ --day-start-hour: 0; diff --git a/wwwroot/css/calendar-layout-css.css b/wwwroot/css/calendar-layout-css.css index a09f956..9caf950 100644 --- a/wwwroot/css/calendar-layout-css.css +++ b/wwwroot/css/calendar-layout-css.css @@ -267,15 +267,18 @@ swp-day-header[data-today="true"] swp-day-date { } -/* All-day event container - spans columns as needed */ +/* All-day container - simple container that expands with auto-rows */ swp-allday-container { + grid-column: 1 / -1; /* Span all columns */ + grid-row: 2; /* Second row of calendar header */ display: grid; - grid-template-columns: 1fr; /* Single column for now, can expand later */ - grid-auto-rows: min-content; + grid-template-columns: repeat(var(--grid-columns, 7), minmax(var(--day-column-min-width), 1fr)); + grid-auto-rows: var(--allday-event-height, 26px); /* Auto-expand rows as needed */ gap: 2px; padding: 2px; - height: 100%; + align-items: center; overflow: hidden; + min-height: var(--allday-event-height, 26px); } /* All-day events in containers */