Some ignored filles was missing

This commit is contained in:
Janus C. H. Knudsen 2026-02-03 00:02:25 +01:00
parent 7db22245e2
commit fd5ab6bc0d
268 changed files with 31970 additions and 4 deletions

View file

@ -0,0 +1,32 @@
import { IEventLayout } from '../utils/AllDayLayoutEngine';
import { IDragStartEventPayload } from '../types/EventTypes';
export declare class AllDayEventRenderer {
private container;
private originalEvent;
private draggedClone;
constructor();
private getContainer;
private getAllDayContainer;
/**
* Handle drag start for all-day events
*/
handleDragStart(payload: IDragStartEventPayload): void;
/**
* Render an all-day event with pre-calculated layout
*/
private renderAllDayEventWithLayout;
/**
* Remove an all-day event by ID
*/
removeAllDayEvent(eventId: string): void;
/**
* Clear cache when DOM changes
*/
clearCache(): void;
/**
* Render all-day events for specific period using AllDayEventRenderer
*/
renderAllDayEventsForPeriod(eventLayouts: IEventLayout[]): void;
private clearAllDayEvents;
handleViewChanged(event: CustomEvent): void;
}

View file

@ -0,0 +1,97 @@
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
export class AllDayEventRenderer {
constructor() {
this.container = null;
this.originalEvent = null;
this.draggedClone = null;
this.getContainer();
}
getContainer() {
const header = document.querySelector('swp-calendar-header');
if (header) {
this.container = header.querySelector('swp-allday-container');
if (!this.container) {
this.container = document.createElement('swp-allday-container');
header.appendChild(this.container);
}
}
return this.container;
}
getAllDayContainer() {
return document.querySelector('swp-calendar-header swp-allday-container');
}
/**
* Handle drag start for all-day events
*/
handleDragStart(payload) {
this.originalEvent = payload.originalElement;
;
this.draggedClone = payload.draggedClone;
if (this.draggedClone) {
const container = this.getAllDayContainer();
if (!container)
return;
this.draggedClone.style.gridColumn = this.originalEvent.style.gridColumn;
this.draggedClone.style.gridRow = this.originalEvent.style.gridRow;
console.log('handleDragStart:this.draggedClone', this.draggedClone);
container.appendChild(this.draggedClone);
// Add dragging style
this.draggedClone.classList.add('dragging');
this.draggedClone.style.zIndex = '1000';
this.draggedClone.style.cursor = 'grabbing';
// Make original semi-transparent
this.originalEvent.style.opacity = '0.3';
this.originalEvent.style.userSelect = 'none';
}
}
/**
* Render an all-day event with pre-calculated layout
*/
renderAllDayEventWithLayout(event, layout) {
const container = this.getContainer();
if (!container)
return null;
const dayEvent = SwpAllDayEventElement.fromCalendarEvent(event);
dayEvent.applyGridPositioning(layout.row, layout.startColumn, layout.endColumn);
// Apply highlight class to show events with highlight color
dayEvent.classList.add('highlight');
container.appendChild(dayEvent);
}
/**
* Remove an all-day event by ID
*/
removeAllDayEvent(eventId) {
const container = this.getContainer();
if (!container)
return;
const eventElement = container.querySelector(`swp-allday-event[data-event-id="${eventId}"]`);
if (eventElement) {
eventElement.remove();
}
}
/**
* Clear cache when DOM changes
*/
clearCache() {
this.container = null;
}
/**
* Render all-day events for specific period using AllDayEventRenderer
*/
renderAllDayEventsForPeriod(eventLayouts) {
this.clearAllDayEvents();
eventLayouts.forEach(layout => {
this.renderAllDayEventWithLayout(layout.calenderEvent, layout);
});
}
clearAllDayEvents() {
const allDayContainer = document.querySelector('swp-allday-container');
if (allDayContainer) {
allDayContainer.querySelectorAll('swp-allday-event:not(.max-event-indicator)').forEach(event => event.remove());
}
}
handleViewChanged(event) {
this.clearAllDayEvents();
}
}
//# sourceMappingURL=AllDayEventRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"AllDayEventRenderer.js","sourceRoot":"","sources":["../../../src/renderers/AllDayEventRenderer.ts"],"names":[],"mappings":"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"}

View file

@ -0,0 +1,26 @@
import { Configuration } from '../configurations/CalendarConfig';
import { DateService } from '../utils/DateService';
import { WorkHoursManager } from '../managers/WorkHoursManager';
/**
* Interface for column rendering strategies
*/
export interface IColumnRenderer {
render(columnContainer: HTMLElement, context: IColumnRenderContext): void;
}
/**
* Context for column rendering
*/
export interface IColumnRenderContext {
currentWeek: Date;
config: Configuration;
}
/**
* Date-based column renderer (original functionality)
*/
export declare class DateColumnRenderer implements IColumnRenderer {
private dateService;
private workHoursManager;
constructor(dateService: DateService, workHoursManager: WorkHoursManager);
render(columnContainer: HTMLElement, context: IColumnRenderContext): void;
private applyWorkHoursToColumn;
}

View file

@ -0,0 +1,44 @@
// Column rendering strategy interface and implementations
/**
* Date-based column renderer (original functionality)
*/
export class DateColumnRenderer {
constructor(dateService, workHoursManager) {
this.dateService = dateService;
this.workHoursManager = workHoursManager;
}
render(columnContainer, context) {
const { currentWeek, config } = context;
const workWeekSettings = config.getWorkWeekSettings();
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
const dateSettings = config.dateViewSettings;
const daysToShow = dates.slice(0, dateSettings.weekDays);
daysToShow.forEach((date) => {
const column = document.createElement('swp-day-column');
column.dataset.date = this.dateService.formatISODate(date);
// Apply work hours styling
this.applyWorkHoursToColumn(column, date);
const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer);
columnContainer.appendChild(column);
});
}
applyWorkHoursToColumn(column, date) {
const workHours = this.workHoursManager.getWorkHoursForDate(date);
if (workHours === 'off') {
// No work hours - mark as off day (full day will be colored)
column.dataset.workHours = 'off';
}
else {
// Calculate and apply non-work hours overlays (before and after work)
const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours);
if (nonWorkStyle) {
// Before work overlay (::before pseudo-element)
column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`);
// After work overlay (::after pseudo-element)
column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`);
}
}
}
}
//# sourceMappingURL=ColumnRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"ColumnRenderer.js","sourceRoot":"","sources":["../../../src/renderers/ColumnRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAqB1D;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAI7B,YACE,WAAwB,EACxB,gBAAkC;QAElC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,eAA4B,EAAE,OAA6B;QAChE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QAGzD,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACvD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEpE,2BAA2B;YAC3B,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAE1C,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;YAC/D,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;YAEhC,eAAe,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,sBAAsB,CAAC,MAAmB,EAAE,IAAU;QAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAElE,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YACxB,6DAA6D;YAC5D,MAAc,CAAC,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAAC;YACjF,IAAI,YAAY,EAAE,CAAC;gBACjB,gDAAgD;gBAChD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,sBAAsB,EAAE,GAAG,YAAY,CAAC,gBAAgB,IAAI,CAAC,CAAC;gBAEvF,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,GAAG,YAAY,CAAC,YAAY,IAAI,CAAC,CAAC;YAEjF,CAAC;QACH,CAAC;IACH,CAAC;CAEF"}

View file

@ -0,0 +1,21 @@
import { Configuration } from '../configurations/CalendarConfig';
/**
* Interface for header rendering strategies
*/
export interface IHeaderRenderer {
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void;
}
/**
* Context for header rendering
*/
export interface IHeaderRenderContext {
currentWeek: Date;
config: Configuration;
}
/**
* Date-based header renderer (original functionality)
*/
export declare class DateHeaderRenderer implements IHeaderRenderer {
private dateService;
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void;
}

View file

