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
|
|
@ -120,8 +120,8 @@ export class ColumnDetector {
|
||||||
});
|
});
|
||||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Snap klonens position til nærmeste 15-min interval
|
// Snap klonens position til nærmeste 15-min interval (only for timed events)
|
||||||
if (this.draggedClone && this.draggedClone.parentElement) {
|
if (this.draggedClone && this.draggedClone.parentElement && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
|
||||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||||
const rawRelativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
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
|
// 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) {
|
if (this.draggedClone && this.draggedClone.parentElement && this.autoScrollAnimationId !== null && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
|
||||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||||
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
||||||
this.draggedClone.style.top = relativeY + 'px';
|
this.draggedClone.style.top = relativeY + 'px';
|
||||||
|
|
@ -176,8 +176,8 @@ export class ColumnDetector {
|
||||||
console.log('Entered column:', date);
|
console.log('Entered column:', date);
|
||||||
this.currentColumn = date;
|
this.currentColumn = date;
|
||||||
|
|
||||||
// Flyt klonen til ny kolonne ved kolonneskift
|
// Flyt klonen til ny kolonne ved kolonneskift (only for timed events)
|
||||||
if (this.draggedClone && this.isMouseDown) {
|
if (this.draggedClone && this.isMouseDown && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
|
||||||
// Flyt klonen til den nye kolonne
|
// Flyt klonen til den nye kolonne
|
||||||
const newColumnElement = document.querySelector(`swp-day-column[data-date="${date}"]`);
|
const newColumnElement = document.querySelector(`swp-day-column[data-date="${date}"]`);
|
||||||
if (newColumnElement) {
|
if (newColumnElement) {
|
||||||
|
|
@ -411,8 +411,8 @@ export class ColumnDetector {
|
||||||
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
||||||
this.scrollContainer.scrollTop += scrollAmount;
|
this.scrollContainer.scrollTop += scrollAmount;
|
||||||
|
|
||||||
// Update clone position based on current mouse position after scroll
|
// Update clone position based on current mouse position after scroll (only for timed events)
|
||||||
if (this.draggedClone && this.draggedClone.parentElement) {
|
if (this.draggedClone && this.draggedClone.parentElement && this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') {
|
||||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||||
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
||||||
this.draggedClone.style.top = relativeY + 'px';
|
this.draggedClone.style.top = relativeY + 'px';
|
||||||
|
|
@ -467,12 +467,33 @@ export class ColumnDetector {
|
||||||
const titleElement = clone.querySelector('swp-event-title');
|
const titleElement = clone.querySelector('swp-event-title');
|
||||||
const eventTitle = titleElement ? titleElement.textContent || 'Untitled Event' : 'Untitled Event';
|
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
|
// Create new all-day event element
|
||||||
const allDayEvent = document.createElement('swp-allday-event');
|
const allDayEvent = document.createElement('swp-allday-event');
|
||||||
allDayEvent.setAttribute('data-event-id', clone.dataset.eventId || '');
|
allDayEvent.setAttribute('data-event-id', clone.dataset.eventId || '');
|
||||||
allDayEvent.setAttribute('data-type', clone.dataset.type || 'work');
|
allDayEvent.setAttribute('data-type', clone.dataset.type || 'work');
|
||||||
allDayEvent.textContent = eventTitle;
|
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
|
// Remove the original clone from its current parent
|
||||||
if (clone.parentElement) {
|
if (clone.parentElement) {
|
||||||
clone.parentElement.removeChild(clone);
|
clone.parentElement.removeChild(clone);
|
||||||
|
|
@ -530,19 +551,11 @@ export class ColumnDetector {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for existing container for this single column
|
// Find the all-day container
|
||||||
const containerKey = `${columnIndex}-1`; // single column span
|
const container = calendarHeader.querySelector('swp-allday-container');
|
||||||
let container = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
|
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
// Create new container
|
console.warn('ColumnDetector: No swp-allday-container found - HeaderRenderer should create this');
|
||||||
container = document.createElement('swp-allday-container');
|
return null;
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return container as HTMLElement;
|
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 {
|
private recalculateAllDayHeight(): void {
|
||||||
const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement;
|
const calendarHeader = document.querySelector('swp-calendar-header') as HTMLElement;
|
||||||
if (!calendarHeader) return;
|
if (!calendarHeader) return;
|
||||||
|
|
||||||
// Find all all-day containers
|
// Find all-day container
|
||||||
const allDayContainers = calendarHeader.querySelectorAll('swp-allday-container');
|
const allDayContainer = calendarHeader.querySelector('swp-allday-container');
|
||||||
let maxStackHeight = 0;
|
if (!allDayContainer) {
|
||||||
|
console.warn('ColumnDetector: No swp-allday-container found for height recalculation');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Count events in each container to find maximum
|
// Count highest row used by any event
|
||||||
allDayContainers.forEach(container => {
|
const events = allDayContainer.querySelectorAll('swp-allday-event');
|
||||||
const eventCount = container.querySelectorAll('swp-allday-event').length;
|
let maxRow = 1;
|
||||||
if (eventCount > maxStackHeight) {
|
|
||||||
maxStackHeight = eventCount;
|
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
|
// Calculate new height
|
||||||
const calculatedHeight = maxStackHeight > 0
|
const root = document.documentElement;
|
||||||
? (maxStackHeight * ALL_DAY_CONSTANTS.EVENT_HEIGHT) +
|
const eventHeight = parseInt(getComputedStyle(root).getPropertyValue('--allday-event-height') || '26');
|
||||||
((maxStackHeight - 1) * ALL_DAY_CONSTANTS.EVENT_GAP) +
|
const calculatedHeight = maxRow * eventHeight;
|
||||||
ALL_DAY_CONSTANTS.CONTAINER_PADDING
|
|
||||||
: ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT; // Keep minimum height
|
|
||||||
|
|
||||||
// Get current heights for animation
|
// Get current heights for animation
|
||||||
const root = document.documentElement;
|
|
||||||
const headerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height'));
|
const headerHeight = parseInt(getComputedStyle(root).getPropertyValue('--header-height'));
|
||||||
const currentAllDayHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
|
const currentAllDayHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
|
||||||
const currentTotalHeight = headerHeight + currentAllDayHeight;
|
const currentTotalHeight = headerHeight + currentAllDayHeight;
|
||||||
|
|
@ -620,7 +639,7 @@ export class ColumnDetector {
|
||||||
eventBus.emit('header:height-changed');
|
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})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
||||||
* Render all-day events in the header row 2
|
* Render all-day events in the header row 2
|
||||||
*/
|
*/
|
||||||
protected renderAllDayEvents(allDayEvents: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void {
|
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
|
// Find the calendar header
|
||||||
const calendarHeader = container.querySelector('swp-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');
|
console.warn('BaseEventRenderer: No calendar header found for all-day events');
|
||||||
return;
|
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 dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
||||||
const dateToColumnMap = new Map<string, number>();
|
const dateToColumnMap = new Map<string, number>();
|
||||||
const visibleDates: string[] = [];
|
|
||||||
|
|
||||||
dayHeaders.forEach((header, index) => {
|
dayHeaders.forEach((header, index) => {
|
||||||
const dateStr = (header as any).dataset.date;
|
const dateStr = (header as any).dataset.date;
|
||||||
if (dateStr) {
|
if (dateStr) {
|
||||||
dateToColumnMap.set(dateStr, index + 1); // 1-based column index
|
dateToColumnMap.set(dateStr, index + 1); // 1-based column index
|
||||||
visibleDates.push(dateStr);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Group events by their start column for container creation
|
// Calculate grid spans for all events
|
||||||
const eventsByStartColumn = new Map<number, CalendarEvent[]>();
|
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 => {
|
eventSpans.forEach(eventItem => {
|
||||||
const startDate = new Date(event.start);
|
let assignedRow = 1;
|
||||||
const startDateKey = this.dateCalculator.formatISODate(startDate);
|
|
||||||
const startColumn = dateToColumnMap.get(startDateKey);
|
|
||||||
|
|
||||||
if (!startColumn) {
|
// Find first row where this event doesn't overlap with any existing event
|
||||||
console.log(`BaseEventRenderer: Event "${event.title}" starts outside visible week`);
|
while (true) {
|
||||||
return;
|
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
|
eventPlacements.push({
|
||||||
if (!eventsByStartColumn.has(startColumn)) {
|
event: eventItem.event,
|
||||||
eventsByStartColumn.set(startColumn, []);
|
span: eventItem.span,
|
||||||
}
|
row: assignedRow
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
console.log(`BaseEventRenderer: Created ${maxRow} rows with auto-expanding grid`);
|
||||||
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})`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void {
|
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}`;
|
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 {
|
clearEvents(container?: HTMLElement): void {
|
||||||
const selector = 'swp-event';
|
const selector = 'swp-event';
|
||||||
const existingEvents = container
|
const existingEvents = container
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,28 @@ export class GridRenderer {
|
||||||
const allDayContainer = target.closest('swp-allday-container');
|
const allDayContainer = target.closest('swp-allday-container');
|
||||||
|
|
||||||
if (dayHeader || allDayContainer) {
|
if (dayHeader || allDayContainer) {
|
||||||
const hoveredElement = dayHeader || allDayContainer;
|
let hoveredElement: HTMLElement;
|
||||||
const targetDate = (hoveredElement as HTMLElement).dataset.date;
|
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:', {
|
console.log('GridRenderer: Detected hover over:', {
|
||||||
elementType: dayHeader ? 'day-header' : 'all-day-container',
|
elementType: dayHeader ? 'day-header' : 'all-day-container',
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
ensureAllDayContainers(calendarHeader: HTMLElement): void {
|
||||||
const root = document.documentElement;
|
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`);
|
root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always ensure containers exist for all days
|
// Always ensure main container and initial stack level exist
|
||||||
this.createEmptyAllDayContainers(calendarHeader);
|
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 {
|
private animateHeaderExpansion(calendarHeader: HTMLElement): void {
|
||||||
|
|
@ -93,39 +93,25 @@ export abstract class BaseHeaderRenderer implements HeaderRenderer {
|
||||||
// Set the CSS variable after animation
|
// Set the CSS variable after animation
|
||||||
root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`);
|
root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`);
|
||||||
|
|
||||||
// Create empty all-day containers after animation
|
// Create all-day main structure after animation
|
||||||
this.createEmptyAllDayContainers(calendarHeader);
|
this.createAllDayMainStructure(calendarHeader);
|
||||||
|
|
||||||
// Notify ScrollManager about header height change
|
// Notify ScrollManager about header height change
|
||||||
eventBus.emit('header:height-changed');
|
eventBus.emit('header:height-changed');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createEmptyAllDayContainers(calendarHeader: HTMLElement): void {
|
private createAllDayMainStructure(calendarHeader: HTMLElement): void {
|
||||||
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
// Check if container already exists
|
||||||
|
let container = calendarHeader.querySelector('swp-allday-container');
|
||||||
|
|
||||||
dayHeaders.forEach((dayHeader, index) => {
|
if (!container) {
|
||||||
const date = (dayHeader as HTMLElement).dataset.date;
|
// Create simple all-day container
|
||||||
if (!date) return;
|
container = document.createElement('swp-allday-container');
|
||||||
|
calendarHeader.appendChild(container);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Created empty all-day containers for all days');
|
console.log('Created simple all-day container');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -204,8 +204,28 @@ export class NavigationRenderer {
|
||||||
const allDayContainer = target.closest('swp-allday-container');
|
const allDayContainer = target.closest('swp-allday-container');
|
||||||
|
|
||||||
if (dayHeader || allDayContainer) {
|
if (dayHeader || allDayContainer) {
|
||||||
const hoveredElement = dayHeader || allDayContainer;
|
let hoveredElement: HTMLElement;
|
||||||
const targetDate = (hoveredElement as HTMLElement).dataset.date;
|
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:', {
|
console.log('NavigationRenderer: Detected hover over:', {
|
||||||
elementType: dayHeader ? 'day-header' : 'all-day-container',
|
elementType: dayHeader ? 'day-header' : 'all-day-container',
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
--week-days: 7;
|
--week-days: 7;
|
||||||
--header-height: 80px;
|
--header-height: 80px;
|
||||||
--all-day-row-height: 0px; /* Default height for all-day events row */
|
--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 */
|
/* Time boundaries - Default fallback values */
|
||||||
--day-start-hour: 0;
|
--day-start-hour: 0;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
swp-allday-container {
|
||||||
|
grid-column: 1 / -1; /* Span all columns */
|
||||||
|
grid-row: 2; /* Second row of calendar header */
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr; /* Single column for now, can expand later */
|
grid-template-columns: repeat(var(--grid-columns, 7), minmax(var(--day-column-min-width), 1fr));
|
||||||
grid-auto-rows: min-content;
|
grid-auto-rows: var(--allday-event-height, 26px); /* Auto-expand rows as needed */
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
height: 100%;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: var(--allday-event-height, 26px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All-day events in containers */
|
/* All-day events in containers */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue