diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index 359f617..1f907d7 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -5,6 +5,7 @@ import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig'; import { CalendarConfig } from '../core/CalendarConfig'; import { DateCalculator } from '../utils/DateCalculator'; import { eventBus } from '../core/EventBus'; +import { CoreEvents } from '../constants/CoreEvents'; /** * Interface for event rendering strategies @@ -63,6 +64,15 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { const { eventId, targetDate, headerRenderer } = (event as CustomEvent).detail; this.handleConvertToAllDay(eventId, targetDate, headerRenderer); }); + + // Handle navigation period change (when slide animation completes) + eventBus.on(CoreEvents.PERIOD_CHANGED, () => { + // Animate all-day height after navigation completes + import('./HeaderRenderer').then(({ DateHeaderRenderer }) => { + const headerRenderer = new DateHeaderRenderer(); + headerRenderer.checkAndAnimateAllDayHeight(); + }); + }); } /** @@ -278,10 +288,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { const allDayContainer = calendarHeader.querySelector('swp-allday-container'); if (!allDayContainer) return; - // Extract title + // Extract all original event data const titleElement = clone.querySelector('swp-event-title'); const eventTitle = titleElement ? titleElement.textContent || 'Untitled' : 'Untitled'; + const timeElement = clone.querySelector('swp-event-time'); + const eventTime = timeElement ? timeElement.textContent || '' : ''; + const eventDuration = timeElement ? timeElement.getAttribute('data-duration') || '' : ''; + // Calculate column index const dayHeaders = document.querySelectorAll('swp-day-header'); let columnIndex = 1; @@ -291,15 +305,19 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { } }); - // Create all-day event + // Create all-day event with standardized data attributes const allDayEvent = document.createElement('swp-allday-event'); allDayEvent.dataset.eventId = clone.dataset.eventId || ''; + allDayEvent.dataset.title = eventTitle; + allDayEvent.dataset.start = `${targetDate}T${eventTime.split(' - ')[0]}:00`; + allDayEvent.dataset.end = `${targetDate}T${eventTime.split(' - ')[1]}:00`; allDayEvent.dataset.type = clone.dataset.type || 'work'; + allDayEvent.dataset.duration = eventDuration; allDayEvent.textContent = eventTitle; // Position in grid (allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString(); - (allDayEvent as HTMLElement).style.gridRow = '1'; + // grid-row will be set by checkAndAnimateAllDayHeight() based on actual position // Remove original clone if (clone.parentElement) { @@ -311,8 +329,16 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { // Update reference this.draggedClone = allDayEvent; + + // Check if height animation is needed + import('./HeaderRenderer').then(({ DateHeaderRenderer }) => { + const headerRenderer = new DateHeaderRenderer(); + headerRenderer.checkAndAnimateAllDayHeight(); + }); } + + /** * Fade out and remove element */ @@ -487,8 +513,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { // 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 data attributes directly from CalendarEvent + allDayEvent.dataset.eventId = event.id; + allDayEvent.dataset.title = event.title; + allDayEvent.dataset.start = event.start; + allDayEvent.dataset.end = event.end; + allDayEvent.dataset.type = event.type; + allDayEvent.dataset.duration = event.metadata?.duration?.toString() || '60'; // Set grid position (column and row) (allDayEvent as HTMLElement).style.gridColumn = span.columnSpan > 1 @@ -510,7 +542,11 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void { const eventElement = document.createElement('swp-event'); eventElement.dataset.eventId = event.id; + eventElement.dataset.title = event.title; + eventElement.dataset.start = event.start; + eventElement.dataset.end = event.end; eventElement.dataset.type = event.type; + eventElement.dataset.duration = event.metadata?.duration?.toString() || '60'; // Calculate position based on time const position = this.calculateEventPosition(event, config); diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index 74c82d6..24e102a 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -12,6 +12,7 @@ export interface HeaderRenderer { render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; addToAllDay(dayHeader: HTMLElement): void; ensureAllDayContainers(calendarHeader: HTMLElement): void; + checkAndAnimateAllDayHeight(): void; } /** @@ -33,7 +34,7 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { if (calendarHeader) { // Ensure container exists BEFORE animation this.createAllDayMainStructure(calendarHeader); - this.animateHeaderExpansion(calendarHeader); + this.checkAndAnimateAllDayHeight(); } } } @@ -45,30 +46,82 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { this.createAllDayMainStructure(calendarHeader); } - private animateHeaderExpansion(calendarHeader: HTMLElement): void { - const root = document.documentElement; - const currentHeaderHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')); - const targetHeight = currentHeaderHeight + ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; + checkAndAnimateAllDayHeight(): void { + const container = document.querySelector('swp-allday-container'); + if (!container) return; - // Find header spacer - const headerSpacer = document.querySelector('swp-header-spacer') as HTMLElement; + const allDayEvents = container.querySelectorAll('swp-allday-event'); - // Find or create all-day container (it should exist but be hidden) - let allDayContainer = calendarHeader.querySelector('swp-allday-container') as HTMLElement; - if (!allDayContainer) { - // Create container if it doesn't exist - allDayContainer = document.createElement('swp-allday-container'); - calendarHeader.appendChild(allDayContainer); + // Calculate required rows - 0 if no events (will collapse) + let maxRows = 0; + + if (allDayEvents.length > 0) { + // Expand events to all dates they span and group by date + const expandedEventsByDate: Record = {}; + + (Array.from(allDayEvents) as HTMLElement[]).forEach((event: HTMLElement) => { + const startISO = event.dataset.start || ''; + const endISO = event.dataset.end || startISO; + const eventId = event.dataset.eventId || ''; + + // Extract dates from ISO strings + const startDate = startISO.split('T')[0]; // YYYY-MM-DD + const endDate = endISO.split('T')[0]; // YYYY-MM-DD + + // Loop through all dates from start to end + let current = new Date(startDate); + const end = new Date(endDate); + + while (current <= end) { + const dateStr = current.toISOString().split('T')[0]; // YYYY-MM-DD format + + if (!expandedEventsByDate[dateStr]) { + expandedEventsByDate[dateStr] = []; + } + expandedEventsByDate[dateStr].push(eventId); + + // Move to next day + current.setDate(current.getDate() + 1); + } + }); + + // Find max rows needed + maxRows = Math.max( + ...Object.values(expandedEventsByDate).map(ids => ids?.length || 0), + 0 + ); } - // Animate container and spacer - CSS Grid auto row will handle header expansion + // Animate to required rows (0 = collapse, >0 = expand) + this.animateToRows(maxRows); + } + + /** + * Animate all-day container to specific number of rows + */ + animateToRows(targetRows: number): void { + const root = document.documentElement; + const targetHeight = targetRows * ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; + const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0'); + + if (targetHeight === currentHeight) return; // No animation needed + + console.log(`🎬 All-day height animation starting: ${currentHeight}px → ${targetHeight}px (${Math.ceil(currentHeight / ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT)} → ${targetRows} rows)`); + + // Find elements to animate + const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement; + const headerSpacer = document.querySelector('swp-header-spacer') as HTMLElement; + const allDayContainer = calendarHeader?.querySelector('swp-allday-container') as HTMLElement; + + if (!calendarHeader || !allDayContainer) return; + const animations = [ - // Container visibility and height animation + // Container height animation allDayContainer.animate([ - { height: '0px', opacity: '0' }, - { height: `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`, opacity: '1' } + { height: `${currentHeight}px`, opacity: currentHeight > 0 ? '1' : '0' }, + { height: `${targetHeight}px`, opacity: '1' } ], { - duration: 150, + duration: 1000, easing: 'ease-out', fill: 'forwards' }) @@ -76,24 +129,24 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer { // Add spacer animation if spacer exists if (headerSpacer) { + const currentSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + currentHeight; + const targetSpacerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height')) + targetHeight; + animations.push( headerSpacer.animate([ - { height: `${currentHeaderHeight}px` }, - { height: `${targetHeight}px` } + { height: `${currentSpacerHeight}px` }, + { height: `${targetSpacerHeight}px` } ], { - duration: 150, + duration: 1000, easing: 'ease-out', fill: 'forwards' }) ); } - // Wait for all animations to finish + // Update CSS variable after animation Promise.all(animations.map(anim => anim.finished)).then(() => { - // Set the CSS variable after animation - root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`); - - // Notify ScrollManager about header height change + root.style.setProperty('--all-day-row-height', `${targetHeight}px`); eventBus.emit('header:height-changed'); }); } diff --git a/tsconfig.json b/tsconfig.json index d235242..acb5270 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "rootDir": "./src", "sourceMap": true, "inlineSourceMap": false, - "lib": ["ES2020", "DOM", "DOM.Iterable"] + "lib": ["ES2024", "DOM", "DOM.Iterable"] }, "include": [ "src/**/*"