@ -0,0 +1,35 @@
// Header rendering strategy interface and implementations
import { DateService } from '../utils/DateService';
/**
* Date-based header renderer (original functionality)
*/
export class DateHeaderRenderer {
render(calendarHeader, context) {
const { currentWeek, config } = context;
// FIRST: Always create all-day container as part of standard header structure
const allDayContainer = document.createElement('swp-allday-container');
calendarHeader.appendChild(allDayContainer);
// Initialize date service with timezone and locale from config
const timezone = config.timeFormatConfig.timezone;
const locale = config.timeFormatConfig.locale;
this.dateService = new DateService(config);
const workWeekSettings = config.getWorkWeekSettings();
const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
const weekDays = config.dateViewSettings.weekDays;
const daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => {
const header = document.createElement('swp-day-header');
if (this.dateService.isSameDay(date, new Date())) {
header.dataset.today = 'true';
}
const dayName = this.dateService.getDayName(date, 'long', locale).toUpperCase();
header.innerHTML = `
<swp-day-name>${dayName}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date>
`;
header.dataset.date = this.dateService.formatISODate(date);
calendarHeader.appendChild(header);
});
}
}
//# sourceMappingURL=DateHeaderRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"DateHeaderRenderer.js","sourceRoot":"","sources":["../../../src/renderers/DateHeaderRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAkBnD;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAG7B,MAAM,CAAC,cAA2B,EAAE,OAA6B;QAC/D,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,8EAA8E;QAC9E,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACvE,cAAc,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAE5C,+DAA+D;QAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC;QAC9C,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QAE3C,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAClD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE5C,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;gBAChD,MAAc,CAAC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;YACzC,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;YAEhF,MAAM,CAAC,SAAS,GAAG;wBACD,OAAO;wBACP,IAAI,CAAC,OAAO,EAAE;OAC/B,CAAC;YACD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEpE,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}

96
wwwroot/js/renderers/EventRenderer.d.ts vendored Normal file
View file

@ -0,0 +1,96 @@
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configurations/CalendarConfig';
import { PositionUtils } from '../utils/PositionUtils';
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
import { IDragColumnChangeEventPayload, IDragMoveEventPayload, IDragStartEventPayload, IDragMouseEnterColumnEventPayload } from '../types/EventTypes';
import { DateService } from '../utils/DateService';
import { EventStackManager } from '../managers/EventStackManager';
import { EventLayoutCoordinator } from '../managers/EventLayoutCoordinator';
/**
* Interface for event rendering strategies
*/
export interface IEventRenderer {
renderEvents(events: ICalendarEvent[], container: HTMLElement): void;
clearEvents(container?: HTMLElement): void;
renderSingleColumnEvents?(column: IColumnBounds, events: ICalendarEvent[]): void;
handleDragStart?(payload: IDragStartEventPayload): void;
handleDragMove?(payload: IDragMoveEventPayload): void;
handleDragAutoScroll?(eventId: string, snappedY: number): void;
handleDragEnd?(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void;
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
handleColumnChange?(payload: IDragColumnChangeEventPayload): void;
handleNavigationCompleted?(): void;
handleConvertAllDayToTimed?(payload: IDragMouseEnterColumnEventPayload): void;
}
/**
* Date-based event renderer
*/
export declare class DateEventRenderer implements IEventRenderer {
private dateService;
private stackManager;
private layoutCoordinator;
private config;
private positionUtils;
private draggedClone;
private originalEvent;
constructor(dateService: DateService, stackManager: EventStackManager, layoutCoordinator: EventLayoutCoordinator, config: Configuration, positionUtils: PositionUtils);
private applyDragStyling;
/**
* Handle drag start event
*/
handleDragStart(payload: IDragStartEventPayload): void;
/**
* Handle drag move event
*/
handleDragMove(payload: IDragMoveEventPayload): void;
/**
* Handle column change during drag
*/
handleColumnChange(payload: IDragColumnChangeEventPayload): void;
/**
* Handle conversion of all-day event to timed event
*/
handleConvertAllDayToTimed(payload: IDragMouseEnterColumnEventPayload): void;
/**
* Handle drag end event
*/
handleDragEnd(originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void;
/**
* Handle navigation completed event
*/
handleNavigationCompleted(): void;
/**
* Fade out and remove element
*/
private fadeOutAndRemove;
renderEvents(events: ICalendarEvent[], container: HTMLElement): void;
/**
* Render events for a single column
*/
renderSingleColumnEvents(column: IColumnBounds, events: ICalendarEvent[]): void;
/**
* Render events in a column using combined stacking + grid algorithm
*/
private renderColumnEvents;
/**
* Render events in a grid container (side-by-side with column sharing)
*/
private renderGridGroup;
/**
* Render a single column within a grid group
* Column may contain multiple events that don't overlap
*/
private renderGridColumn;
/**
* Render event within a grid container (absolute positioning within column)
*/
private renderEventInGrid;
private renderEvent;
protected calculateEventPosition(event: ICalendarEvent): {
top: number;
height: number;
};
clearEvents(container?: HTMLElement): void;
protected getColumns(container: HTMLElement): HTMLElement[];
protected getEventsForColumn(column: HTMLElement, events: ICalendarEvent[]): ICalendarEvent[];
}

View file

@ -0,0 +1,296 @@
// Event rendering strategy interface and implementations
import { SwpEventElement } from '../elements/SwpEventElement';
/**
* Date-based event renderer
*/
export class DateEventRenderer {
constructor(dateService, stackManager, layoutCoordinator, config, positionUtils) {
this.draggedClone = null;
this.originalEvent = null;
this.dateService = dateService;
this.stackManager = stackManager;
this.layoutCoordinator = layoutCoordinator;
this.config = config;
this.positionUtils = positionUtils;
}
applyDragStyling(element) {
element.classList.add('dragging');
element.style.removeProperty("margin-left");
}
/**
* Handle drag start event
*/
handleDragStart(payload) {
this.originalEvent = payload.originalElement;
;
// Use the clone from the payload instead of creating a new one
this.draggedClone = payload.draggedClone;
if (this.draggedClone && payload.columnBounds) {
// Apply drag styling
this.applyDragStyling(this.draggedClone);
// Add to current column's events layer (not directly to column)
const eventsLayer = payload.columnBounds.element.querySelector('swp-events-layer');
if (eventsLayer) {
eventsLayer.appendChild(this.draggedClone);
// Set initial position to prevent "jump to top" effect
// Calculate absolute Y position from original element
const originalRect = this.originalEvent.getBoundingClientRect();
const columnRect = payload.columnBounds.boundingClientRect;
const initialTop = originalRect.top - columnRect.top;
this.draggedClone.style.top = `${initialTop}px`;
}
}
// Make original semi-transparent
this.originalEvent.style.opacity = '0.3';
this.originalEvent.style.userSelect = 'none';
}
/**
* Handle drag move event
*/
handleDragMove(payload) {
const swpEvent = payload.draggedClone;
const columnDate = this.dateService.parseISO(payload.columnBounds.date);
swpEvent.updatePosition(columnDate, payload.snappedY);
}
/**
* Handle column change during drag
*/
handleColumnChange(payload) {
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
eventsLayer.appendChild(payload.draggedClone);
// Recalculate timestamps with new column date
const currentTop = parseFloat(payload.draggedClone.style.top) || 0;
const swpEvent = payload.draggedClone;
const columnDate = this.dateService.parseISO(payload.newColumn.date);
swpEvent.updatePosition(columnDate, currentTop);
}
}
/**
* Handle conversion of all-day event to timed event
*/
handleConvertAllDayToTimed(payload) {
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
eventId: payload.calendarEvent.id,
targetColumn: payload.targetColumn.date,
snappedY: payload.snappedY
});
let timedClone = SwpEventElement.fromCalendarEvent(payload.calendarEvent);
let position = this.calculateEventPosition(payload.calendarEvent);
// Set position at snapped Y
//timedClone.style.top = `${snappedY}px`;
// Set complete styling for dragged clone (matching normal event rendering)
timedClone.style.height = `${position.height - 3}px`;
timedClone.style.left = '2px';
timedClone.style.right = '2px';
timedClone.style.width = 'auto';
timedClone.style.pointerEvents = 'none';
// Apply drag styling
this.applyDragStyling(timedClone);
// Find the events layer in the target column
let eventsLayer = payload.targetColumn.element.querySelector('swp-events-layer');
// Add "clone-" prefix to match clone ID pattern
//timedClone.dataset.eventId = `clone-${payload.calendarEvent.id}`;
// Remove old all-day clone and replace with new timed clone
payload.draggedClone.remove();
payload.replaceClone(timedClone);
eventsLayer.appendChild(timedClone);
}
/**
* Handle drag end event
*/
handleDragEnd(originalElement, draggedClone, finalColumn, finalY) {
if (!draggedClone || !originalElement) {
console.warn('Missing draggedClone or originalElement');
return;
}
// Only fade out and remove if it's a swp-event (not swp-allday-event)
// AllDayManager handles removal of swp-allday-event elements
if (originalElement.tagName === 'SWP-EVENT') {
this.fadeOutAndRemove(originalElement);
}
// Remove clone prefix and normalize clone to be a regular event
const cloneId = draggedClone.dataset.eventId;
if (cloneId && cloneId.startsWith('clone-')) {
draggedClone.dataset.eventId = cloneId.replace('clone-', '');
}
// Fully normalize the clone to be a regular event
draggedClone.classList.remove('dragging');
draggedClone.style.pointerEvents = ''; // Re-enable pointer events
// Clean up instance state
this.draggedClone = null;
this.originalEvent = null;
// Clean up any remaining day event clones
const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${cloneId}"]`);
if (dayEventClone) {
dayEventClone.remove();
}
}
/**
* Handle navigation completed event
*/
handleNavigationCompleted() {
// Default implementation - can be overridden by subclasses
}
/**
* Fade out and remove element
*/
fadeOutAndRemove(element) {
element.style.transition = 'opacity 0.3s ease-out';
element.style.opacity = '0';
setTimeout(() => {
element.remove();
}, 300);
}
renderEvents(events, container) {
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events.filter(event => !event.allDay);
// Find columns in the specific container for regular events
const columns = this.getColumns(container);
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, timedEvents);
const eventsLayer = column.querySelector('swp-events-layer');
if (eventsLayer) {
this.renderColumnEvents(columnEvents, eventsLayer);
}
});
}
/**
* Render events for a single column
*/
renderSingleColumnEvents(column, events) {
const columnEvents = this.getEventsForColumn(column.element, events);
const eventsLayer = column.element.querySelector('swp-events-layer');
if (eventsLayer) {
this.renderColumnEvents(columnEvents, eventsLayer);
}
}
/**
* Render events in a column using combined stacking + grid algorithm
*/
renderColumnEvents(columnEvents, eventsLayer) {
if (columnEvents.length === 0)
return;
// Get layout from coordinator
const layout = this.layoutCoordinator.calculateColumnLayout(columnEvents);
// Render grid groups
layout.gridGroups.forEach(gridGroup => {
this.renderGridGroup(gridGroup, eventsLayer);
});
// Render stacked events
layout.stackedEvents.forEach(stackedEvent => {
const element = this.renderEvent(stackedEvent.event);
this.stackManager.applyStackLinkToElement(element, stackedEvent.stackLink);
this.stackManager.applyVisualStyling(element, stackedEvent.stackLink.stackLevel);
eventsLayer.appendChild(element);
});
}
/**
* Render events in a grid container (side-by-side with column sharing)
*/
renderGridGroup(gridGroup, eventsLayer) {
const groupElement = document.createElement('swp-event-group');
// Add grid column class based on number of columns (not events)
const colCount = gridGroup.columns.length;
groupElement.classList.add(`cols-${colCount}`);
// Add stack level class for margin-left offset
groupElement.classList.add(`stack-level-${gridGroup.stackLevel}`);
// Position from layout
groupElement.style.top = `${gridGroup.position.top}px`;
// Add stack-link attribute for drag-drop (group acts as a stacked item)
const stackLink = {
stackLevel: gridGroup.stackLevel
};
this.stackManager.applyStackLinkToElement(groupElement, stackLink);
// Apply visual styling (margin-left and z-index) using StackManager
this.stackManager.applyVisualStyling(groupElement, gridGroup.stackLevel);
// Render each column
const earliestEvent = gridGroup.events[0];
gridGroup.columns.forEach((columnEvents) => {
const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start);
groupElement.appendChild(columnContainer);
});
eventsLayer.appendChild(groupElement);
}
/**
* Render a single column within a grid group
* Column may contain multiple events that don't overlap
*/
renderGridColumn(columnEvents, containerStart) {
const columnContainer = document.createElement('div');
columnContainer.style.position = 'relative';
columnEvents.forEach(event => {
const element = this.renderEventInGrid(event, containerStart);
columnContainer.appendChild(element);
});
return columnContainer;
}
/**
* Render event within a grid container (absolute positioning within column)
*/
renderEventInGrid(event, containerStart) {
const element = SwpEventElement.fromCalendarEvent(event);
// Calculate event height
const position = this.calculateEventPosition(event);
// Calculate relative top offset if event starts after container start
// (e.g., if container starts at 07:00 and event starts at 08:15, offset = 75 min)
const timeDiffMs = event.start.getTime() - containerStart.getTime();
const timeDiffMinutes = timeDiffMs / (1000 * 60);
const gridSettings = this.config.gridSettings;
const relativeTop = timeDiffMinutes > 0 ? (timeDiffMinutes / 60) * gridSettings.hourHeight : 0;
// Events in grid columns are positioned absolutely within their column container
element.style.position = 'absolute';
element.style.top = `${relativeTop}px`;
element.style.height = `${position.height - 3}px`;
element.style.left = '0';
element.style.right = '0';
return element;
}
renderEvent(event) {
const element = SwpEventElement.fromCalendarEvent(event);
// Apply positioning (moved from SwpEventElement.applyPositioning)
const position = this.calculateEventPosition(event);
element.style.position = 'absolute';
element.style.top = `${position.top + 1}px`;
element.style.height = `${position.height - 3}px`;
element.style.left = '2px';
element.style.right = '2px';
return element;
}
calculateEventPosition(event) {
// Delegate to PositionUtils for centralized position calculation
return this.positionUtils.calculateEventPosition(event.start, event.end);
}
clearEvents(container) {
const eventSelector = 'swp-event';
const groupSelector = 'swp-event-group';
const existingEvents = container
? container.querySelectorAll(eventSelector)
: document.querySelectorAll(eventSelector);
const existingGroups = container
? container.querySelectorAll(groupSelector)
: document.querySelectorAll(groupSelector);
existingEvents.forEach(event => event.remove());
existingGroups.forEach(group => group.remove());
}
getColumns(container) {
const columns = container.querySelectorAll('swp-day-column');
return Array.from(columns);
}
getEventsForColumn(column, events) {
const columnDate = column.dataset.date;
if (!columnDate) {
return [];
}
// Create start and end of day for interval overlap check
const columnStart = this.dateService.parseISO(`${columnDate}T00:00:00`);
const columnEnd = this.dateService.parseISO(`${columnDate}T23:59:59.999`);
const columnEvents = events.filter(event => {
// Interval overlap: event overlaps with column day if event.start < columnEnd AND event.end > columnStart
const overlaps = event.start < columnEnd && event.end > columnStart;
return overlaps;
});
return columnEvents;
}
}
//# sourceMappingURL=EventRenderer.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,55 @@
import { IEventBus, IRenderContext } from '../types/CalendarTypes';
import { EventManager } from '../managers/EventManager';
import { IEventRenderer } from './EventRenderer';
import { DateService } from '../utils/DateService';
/**
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
* Håndterer event positioning og overlap detection
*/
export declare class EventRenderingService {
private eventBus;
private eventManager;
private strategy;
private dateService;
private dragMouseLeaveHeaderListener;
constructor(eventBus: IEventBus, eventManager: EventManager, strategy: IEventRenderer, dateService: DateService);
/**
* Render events in a specific container for a given period
*/
renderEvents(context: IRenderContext): Promise<void>;
private setupEventListeners;
/**
* Handle GRID_RENDERED event - render events in the current grid
*/
private handleGridRendered;
/**
* Handle VIEW_CHANGED event - clear and re-render for new view
*/
private handleViewChanged;
/**
* Setup all drag event listeners - moved from EventRenderer for better separation of concerns
*/
private setupDragEventListeners;
private setupDragStartListener;
private setupDragMoveListener;
private setupDragEndListener;
private setupDragColumnChangeListener;
private setupDragMouseLeaveHeaderListener;
private setupDragMouseEnterColumnListener;
private setupResizeEndListener;
private setupNavigationCompletedListener;
/**
* Re-render affected columns after drag to recalculate stacking/grouping
*/
private reRenderAffectedColumns;
/**
* Clear events in a single column's events layer
*/
private clearColumnEvents;
/**
* Render events for a single column
*/
private renderSingleColumn;
private clearEvents;
refresh(container?: HTMLElement): void;
}

View file

@ -0,0 +1,264 @@
import { CoreEvents } from '../constants/CoreEvents';
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
/**
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
* Håndterer event positioning og overlap detection
*/
export class EventRenderingService {
constructor(eventBus, eventManager, strategy, dateService) {
this.dragMouseLeaveHeaderListener = null;
this.eventBus = eventBus;
this.eventManager = eventManager;
this.strategy = strategy;
this.dateService = dateService;
this.setupEventListeners();
}
/**
* Render events in a specific container for a given period
*/
async renderEvents(context) {
// Clear existing events in the specific container first
this.strategy.clearEvents(context.container);
// Get events from EventManager for the period
const events = await this.eventManager.getEventsForPeriod(context.startDate, context.endDate);
if (events.length === 0) {
return;
}
// Filter events by type - only render timed events here
const timedEvents = events.filter(event => !event.allDay);
console.log('🎯 EventRenderingService: Event filtering', {
totalEvents: events.length,
timedEvents: timedEvents.length,
allDayEvents: events.length - timedEvents.length
});
// Render timed events using existing strategy
if (timedEvents.length > 0) {
this.strategy.renderEvents(timedEvents, context.container);
}
// Emit EVENTS_RENDERED event for filtering system
this.eventBus.emit(CoreEvents.EVENTS_RENDERED, {
events: events,
container: context.container
});
}
setupEventListeners() {
this.eventBus.on(CoreEvents.GRID_RENDERED, (event) => {
this.handleGridRendered(event);
});
this.eventBus.on(CoreEvents.VIEW_CHANGED, (event) => {
this.handleViewChanged(event);
});
// Handle all drag events and delegate to appropriate renderer
this.setupDragEventListeners();
}
/**
* Handle GRID_RENDERED event - render events in the current grid
*/
handleGridRendered(event) {
const { container, dates } = event.detail;
if (!container || !dates || dates.length === 0) {
return;
}
// Calculate startDate and endDate from dates array
const startDate = dates[0];
const endDate = dates[dates.length - 1];
this.renderEvents({
container,
startDate,
endDate
});
}
/**
* Handle VIEW_CHANGED event - clear and re-render for new view
*/
handleViewChanged(event) {
// Clear all existing events since view structure may have changed
this.clearEvents();
// New rendering will be triggered by subsequent GRID_RENDERED event
}
/**
* Setup all drag event listeners - moved from EventRenderer for better separation of concerns
*/
setupDragEventListeners() {
this.setupDragStartListener();
this.setupDragMoveListener();
this.setupDragEndListener();
this.setupDragColumnChangeListener();
this.setupDragMouseLeaveHeaderListener();
this.setupDragMouseEnterColumnListener();
this.setupResizeEndListener();
this.setupNavigationCompletedListener();
}
setupDragStartListener() {
this.eventBus.on('drag:start', (event) => {
const dragStartPayload = event.detail;
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
return;
}
if (dragStartPayload.originalElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
this.strategy.handleDragStart(dragStartPayload);
}
});
}
setupDragMoveListener() {
this.eventBus.on('drag:move', (event) => {
let dragEvent = event.detail;
if (dragEvent.draggedClone.hasAttribute('data-allday')) {
return;
}
if (this.strategy.handleDragMove) {
this.strategy.handleDragMove(dragEvent);
}
});
}
setupDragEndListener() {
this.eventBus.on('drag:end', async (event) => {
const { originalElement, draggedClone, originalSourceColumn, finalPosition, target } = event.detail;
const finalColumn = finalPosition.column;
const finalY = finalPosition.snappedY;
let element = draggedClone;
// Only handle day column drops for EventRenderer
if (target === 'swp-day-column' && finalColumn) {
if (originalElement && draggedClone && this.strategy.handleDragEnd) {
this.strategy.handleDragEnd(originalElement, draggedClone, finalColumn, finalY);
}
await this.eventManager.updateEvent(element.eventId, {
start: element.start,
end: element.end,
allDay: false
});
// Re-render affected columns for stacking/grouping (now with updated data)
await this.reRenderAffectedColumns(originalSourceColumn, finalColumn);
}
});
}
setupDragColumnChangeListener() {
this.eventBus.on('drag:column-change', (event) => {
let columnChangeEvent = event.detail;
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
return;
}
if (this.strategy.handleColumnChange) {
this.strategy.handleColumnChange(columnChangeEvent);
}
});
}
setupDragMouseLeaveHeaderListener() {
this.dragMouseLeaveHeaderListener = (event) => {
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = event.detail;
if (cloneElement)
cloneElement.style.display = '';
console.log('🚪 EventRendererManager: Received drag:mouseleave-header', {
targetDate,
originalElement: originalElement,
cloneElement: cloneElement
});
};
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
}
setupDragMouseEnterColumnListener() {
this.eventBus.on('drag:mouseenter-column', (event) => {
const payload = event.detail;
// Only handle if clone is an all-day event
if (!payload.draggedClone.hasAttribute('data-allday')) {
return;
}
console.log('🎯 EventRendererManager: Received drag:mouseenter-column', {
targetColumn: payload.targetColumn,
snappedY: payload.snappedY,
calendarEvent: payload.calendarEvent
});
// Delegate to strategy for conversion
if (this.strategy.handleConvertAllDayToTimed) {
this.strategy.handleConvertAllDayToTimed(payload);
}
});
}
setupResizeEndListener() {
this.eventBus.on('resize:end', async (event) => {
const { eventId, element } = event.detail;
// Update event data in EventManager with new end time from resized element
const swpEvent = element;
const newStart = swpEvent.start;
const newEnd = swpEvent.end;
await this.eventManager.updateEvent(eventId, {
start: newStart,
end: newEnd
});
console.log('📝 EventRendererManager: Updated event after resize', {
eventId,
newStart,
newEnd
});
let columnBounds = ColumnDetectionUtils.getColumnBoundsByDate(newStart);
if (columnBounds)
await this.renderSingleColumn(columnBounds);
});
}
setupNavigationCompletedListener() {
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
// Delegate to strategy if it handles navigation
if (this.strategy.handleNavigationCompleted) {
this.strategy.handleNavigationCompleted();
}
});
}
/**
* Re-render affected columns after drag to recalculate stacking/grouping
*/
async reRenderAffectedColumns(originalSourceColumn, targetColumn) {
// Re-render original source column if exists
if (originalSourceColumn) {
await this.renderSingleColumn(originalSourceColumn);
}
// Re-render target column if exists and different from source
if (targetColumn && targetColumn.date !== originalSourceColumn?.date) {
await this.renderSingleColumn(targetColumn);
}
}
/**
* Clear events in a single column's events layer
*/
clearColumnEvents(eventsLayer) {
const existingEvents = eventsLayer.querySelectorAll('swp-event');
const existingGroups = eventsLayer.querySelectorAll('swp-event-group');
existingEvents.forEach(event => event.remove());
existingGroups.forEach(group => group.remove());
}
/**
* Render events for a single column
*/
async renderSingleColumn(column) {
// Get events for just this column's date
const columnStart = this.dateService.parseISO(`${column.date}T00:00:00`);
const columnEnd = this.dateService.parseISO(`${column.date}T23:59:59.999`);
// Get events from EventManager for this single date
const events = await this.eventManager.getEventsForPeriod(columnStart, columnEnd);
// Filter to timed events only
const timedEvents = events.filter(event => !event.allDay);
// Get events layer within this specific column
const eventsLayer = column.element.querySelector('swp-events-layer');
if (!eventsLayer) {
console.warn('EventRendererManager: Events layer not found in column');
return;
}
// Clear only this column's events
this.clearColumnEvents(eventsLayer);
// Render events for this column using strategy
if (this.strategy.renderSingleColumnEvents) {
this.strategy.renderSingleColumnEvents(column, timedEvents);
}
console.log('🔄 EventRendererManager: Re-rendered single column', {
columnDate: column.date,
eventsCount: timedEvents.length
});
}
clearEvents(container) {
this.strategy.clearEvents(container);
}
refresh(container) {
this.clearEvents(container);
}
}
//# sourceMappingURL=EventRendererManager.js.map

File diff suppressed because one or more lines are too long

180
wwwroot/js/renderers/GridRenderer.d.ts vendored Normal file
View file

@ -0,0 +1,180 @@
import { Configuration } from '../configurations/CalendarConfig';
import { CalendarView, ICalendarEvent } from '../types/CalendarTypes';
import { IColumnRenderer } from './ColumnRenderer';
import { DateService } from '../utils/DateService';
import { WorkHoursManager } from '../managers/WorkHoursManager';
/**
* GridRenderer - Centralized DOM rendering for calendar grid structure
*
* ARCHITECTURE OVERVIEW:
* =====================
* GridRenderer is responsible for creating and managing the complete DOM structure
* of the calendar grid. It follows the Strategy Pattern by delegating specific
* rendering tasks to specialized renderers (DateHeaderRenderer, ColumnRenderer).
*
* RESPONSIBILITY HIERARCHY:
* ========================
* GridRenderer (this file)
* Creates overall grid skeleton
* Manages time axis (hour markers)
* Delegates to specialized renderers:
* DateHeaderRenderer Renders date headers
* ColumnRenderer Renders day columns
*
* DOM STRUCTURE CREATED:
* =====================
* <swp-calendar-container>
* <swp-header-spacer /> GridRenderer
* <swp-time-axis> GridRenderer
* <swp-hour-marker>00:00</...> GridRenderer (iterates hours)
* </swp-time-axis>
* <swp-grid-container> GridRenderer
* <swp-calendar-header> GridRenderer creates container
* <swp-day-header /> DateHeaderRenderer (iterates dates)
* </swp-calendar-header>
* <swp-scrollable-content> GridRenderer
* <swp-time-grid> GridRenderer
* <swp-grid-lines /> GridRenderer
* <swp-day-columns> GridRenderer creates container
* <swp-day-column /> ColumnRenderer (iterates dates)
* </swp-day-columns>
* </swp-time-grid>
* </swp-scrollable-content>
* </swp-grid-container>
* </swp-calendar-container>
*
* RENDERING FLOW:
* ==============
* 1. renderGrid() - Entry point called by GridManager
* First render: createCompleteGridStructure()
* Updates: updateGridContent()
*
* 2. createCompleteGridStructure()
* Creates header spacer
* Creates time axis (calls createOptimizedTimeAxis)
* Creates grid container (calls createOptimizedGridContainer)
*
* 3. createOptimizedGridContainer()
* Creates calendar header container
* Creates scrollable content structure
* Creates column container (calls renderColumnContainer)
*
* 4. renderColumnContainer()
* Delegates to ColumnRenderer.render()
* ColumnRenderer iterates dates and creates columns
*
* OPTIMIZATION STRATEGY:
* =====================
* - Caches DOM references (cachedGridContainer, cachedTimeAxis)
* - Uses DocumentFragment for batch DOM insertions
* - Only updates changed content on re-renders
* - Delegates specialized tasks to strategy renderers
*
* USAGE EXAMPLE:
* =============
* const gridRenderer = new GridRenderer(columnRenderer, dateService, config);
* gridRenderer.renderGrid(containerElement, new Date(), 'week');
*/
export declare class GridRenderer {
private cachedGridContainer;
private cachedTimeAxis;
private dateService;
private columnRenderer;
private config;
private workHoursManager;
constructor(columnRenderer: IColumnRenderer, dateService: DateService, config: Configuration, workHoursManager: WorkHoursManager);
/**
* Main entry point for rendering the complete calendar grid
*
* This method decides between full render (first time) or optimized update.
* It caches the grid reference for performance.
*
* @param grid - Container element where grid will be rendered
* @param currentDate - Base date for the current view (e.g., any date in the week)
* @param view - Calendar view type (day/week/month)
* @param dates - Array of dates to render as columns
* @param events - All events for the period
*/
renderGrid(grid: HTMLElement, currentDate: Date, view?: CalendarView, dates?: Date[], events?: ICalendarEvent[]): void;
/**
* Creates the complete grid structure from scratch
*
* Uses DocumentFragment for optimal performance by minimizing reflows.
* Creates all child elements in memory first, then appends everything at once.
*
* Structure created:
* 1. Header spacer (placeholder for alignment)
* 2. Time axis (hour markers 00:00-23:00)
* 3. Grid container (header + scrollable content)
*
* @param grid - Parent container
* @param currentDate - Current view date
* @param view - View type
* @param dates - Array of dates to render
*/
private createCompleteGridStructure;
/**
* Creates the time axis with hour markers
*
* Iterates from dayStartHour to dayEndHour (configured in GridSettings).
* Each marker shows the hour in the configured time format.
*
* @returns Time axis element with all hour markers
*/
private createOptimizedTimeAxis;
/**
* Creates the main grid container with header and columns
*
* This is the scrollable area containing:
* - Calendar header (dates/resources) - created here, populated by DateHeaderRenderer
* - Time grid (grid lines + day columns) - structure created here
* - Column container - created here, populated by ColumnRenderer
*
* @param currentDate - Current view date
* @param view - View type
* @param dates - Array of dates to render
* @returns Complete grid container element
*/
private createOptimizedGridContainer;
/**
* Renders columns by iterating through dates
*
* GridRenderer creates column structure with work hours styling.
* Event rendering is handled by EventRenderingService listening to GRID_RENDERED.
*
* @param columnContainer - Empty container to populate
* @param dates - Array of dates to render
* @param events - All events for the period (passed through, not used here)
*/
private renderColumnContainer;
/**
* Apply work hours styling to a column
*/
private applyWorkHoursStyling;
/**
* Optimized update of grid content without full rebuild
*
* Only updates the column container content, leaving the structure intact.
* This is much faster than recreating the entire grid.
*
* @param grid - Existing grid element
* @param currentDate - New view date
* @param view - View type
* @param dates - Array of dates to render
* @param events - All events for the period
*/
private updateGridContent;
/**
* Creates a new grid for slide animations during navigation
*
* Used by NavigationManager for smooth week-to-week transitions.
* Creates a complete grid positioned absolutely for animation.
*
* Note: Positioning is handled by Animation API, not here.
*
* @param parentContainer - Container for the new grid
* @param weekStart - Start date of the new week
* @returns New grid element ready for animation
*/
createNavigationGrid(parentContainer: HTMLElement, weekStart: Date): HTMLElement;
}

View file

@ -0,0 +1,289 @@
import { TimeFormatter } from '../utils/TimeFormatter';
/**
* GridRenderer - Centralized DOM rendering for calendar grid structure
*
* ARCHITECTURE OVERVIEW:
* =====================
* GridRenderer is responsible for creating and managing the complete DOM structure
* of the calendar grid. It follows the Strategy Pattern by delegating specific
* rendering tasks to specialized renderers (DateHeaderRenderer, ColumnRenderer).
*
* RESPONSIBILITY HIERARCHY:
* ========================
* GridRenderer (this file)
* Creates overall grid skeleton
* Manages time axis (hour markers)
* Delegates to specialized renderers:
* DateHeaderRenderer Renders date headers
* ColumnRenderer Renders day columns
*
* DOM STRUCTURE CREATED:
* =====================
* <swp-calendar-container>
* <swp-header-spacer /> GridRenderer
* <swp-time-axis> GridRenderer
* <swp-hour-marker>00:00</...> GridRenderer (iterates hours)
* </swp-time-axis>
* <swp-grid-container> GridRenderer
* <swp-calendar-header> GridRenderer creates container
* <swp-day-header /> DateHeaderRenderer (iterates dates)
* </swp-calendar-header>
* <swp-scrollable-content> GridRenderer
* <swp-time-grid> GridRenderer
* <swp-grid-lines /> GridRenderer
* <swp-day-columns> GridRenderer creates container
* <swp-day-column /> ColumnRenderer (iterates dates)
* </swp-day-columns>
* </swp-time-grid>
* </swp-scrollable-content>
* </swp-grid-container>
* </swp-calendar-container>
*
* RENDERING FLOW:
* ==============
* 1. renderGrid() - Entry point called by GridManager
* First render: createCompleteGridStructure()
* Updates: updateGridContent()
*
* 2. createCompleteGridStructure()
* Creates header spacer
* Creates time axis (calls createOptimizedTimeAxis)
* Creates grid container (calls createOptimizedGridContainer)
*
* 3. createOptimizedGridContainer()
* Creates calendar header container
* Creates scrollable content structure
* Creates column container (calls renderColumnContainer)
*
* 4. renderColumnContainer()
* Delegates to ColumnRenderer.render()
* ColumnRenderer iterates dates and creates columns
*
* OPTIMIZATION STRATEGY:
* =====================
* - Caches DOM references (cachedGridContainer, cachedTimeAxis)
* - Uses DocumentFragment for batch DOM insertions
* - Only updates changed content on re-renders
* - Delegates specialized tasks to strategy renderers
*
* USAGE EXAMPLE:
* =============
* const gridRenderer = new GridRenderer(columnRenderer, dateService, config);
* gridRenderer.renderGrid(containerElement, new Date(), 'week');
*/
export class GridRenderer {
constructor(columnRenderer, dateService, config, workHoursManager) {
this.cachedGridContainer = null;
this.cachedTimeAxis = null;
this.dateService = dateService;
this.columnRenderer = columnRenderer;
this.config = config;
this.workHoursManager = workHoursManager;
}
/**
* Main entry point for rendering the complete calendar grid
*
* This method decides between full render (first time) or optimized update.
* It caches the grid reference for performance.
*
* @param grid - Container element where grid will be rendered
* @param currentDate - Base date for the current view (e.g., any date in the week)
* @param view - Calendar view type (day/week/month)
* @param dates - Array of dates to render as columns
* @param events - All events for the period
*/
renderGrid(grid, currentDate, view = 'week', dates = [], events = []) {
if (!grid || !currentDate) {
return;
}
// Cache grid reference for performance
this.cachedGridContainer = grid;
// Only clear and rebuild if grid is empty (first render)
if (grid.children.length === 0) {
this.createCompleteGridStructure(grid, currentDate, view, dates, events);
}
else {
// Optimized update - only refresh dynamic content
this.updateGridContent(grid, currentDate, view, dates, events);
}
}
/**
* Creates the complete grid structure from scratch
*
* Uses DocumentFragment for optimal performance by minimizing reflows.
* Creates all child elements in memory first, then appends everything at once.
*
* Structure created:
* 1. Header spacer (placeholder for alignment)
* 2. Time axis (hour markers 00:00-23:00)
* 3. Grid container (header + scrollable content)
*
* @param grid - Parent container
* @param currentDate - Current view date
* @param view - View type
* @param dates - Array of dates to render
*/
createCompleteGridStructure(grid, currentDate, view, dates, events) {
// Create all elements in memory first for better performance
const fragment = document.createDocumentFragment();
// Create header spacer
const headerSpacer = document.createElement('swp-header-spacer');
fragment.appendChild(headerSpacer);
// Create time axis with caching
const timeAxis = this.createOptimizedTimeAxis();
this.cachedTimeAxis = timeAxis;
fragment.appendChild(timeAxis);
// Create grid container with caching
const gridContainer = this.createOptimizedGridContainer(currentDate, view, dates, events);
this.cachedGridContainer = gridContainer;
fragment.appendChild(gridContainer);
// Append all at once to minimize reflows
grid.appendChild(fragment);
}
/**
* Creates the time axis with hour markers
*
* Iterates from dayStartHour to dayEndHour (configured in GridSettings).
* Each marker shows the hour in the configured time format.
*
* @returns Time axis element with all hour markers
*/
createOptimizedTimeAxis() {
const timeAxis = document.createElement('swp-time-axis');
const timeAxisContent = document.createElement('swp-time-axis-content');
const gridSettings = this.config.gridSettings;
const startHour = gridSettings.dayStartHour;
const endHour = gridSettings.dayEndHour;
const fragment = document.createDocumentFragment();
for (let hour = startHour; hour < endHour; hour++) {
const marker = document.createElement('swp-hour-marker');
const date = new Date(2024, 0, 1, hour, 0);
marker.textContent = TimeFormatter.formatTime(date);
fragment.appendChild(marker);
}
timeAxisContent.appendChild(fragment);
timeAxisContent.style.top = '-1px';
timeAxis.appendChild(timeAxisContent);
return timeAxis;
}
/**
* Creates the main grid container with header and columns
*
* This is the scrollable area containing:
* - Calendar header (dates/resources) - created here, populated by DateHeaderRenderer
* - Time grid (grid lines + day columns) - structure created here
* - Column container - created here, populated by ColumnRenderer
*
* @param currentDate - Current view date
* @param view - View type
* @param dates - Array of dates to render
* @returns Complete grid container element
*/
createOptimizedGridContainer(dates, events) {
const gridContainer = document.createElement('swp-grid-container');
// Create calendar header as first child - always exists now!
const calendarHeader = document.createElement('swp-calendar-header');
gridContainer.appendChild(calendarHeader);
// Create scrollable content structure
const scrollableContent = document.createElement('swp-scrollable-content');
const timeGrid = document.createElement('swp-time-grid');
// Add grid lines
const gridLines = document.createElement('swp-grid-lines');
timeGrid.appendChild(gridLines);
// Create column container
const columnContainer = document.createElement('swp-day-columns');
this.renderColumnContainer(columnContainer, dates, events);
timeGrid.appendChild(columnContainer);
scrollableContent.appendChild(timeGrid);
gridContainer.appendChild(scrollableContent);
return gridContainer;
}
/**
* Renders columns by iterating through dates
*
* GridRenderer creates column structure with work hours styling.
* Event rendering is handled by EventRenderingService listening to GRID_RENDERED.
*
* @param columnContainer - Empty container to populate
* @param dates - Array of dates to render
* @param events - All events for the period (passed through, not used here)
*/
renderColumnContainer(columnContainer, dates, events) {
// Iterate through dates and render each column structure
dates.forEach(date => {
// Create column with data-date attribute
const column = document.createElement('swp-day-column');
column.dataset.date = this.dateService.formatISODate(date);
// Apply work hours styling
this.applyWorkHoursStyling(column, date);
// Add events layer (events will be rendered by EventRenderingService)
const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer);
columnContainer.appendChild(column);
});
}
/**
* Apply work hours styling to a column
*/
applyWorkHoursStyling(column, date) {
const workHours = this.workHoursManager.getWorkHoursForDate(date);
if (workHours === 'off') {
column.setAttribute('data-day-off', 'true');
}
else {
column.removeAttribute('data-day-off');
// Calculate non-work hours overlay positions
const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours);
if (nonWorkStyle) {
column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`);
column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`);
}
}
}
/**
* Optimized update of grid content without full rebuild
*
* Only updates the column container content, leaving the structure intact.
* This is much faster than recreating the entire grid.
*
* @param grid - Existing grid element
* @param currentDate - New view date
* @param view - View type
* @param dates - Array of dates to render
* @param events - All events for the period
*/
updateGridContent(grid, currentDate, view, dates, events) {
// Update column container if needed
const columnContainer = grid.querySelector('swp-day-columns');
if (columnContainer) {
columnContainer.innerHTML = '';
this.renderColumnContainer(columnContainer, dates, events);
}
}
/**
* Creates a new grid for slide animations during navigation
*
* Used by NavigationManager for smooth week-to-week transitions.
* Creates a complete grid positioned absolutely for animation.
*
* Note: Positioning is handled by Animation API, not here.
*
* @param parentContainer - Container for the new grid
* @param weekStart - Start date of the new week
* @returns New grid element ready for animation
*/
createNavigationGrid(parentContainer, weekStart) {
// Use SAME method as initial load - respects workweek settings
const newGrid = this.createOptimizedGridContainer(weekStart, 'week');
// Position new grid for animation - NO transform here, let Animation API handle it
newGrid.style.position = 'absolute';
newGrid.style.top = '0';
newGrid.style.left = '0';
newGrid.style.width = '100%';
newGrid.style.height = '100%';
// Add to parent container
parentContainer.appendChild(newGrid);
return newGrid;
}
}
//# sourceMappingURL=GridRenderer.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,24 @@
import { ResourceCalendarData } from '../types/CalendarTypes';
/**
* GridStyleManager - Manages CSS variables and styling for the grid
* Separated from GridManager to follow Single Responsibility Principle
*/
export declare class GridStyleManager {
constructor();
/**
* Update all grid CSS variables
*/
updateGridStyles(resourceData?: ResourceCalendarData | null): void;
/**
* Set time-related CSS variables
*/
private setTimeVariables;
/**
* Calculate number of columns based on calendar type and view
*/
private calculateColumnCount;
/**
* Set column width based on fitToWidth setting
*/
private setColumnWidth;
}

View file

@ -0,0 +1,76 @@
import { calendarConfig } from '../core/CalendarConfig';
/**
* GridStyleManager - Manages CSS variables and styling for the grid
* Separated from GridManager to follow Single Responsibility Principle
*/
export class GridStyleManager {
constructor() {
}
/**
* Update all grid CSS variables
*/
updateGridStyles(resourceData = null) {
const root = document.documentElement;
const gridSettings = calendarConfig.getGridSettings();
const calendar = document.querySelector('swp-calendar');
const calendarType = calendarConfig.getCalendarMode();
// Set CSS variables for time and grid measurements
this.setTimeVariables(root, gridSettings);
// Set column count based on calendar type
const columnCount = this.calculateColumnCount(calendarType, resourceData);
root.style.setProperty('--grid-columns', columnCount.toString());
// Set column width based on fitToWidth setting
this.setColumnWidth(root, gridSettings);
// Set fitToWidth data attribute for CSS targeting
if (calendar) {
calendar.setAttribute('data-fit-to-width', gridSettings.fitToWidth.toString());
}
}
/**
* Set time-related CSS variables
*/
setTimeVariables(root, gridSettings) {
root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`);
root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString());
root.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
root.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
root.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
root.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
}
/**
* Calculate number of columns based on calendar type and view
*/
calculateColumnCount(calendarType, resourceData) {
if (calendarType === 'resource' && resourceData) {
return resourceData.resources.length;
}
else if (calendarType === 'date') {
const dateSettings = calendarConfig.getDateViewSettings();
const workWeekSettings = calendarConfig.getWorkWeekSettings();
switch (dateSettings.period) {
case 'day':
return 1;
case 'week':
return workWeekSettings.totalDays;
case 'month':
return workWeekSettings.totalDays; // Use work week for month view too
default:
return workWeekSettings.totalDays;
}
}
return calendarConfig.getWorkWeekSettings().totalDays; // Default to work week
}
/**
* Set column width based on fitToWidth setting
*/
setColumnWidth(root, gridSettings) {
if (gridSettings.fitToWidth) {
root.style.setProperty('--day-column-min-width', '50px'); // Small min-width allows columns to fit available space
}
else {
root.style.setProperty('--day-column-min-width', '250px'); // Default min-width for horizontal scroll mode
}
}
}
//# sourceMappingURL=GridStyleManager.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"GridStyleManager.js","sourceRoot":"","sources":["../../../src/renderers/GridStyleManager.ts"],"names":[],"mappings":"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"}

View file

@ -0,0 +1,29 @@
import { CalendarConfig } from '../core/CalendarConfig';
import { ResourceCalendarData } from '../types/CalendarTypes';
/**
* Interface for header rendering strategies
*/
export interface HeaderRenderer {
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
}
/**
* Context for header rendering
*/
export interface HeaderRenderContext {
currentWeek: Date;
config: CalendarConfig;
resourceData?: ResourceCalendarData | null;
}
/**
* Date-based header renderer (original functionality)
*/
export declare class DateHeaderRenderer implements HeaderRenderer {
private dateService;
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
}
/**
* Resource-based header renderer
*/
export declare class ResourceHeaderRenderer implements HeaderRenderer {
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
}

View file

@ -0,0 +1,56 @@
// Header rendering strategy interface and implementations
import { DateCalculator } from '../utils/DateCalculator';
/**
* Date-based header renderer (original functionality)
*/
export class DateHeaderRenderer {
render(calendarHeader, context) {
const { currentWeek, config } = context;
// FIRST: Always create all-day container as part of standard header structure
const allDayContainer = document.createElement('swp-allday-container');
calendarHeader.appendChild(allDayContainer);
// Initialize date calculator with config
DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
const dates = DateCalculator.getWorkWeekDates(currentWeek);
const weekDays = config.getDateViewSettings().weekDays;
const daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => {
const header = document.createElement('swp-day-header');
if (DateCalculator.isToday(date)) {
header.dataset.today = 'true';
}
const dayName = DateCalculator.getDayName(date, 'short');
header.innerHTML = `
<swp-day-name>${dayName}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date>
`;
header.dataset.date = DateCalculator.formatISODate(date);
calendarHeader.appendChild(header);
});
}
}
/**
* Resource-based header renderer
*/
export class ResourceHeaderRenderer {
render(calendarHeader, context) {
const { resourceData } = context;
if (!resourceData) {
return;
}
resourceData.resources.forEach((resource) => {
const header = document.createElement('swp-resource-header');
header.setAttribute('data-resource', resource.name);
header.setAttribute('data-employee-id', resource.employeeId);
header.innerHTML = `
<swp-resource-avatar>
<img src="${resource.avatarUrl}" alt="${resource.displayName}" onerror="this.style.display='none'">
</swp-resource-avatar>
<swp-resource-name>${resource.displayName}</swp-resource-name>
`;
calendarHeader.appendChild(header);
});
}
}
//# sourceMappingURL=HeaderRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"HeaderRenderer.js","sourceRoot":"","sources":["../../../src/renderers/HeaderRenderer.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAI1D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAmBzD;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAG7B,MAAM,CAAC,cAA2B,EAAE,OAA4B;QAC9D,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAExC,8EAA8E;QAC9E,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACvE,cAAc,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAE5C,yCAAyC;QACzC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;QAE3C,MAAM,KAAK,GAAG,cAAc,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC,QAAQ,CAAC;QACvD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE5C,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,MAAc,CAAC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;YACzC,CAAC;YAED,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAEzD,MAAM,CAAC,SAAS,GAAG;wBACD,OAAO;wBACP,IAAI,CAAC,OAAO,EAAE;OAC/B,CAAC;YACD,MAAc,CAAC,OAAO,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAElE,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACjC,MAAM,CAAC,cAA2B,EAAE,OAA4B;QAC9D,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;QAEjC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;YAC7D,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,CAAC,YAAY,CAAC,kBAAkB,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YAE7D,MAAM,CAAC,SAAS,GAAG;;sBAEH,QAAQ,CAAC,SAAS,UAAU,QAAQ,CAAC,WAAW;;6BAEzC,QAAQ,CAAC,WAAW;OAC1C,CAAC;YAEF,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}

View file

@ -0,0 +1,22 @@
import { IEventBus } from '../types/CalendarTypes';
import { EventRenderingService } from './EventRendererManager';
/**
* NavigationRenderer - Handles DOM rendering for navigation containers
* Separated from NavigationManager to follow Single Responsibility Principle
*/
export declare class NavigationRenderer {
private eventBus;
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService);
/**
* Setup event listeners for DOM updates
*/
private setupEventListeners;
private updateWeekInfoInDOM;
/**
* Apply filter state to pre-rendered grids
*/
applyFilterToPreRenderedGrids(filterState: {
active: boolean;
matchingIds: string[];
}): void;
}

View file

@ -0,0 +1,68 @@
import { CoreEvents } from '../constants/CoreEvents';
/**
* NavigationRenderer - Handles DOM rendering for navigation containers
* Separated from NavigationManager to follow Single Responsibility Principle
*/
export class NavigationRenderer {
constructor(eventBus, eventRenderer) {
this.eventBus = eventBus;
this.setupEventListeners();
}
/**
* Setup event listeners for DOM updates
*/
setupEventListeners() {
this.eventBus.on(CoreEvents.PERIOD_INFO_UPDATE, (event) => {
const customEvent = event;
const { weekNumber, dateRange } = customEvent.detail;
this.updateWeekInfoInDOM(weekNumber, dateRange);
});
}
updateWeekInfoInDOM(weekNumber, dateRange) {
const weekNumberElement = document.querySelector('swp-week-number');
const dateRangeElement = document.querySelector('swp-date-range');
if (weekNumberElement) {
weekNumberElement.textContent = `Week ${weekNumber}`;
}
if (dateRangeElement) {
dateRangeElement.textContent = dateRange;
}
}
/**
* Apply filter state to pre-rendered grids
*/
applyFilterToPreRenderedGrids(filterState) {
// Find all grid containers (including pre-rendered ones)
const allGridContainers = document.querySelectorAll('swp-grid-container');
allGridContainers.forEach(container => {
const eventsLayers = container.querySelectorAll('swp-events-layer');
eventsLayers.forEach(layer => {
if (filterState.active) {
// Apply filter active state
layer.setAttribute('data-filter-active', 'true');
// Mark matching events in this layer
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
const eventId = event.getAttribute('data-event-id');
if (eventId && filterState.matchingIds.includes(eventId)) {
event.setAttribute('data-matches', 'true');
}
else {
event.removeAttribute('data-matches');
}
});
}
else {
// Remove filter state
layer.removeAttribute('data-filter-active');
// Remove all match attributes
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
event.removeAttribute('data-matches');
});
}
});
});
}
}
//# sourceMappingURL=NavigationRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"NavigationRenderer.js","sourceRoot":"","sources":["../../../src/renderers/NavigationRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD;;;GAGG;AAEH,MAAM,OAAO,kBAAkB;IAG7B,YAAY,QAAmB,EAAE,aAAoC;QACnE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAID;;OAEG;IACK,mBAAmB;QACzB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,KAAY,EAAE,EAAE;YAC/D,MAAM,WAAW,GAAG,KAAoB,CAAC;YACzC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC;YACrD,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAGO,mBAAmB,CAAC,UAAkB,EAAE,SAAiB;QAE/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;QACpE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAElE,IAAI,iBAAiB,EAAE,CAAC;YACtB,iBAAiB,CAAC,WAAW,GAAG,QAAQ,UAAU,EAAE,CAAC;QACvD,CAAC;QAED,IAAI,gBAAgB,EAAE,CAAC;YACrB,gBAAgB,CAAC,WAAW,GAAG,SAAS,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;OAEG;IACI,6BAA6B,CAAC,WAAuD;QAC1F,yDAAyD;QACzD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;QAE1E,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YACpC,MAAM,YAAY,GAAG,SAAS,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;YAEpE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAC3B,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;oBACvB,4BAA4B;oBAC5B,KAAK,CAAC,YAAY,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;oBAEjD,qCAAqC;oBACrC,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;oBACnD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;wBACrB,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;wBACpD,IAAI,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;4BACzD,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;wBAC7C,CAAC;6BAAM,CAAC;4BACN,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,sBAAsB;oBACtB,KAAK,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;oBAE5C,8BAA8B;oBAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;oBACnD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;wBACrB,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;oBACxC,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CAEF"}

View file

@ -0,0 +1,26 @@
import { IEventBus } from '../types/CalendarTypes';
import { EventRenderingService } from './EventRendererManager';
import { DateService } from '../utils/DateService';
/**
* WeekInfoRenderer - Handles DOM rendering for week info display
* Updates swp-week-number and swp-date-range elements
*
* Renamed from NavigationRenderer to better reflect its actual responsibility
*/
export declare class WeekInfoRenderer {
private eventBus;
private dateService;
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService, dateService: DateService);
/**
* Setup event listeners for DOM updates
*/
private setupEventListeners;
private updateWeekInfoInDOM;
/**
* Apply filter state to pre-rendered grids
*/
applyFilterToPreRenderedGrids(filterState: {
active: boolean;
matchingIds: string[];
}): void;
}

View file

@ -0,0 +1,75 @@
import { CoreEvents } from '../constants/CoreEvents';
/**
* WeekInfoRenderer - Handles DOM rendering for week info display
* Updates swp-week-number and swp-date-range elements
*
* Renamed from NavigationRenderer to better reflect its actual responsibility
*/
export class WeekInfoRenderer {
constructor(eventBus, eventRenderer, dateService) {
this.eventBus = eventBus;
this.dateService = dateService;
this.setupEventListeners();
}
/**
* Setup event listeners for DOM updates
*/
setupEventListeners() {
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (event) => {
const customEvent = event;
const { newDate } = customEvent.detail;
// Calculate week number and date range from the new date
const weekNumber = this.dateService.getWeekNumber(newDate);
const weekEnd = this.dateService.addDays(newDate, 6);
const dateRange = this.dateService.formatDateRange(newDate, weekEnd);
this.updateWeekInfoInDOM(weekNumber, dateRange);
});
}
updateWeekInfoInDOM(weekNumber, dateRange) {
const weekNumberElement = document.querySelector('swp-week-number');
const dateRangeElement = document.querySelector('swp-date-range');
if (weekNumberElement) {
weekNumberElement.textContent = `Week ${weekNumber}`;
}
if (dateRangeElement) {
dateRangeElement.textContent = dateRange;
}
}
/**
* Apply filter state to pre-rendered grids
*/
applyFilterToPreRenderedGrids(filterState) {
// Find all grid containers (including pre-rendered ones)
const allGridContainers = document.querySelectorAll('swp-grid-container');
allGridContainers.forEach(container => {
const eventsLayers = container.querySelectorAll('swp-events-layer');
eventsLayers.forEach(layer => {
if (filterState.active) {
// Apply filter active state
layer.setAttribute('data-filter-active', 'true');
// Mark matching events in this layer
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
const eventId = event.getAttribute('data-event-id');
if (eventId && filterState.matchingIds.includes(eventId)) {
event.setAttribute('data-matches', 'true');
}
else {
event.removeAttribute('data-matches');
}
});
}
else {
// Remove filter state
layer.removeAttribute('data-filter-active');
// Remove all match attributes
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
event.removeAttribute('data-matches');
});
}
});
});
}
}
//# sourceMappingURL=WeekInfoRenderer.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"WeekInfoRenderer.js","sourceRoot":"","sources":["../../../src/renderers/WeekInfoRenderer.ts"],"names":[],"mappings":"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"}