Adds I-prefix to all interfaces

This commit is contained in:
Janus C. H. Knudsen 2025-11-03 21:30:50 +01:00
parent 80aaab46f2
commit 8ec5f52872
44 changed files with 1731 additions and 1949 deletions

View file

@ -10,7 +10,9 @@
"Bash(rm:*)",
"Bash(npm install:*)",
"Bash(npm test)",
"Bash(cat:*)"
"Bash(cat:*)",
"Bash(npm run test:run:*)",
"Bash(npx tsc)"
],
"deny": []
}

View file

@ -0,0 +1,179 @@
import { ICalendarConfig } from './ICalendarConfig';
import { IGridSettings } from './GridSettings';
import { IDateViewSettings } from './DateViewSettings';
import { ITimeFormatConfig } from './TimeFormatConfig';
import { IWorkWeekSettings } from './WorkWeekSettings';
/**
* All-day event layout constants
*/
export const ALL_DAY_CONSTANTS = {
EVENT_HEIGHT: 22,
EVENT_GAP: 2,
CONTAINER_PADDING: 4,
MAX_COLLAPSED_ROWS: 4,
get SINGLE_ROW_HEIGHT() {
return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px
}
} as const;
/**
* Work week presets
*/
export const WORK_WEEK_PRESETS: { [key: string]: IWorkWeekSettings } = {
'standard': {
id: 'standard',
workDays: [1, 2, 3, 4, 5],
totalDays: 5,
firstWorkDay: 1
},
'compressed': {
id: 'compressed',
workDays: [1, 2, 3, 4],
totalDays: 4,
firstWorkDay: 1
},
'midweek': {
id: 'midweek',
workDays: [3, 4, 5],
totalDays: 3,
firstWorkDay: 3
},
'weekend': {
id: 'weekend',
workDays: [6, 7],
totalDays: 2,
firstWorkDay: 6
},
'fullweek': {
id: 'fullweek',
workDays: [1, 2, 3, 4, 5, 6, 7],
totalDays: 7,
firstWorkDay: 1
}
};
/**
* Configuration - DTO container for all configuration
* Pure data object loaded from JSON via ConfigManager
*/
export class Configuration {
private static _instance: Configuration | null = null;
public config: ICalendarConfig;
public gridSettings: IGridSettings;
public dateViewSettings: IDateViewSettings;
public timeFormatConfig: ITimeFormatConfig;
public currentWorkWeek: string;
public selectedDate: Date;
constructor(
config: ICalendarConfig,
gridSettings: IGridSettings,
dateViewSettings: IDateViewSettings,
timeFormatConfig: ITimeFormatConfig,
currentWorkWeek: string,
selectedDate: Date = new Date()
) {
this.config = config;
this.gridSettings = gridSettings;
this.dateViewSettings = dateViewSettings;
this.timeFormatConfig = timeFormatConfig;
this.currentWorkWeek = currentWorkWeek;
this.selectedDate = selectedDate;
// Store as singleton instance for web components
Configuration._instance = this;
}
/**
* Get the current Configuration instance
* Used by web components that can't use dependency injection
*/
public static getInstance(): Configuration {
if (!Configuration._instance) {
throw new Error('Configuration has not been initialized. Call ConfigManager.load() first.');
}
return Configuration._instance;
}
// Computed properties
get minuteHeight(): number {
return this.gridSettings.hourHeight / 60;
}
get totalHours(): number {
return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour;
}
get totalMinutes(): number {
return this.totalHours * 60;
}
get slotsPerHour(): number {
return 60 / this.gridSettings.snapInterval;
}
get totalSlots(): number {
return this.totalHours * this.slotsPerHour;
}
get slotHeight(): number {
return this.gridSettings.hourHeight / this.slotsPerHour;
}
// Backward compatibility getters
getGridSettings(): IGridSettings {
return this.gridSettings;
}
getDateViewSettings(): IDateViewSettings {
return this.dateViewSettings;
}
getWorkWeekSettings(): IWorkWeekSettings {
return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard'];
}
getCurrentWorkWeek(): string {
return this.currentWorkWeek;
}
getTimezone(): string {
return this.timeFormatConfig.timezone;
}
getLocale(): string {
return this.timeFormatConfig.locale;
}
getTimeFormatSettings(): ITimeFormatConfig {
return this.timeFormatConfig;
}
is24HourFormat(): boolean {
return this.timeFormatConfig.use24HourFormat;
}
getDateFormat(): 'locale' | 'technical' {
return this.timeFormatConfig.dateFormat;
}
setWorkWeek(workWeekId: string): void {
if (WORK_WEEK_PRESETS[workWeekId]) {
this.currentWorkWeek = workWeekId;
this.dateViewSettings.weekDays = WORK_WEEK_PRESETS[workWeekId].totalDays;
}
}
setSelectedDate(date: Date): void {
this.selectedDate = date;
}
isValidSnapInterval(interval: number): boolean {
return [5, 10, 15, 30, 60].includes(interval);
}
}
// Backward compatibility alias
export { Configuration as CalendarConfig };

View file

@ -0,0 +1,55 @@
import { Configuration } from './CalendarConfig';
import { ICalendarConfig } from './ICalendarConfig';
import { TimeFormatter } from '../utils/TimeFormatter';
/**
* ConfigManager - Static configuration loader
* Loads JSON and creates Configuration instance
*/
export class ConfigManager {
/**
* Load configuration from JSON and create Configuration instance
*/
static async load(): Promise<Configuration> {
const response = await fetch('/wwwroot/data/calendar-config.json');
if (!response.ok) {
throw new Error(`Failed to load config: ${response.statusText}`);
}
const data = await response.json();
// Build main config
const mainConfig: ICalendarConfig = {
scrollbarWidth: data.scrollbar.width,
scrollbarColor: data.scrollbar.color,
scrollbarTrackColor: data.scrollbar.trackColor,
scrollbarHoverColor: data.scrollbar.hoverColor,
scrollbarBorderRadius: data.scrollbar.borderRadius,
allowDrag: data.interaction.allowDrag,
allowResize: data.interaction.allowResize,
allowCreate: data.interaction.allowCreate,
apiEndpoint: data.api.endpoint,
dateFormat: data.api.dateFormat,
timeFormat: data.api.timeFormat,
enableSearch: data.features.enableSearch,
enableTouch: data.features.enableTouch,
defaultEventDuration: data.eventDefaults.defaultEventDuration,
minEventDuration: data.gridSettings.snapInterval,
maxEventDuration: data.eventDefaults.maxEventDuration
};
// Create Configuration instance
const config = new Configuration(
mainConfig,
data.gridSettings,
data.dateViewSettings,
data.timeFormatConfig,
data.currentWorkWeek
);
// Configure TimeFormatter
TimeFormatter.configure(config.timeFormatConfig);
return config;
}
}

View file

@ -0,0 +1,11 @@
import { ViewPeriod } from '../types/CalendarTypes';
/**
* View settings for date-based calendar mode
*/
export interface IDateViewSettings {
period: ViewPeriod;
weekDays: number;
firstDayOfWeek: number;
showAllDay: boolean;
}

View file

@ -0,0 +1,16 @@
/**
* Grid display settings interface
*/
export interface IGridSettings {
dayStartHour: number;
dayEndHour: number;
workStartHour: number;
workEndHour: number;
hourHeight: number;
snapInterval: number;
fitToWidth: boolean;
scrollToHour: number | null;
gridStartThresholdMinutes: number;
showCurrentTime: boolean;
showWorkHours: boolean;
}

View file

@ -0,0 +1,30 @@
/**
* Main calendar configuration interface
*/
export interface ICalendarConfig {
// Scrollbar styling
scrollbarWidth: number;
scrollbarColor: string;
scrollbarTrackColor: string;
scrollbarHoverColor: string;
scrollbarBorderRadius: number;
// Interaction settings
allowDrag: boolean;
allowResize: boolean;
allowCreate: boolean;
// API settings
apiEndpoint: string;
dateFormat: string;
timeFormat: string;
// Feature flags
enableSearch: boolean;
enableTouch: boolean;
// Event defaults
defaultEventDuration: number;
minEventDuration: number;
maxEventDuration: number;
}

View file

@ -0,0 +1,10 @@
/**
* Time format configuration settings
*/
export interface ITimeFormatConfig {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}

View file

@ -0,0 +1,9 @@
/**
* Work week configuration settings
*/
export interface IWorkWeekSettings {
id: string;
workDays: number[];
totalDays: number;
firstWorkDay: number;
}

View file

@ -1,436 +0,0 @@
// Calendar configuration management
// Pure static configuration class - no dependencies, no events
import { ICalendarConfig, ViewPeriod } from '../types/CalendarTypes';
import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter';
/**
* All-day event layout constants
*/
export const ALL_DAY_CONSTANTS = {
EVENT_HEIGHT: 22, // Height of single all-day event
EVENT_GAP: 2, // Gap between stacked events
CONTAINER_PADDING: 4, // Container padding (top + bottom)
MAX_COLLAPSED_ROWS: 4, // Show 4 rows when collapsed (3 events + 1 indicator row)
get SINGLE_ROW_HEIGHT() {
return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px
}
} as const;
/**
* Layout and timing settings for the calendar grid
*/
interface GridSettings {
// Time boundaries
dayStartHour: number;
dayEndHour: number;
workStartHour: number;
workEndHour: number;
// Layout settings
hourHeight: number;
snapInterval: number;
fitToWidth: boolean;
scrollToHour: number | null;
// Event grouping settings
gridStartThresholdMinutes: number; // ±N minutes for events to share grid columns
// Display options
showCurrentTime: boolean;
showWorkHours: boolean;
}
/**
* View settings for date-based calendar mode
*/
interface DateViewSettings {
period: ViewPeriod; // day/week/month
weekDays: number; // Number of days to show in week view
firstDayOfWeek: number; // 0=Sunday, 1=Monday
showAllDay: boolean; // Show all-day event row
}
/**
* Work week configuration settings
*/
interface WorkWeekSettings {
id: string;
workDays: number[]; // ISO 8601: [1,2,3,4,5] for mon-fri (1=Mon, 7=Sun)
totalDays: number; // 5
firstWorkDay: number; // ISO: 1 = Monday, 7 = Sunday
}
/**
* Time format configuration settings
*/
interface TimeFormatConfig {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}
/**
* Calendar configuration management - Pure static config
*/
export class CalendarConfig {
private static config: ICalendarConfig = {
// Scrollbar styling
scrollbarWidth: 16, // Width of scrollbar in pixels
scrollbarColor: '#666', // Scrollbar thumb color
scrollbarTrackColor: '#f0f0f0', // Scrollbar track color
scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color
scrollbarBorderRadius: 6, // Border radius for scrollbar thumb
// Interaction settings
allowDrag: true,
allowResize: true,
allowCreate: true,
// API settings
apiEndpoint: '/api/events',
dateFormat: 'YYYY-MM-DD',
timeFormat: 'HH:mm',
// Feature flags
enableSearch: true,
enableTouch: true,
// Event defaults
defaultEventDuration: 60, // Minutes
minEventDuration: 15, // Will be same as snapInterval
maxEventDuration: 480 // 8 hours
};
private static selectedDate: Date | null = new Date();
private static currentWorkWeek: string = 'standard';
// Grid display settings
private static gridSettings: GridSettings = {
hourHeight: 60,
dayStartHour: 0,
dayEndHour: 24,
workStartHour: 8,
workEndHour: 17,
snapInterval: 15,
gridStartThresholdMinutes: 30, // Events starting within ±15 min share grid columns
showCurrentTime: true,
showWorkHours: true,
fitToWidth: false,
scrollToHour: 8
};
// Date view settings
private static dateViewSettings: DateViewSettings = {
period: 'week',
weekDays: 7,
firstDayOfWeek: 1,
showAllDay: true
};
// Time format settings - default to Denmark with technical format
private static timeFormatConfig: TimeFormatConfig = {
timezone: 'Europe/Copenhagen',
use24HourFormat: true,
locale: 'da-DK',
dateFormat: 'technical',
showSeconds: false
};
/**
* Initialize configuration - called once at startup
*/
static initialize(): void {
// Set computed values
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
// Initialize TimeFormatter with default settings
TimeFormatter.configure(CalendarConfig.timeFormatConfig);
// Load from data attributes
CalendarConfig.loadFromDOM();
}
/**
* Load configuration from DOM data attributes
*/
private static loadFromDOM(): void {
const calendar = document.querySelector('swp-calendar') as HTMLElement;
if (!calendar) return;
// Read data attributes
const attrs = calendar.dataset;
// Update date view settings
if (attrs.view) CalendarConfig.dateViewSettings.period = attrs.view as ViewPeriod;
if (attrs.weekDays) CalendarConfig.dateViewSettings.weekDays = parseInt(attrs.weekDays);
// Update grid settings
if (attrs.snapInterval) CalendarConfig.gridSettings.snapInterval = parseInt(attrs.snapInterval);
if (attrs.dayStartHour) CalendarConfig.gridSettings.dayStartHour = parseInt(attrs.dayStartHour);
if (attrs.dayEndHour) CalendarConfig.gridSettings.dayEndHour = parseInt(attrs.dayEndHour);
if (attrs.hourHeight) CalendarConfig.gridSettings.hourHeight = parseInt(attrs.hourHeight);
if (attrs.fitToWidth !== undefined) CalendarConfig.gridSettings.fitToWidth = attrs.fitToWidth === 'true';
// Update computed values
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
}
/**
* Get a config value
*/
static get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
return CalendarConfig.config[key];
}
/**
* Set a config value (no events - use ConfigManager for updates with events)
*/
static set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
CalendarConfig.config[key] = value;
}
/**
* Update multiple config values (no events - use ConfigManager for updates with events)
*/
static update(updates: Partial<ICalendarConfig>): void {
Object.entries(updates).forEach(([key, value]) => {
CalendarConfig.set(key as keyof ICalendarConfig, value);
});
}
/**
* Get all config
*/
static getAll(): ICalendarConfig {
return { ...CalendarConfig.config };
}
/**
* Calculate derived values
*/
static get minuteHeight(): number {
return CalendarConfig.gridSettings.hourHeight / 60;
}
static get totalHours(): number {
return CalendarConfig.gridSettings.dayEndHour - CalendarConfig.gridSettings.dayStartHour;
}
static get totalMinutes(): number {
return CalendarConfig.totalHours * 60;
}
static get slotsPerHour(): number {
return 60 / CalendarConfig.gridSettings.snapInterval;
}
static get totalSlots(): number {
return CalendarConfig.totalHours * CalendarConfig.slotsPerHour;
}
static get slotHeight(): number {
return CalendarConfig.gridSettings.hourHeight / CalendarConfig.slotsPerHour;
}
/**
* Validate snap interval
*/
static isValidSnapInterval(interval: number): boolean {
return [5, 10, 15, 30, 60].includes(interval);
}
/**
* Get grid display settings
*/
static getGridSettings(): GridSettings {
return { ...CalendarConfig.gridSettings };
}
/**
* Update grid display settings (no events - use ConfigManager for updates with events)
*/
static updateGridSettings(updates: Partial<GridSettings>): void {
CalendarConfig.gridSettings = { ...CalendarConfig.gridSettings, ...updates };
// Update computed values
if (updates.snapInterval) {
CalendarConfig.config.minEventDuration = updates.snapInterval;
}
}
/**
* Get date view settings
*/
static getDateViewSettings(): DateViewSettings {
return { ...CalendarConfig.dateViewSettings };
}
/**
* Get selected date
*/
static getSelectedDate(): Date | null {
return CalendarConfig.selectedDate;
}
/**
* Set selected date
* Note: Does not emit events - caller is responsible for event emission
*/
static setSelectedDate(date: Date): void {
CalendarConfig.selectedDate = date;
}
/**
* Get work week presets
*/
private static getWorkWeekPresets(): { [key: string]: WorkWeekSettings } {
return {
'standard': {
id: 'standard',
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
totalDays: 5,
firstWorkDay: 1
},
'compressed': {
id: 'compressed',
workDays: [1,2,3,4], // Monday-Thursday (ISO)
totalDays: 4,
firstWorkDay: 1
},
'midweek': {
id: 'midweek',
workDays: [3,4,5], // Wednesday-Friday (ISO)
totalDays: 3,
firstWorkDay: 3
},
'weekend': {
id: 'weekend',
workDays: [6,7], // Saturday-Sunday (ISO)
totalDays: 2,
firstWorkDay: 6
},
'fullweek': {
id: 'fullweek',
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
totalDays: 7,
firstWorkDay: 1
}
};
}
/**
* Get current work week settings
*/
static getWorkWeekSettings(): WorkWeekSettings {
const presets = CalendarConfig.getWorkWeekPresets();
return presets[CalendarConfig.currentWorkWeek] || presets['standard'];
}
/**
* Set work week preset
* Note: Does not emit events - caller is responsible for event emission
*/
static setWorkWeek(workWeekId: string): void {
const presets = CalendarConfig.getWorkWeekPresets();
if (presets[workWeekId]) {
CalendarConfig.currentWorkWeek = workWeekId;
// Update dateViewSettings to match work week
CalendarConfig.dateViewSettings.weekDays = presets[workWeekId].totalDays;
}
}
/**
* Get current work week ID
*/
static getCurrentWorkWeek(): string {
return CalendarConfig.currentWorkWeek;
}
/**
* Get time format settings
*/
static getTimeFormatSettings(): TimeFormatConfig {
return { ...CalendarConfig.timeFormatConfig };
}
/**
* Get configured timezone
*/
static getTimezone(): string {
return CalendarConfig.timeFormatConfig.timezone;
}
/**
* Get configured locale
*/
static getLocale(): string {
return CalendarConfig.timeFormatConfig.locale;
}
/**
* Check if using 24-hour format
*/
static is24HourFormat(): boolean {
return CalendarConfig.timeFormatConfig.use24HourFormat;
}
/**
* Get current date format
*/
static getDateFormat(): 'locale' | 'technical' {
return CalendarConfig.timeFormatConfig.dateFormat;
}
/**
* Load configuration from JSON
*/
static loadFromJSON(json: string): void {
try {
const data = JSON.parse(json);
if (data.gridSettings) CalendarConfig.updateGridSettings(data.gridSettings);
if (data.dateViewSettings) CalendarConfig.dateViewSettings = { ...CalendarConfig.dateViewSettings, ...data.dateViewSettings };
if (data.timeFormatConfig) {
CalendarConfig.timeFormatConfig = { ...CalendarConfig.timeFormatConfig, ...data.timeFormatConfig };
TimeFormatter.configure(CalendarConfig.timeFormatConfig);
}
} catch (error) {
console.error('Failed to load config from JSON:', error);
}
}
// ========================================================================
// Instance method wrappers for backward compatibility
// These allow injected CalendarConfig to work with existing code
// ========================================================================
get(key: keyof ICalendarConfig) { return CalendarConfig.get(key); }
set(key: keyof ICalendarConfig, value: any) { return CalendarConfig.set(key, value); }
update(updates: Partial<ICalendarConfig>) { return CalendarConfig.update(updates); }
getAll() { return CalendarConfig.getAll(); }
get minuteHeight() { return CalendarConfig.minuteHeight; }
get totalHours() { return CalendarConfig.totalHours; }
get totalMinutes() { return CalendarConfig.totalMinutes; }
get slotsPerHour() { return CalendarConfig.slotsPerHour; }
get totalSlots() { return CalendarConfig.totalSlots; }
get slotHeight() { return CalendarConfig.slotHeight; }
isValidSnapInterval(interval: number) { return CalendarConfig.isValidSnapInterval(interval); }
getGridSettings() { return CalendarConfig.getGridSettings(); }
updateGridSettings(updates: Partial<GridSettings>) { return CalendarConfig.updateGridSettings(updates); }
getDateViewSettings() { return CalendarConfig.getDateViewSettings(); }
getSelectedDate() { return CalendarConfig.getSelectedDate(); }
setSelectedDate(date: Date) { return CalendarConfig.setSelectedDate(date); }
getWorkWeekSettings() { return CalendarConfig.getWorkWeekSettings(); }
setWorkWeek(workWeekId: string) { return CalendarConfig.setWorkWeek(workWeekId); }
getCurrentWorkWeek() { return CalendarConfig.getCurrentWorkWeek(); }
getTimeFormatSettings() { return CalendarConfig.getTimeFormatSettings(); }
getTimezone() { return CalendarConfig.getTimezone(); }
getLocale() { return CalendarConfig.getLocale(); }
is24HourFormat() { return CalendarConfig.is24HourFormat(); }
getDateFormat() { return CalendarConfig.getDateFormat(); }
}

View file

@ -1,14 +1,14 @@
// Core EventBus using pure DOM CustomEvents
import { EventLogEntry, ListenerEntry, IEventBus } from '../types/CalendarTypes';
import { IEventLogEntry, IListenerEntry, IEventBus } from '../types/CalendarTypes';
/**
* Central event dispatcher for calendar using DOM CustomEvents
* Provides logging and debugging capabilities
*/
export class EventBus implements IEventBus {
private eventLog: EventLogEntry[] = [];
private eventLog: IEventLogEntry[] = [];
private debug: boolean = false;
private listeners: Set<ListenerEntry> = new Set();
private listeners: Set<IListenerEntry> = new Set();
// Log configuration for different categories
private logConfig: { [key: string]: boolean } = {
@ -161,7 +161,7 @@ export class EventBus implements IEventBus {
/**
* Get event history
*/
getEventLog(eventType?: string): EventLogEntry[] {
getEventLog(eventType?: string): IEventLogEntry[] {
if (eventType) {
return this.eventLog.filter(e => e.type === eventType);
}

View file

@ -1,5 +1,5 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { CalendarConfig } from '../core/CalendarConfig';
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { TimeFormatter } from '../utils/TimeFormatter';
import { PositionUtils } from '../utils/PositionUtils';
import { DateService } from '../utils/DateService';
@ -9,12 +9,12 @@ import { DateService } from '../utils/DateService';
*/
export abstract class BaseSwpEventElement extends HTMLElement {
protected dateService: DateService;
protected config: CalendarConfig;
protected config: Configuration;
constructor() {
super();
// TODO: Find better solution for web component DI
this.config = new CalendarConfig();
// Get singleton instance for web components (can't use DI)
this.config = Configuration.getInstance();
this.dateService = new DateService(this.config);
}
@ -256,11 +256,11 @@ export class SwpEventElement extends BaseSwpEventElement {
// ============================================
/**
* Create SwpEventElement from CalendarEvent
* Create SwpEventElement from ICalendarEvent
*/
public static fromCalendarEvent(event: CalendarEvent): SwpEventElement {
public static fromCalendarEvent(event: ICalendarEvent): SwpEventElement {
const element = document.createElement('swp-event') as SwpEventElement;
const config = new CalendarConfig();
const config = Configuration.getInstance();
const dateService = new DateService(config);
element.dataset.eventId = event.id;
@ -274,9 +274,9 @@ export class SwpEventElement extends BaseSwpEventElement {
}
/**
* Extract CalendarEvent from DOM element
* Extract ICalendarEvent from DOM element
*/
public static extractCalendarEventFromElement(element: HTMLElement): CalendarEvent {
public static extractCalendarEventFromElement(element: HTMLElement): ICalendarEvent {
return {
id: element.dataset.eventId || '',
title: element.dataset.title || '',
@ -331,11 +331,11 @@ export class SwpAllDayEventElement extends BaseSwpEventElement {
}
/**
* Create from CalendarEvent
* Create from ICalendarEvent
*/
public static fromCalendarEvent(event: CalendarEvent): SwpAllDayEventElement {
public static fromCalendarEvent(event: ICalendarEvent): SwpAllDayEventElement {
const element = document.createElement('swp-allday-event') as SwpAllDayEventElement;
const config = new CalendarConfig();
const config = Configuration.getInstance();
const dateService = new DateService(config);
element.dataset.eventId = event.id;

View file

@ -1,7 +1,8 @@
// Main entry point for Calendar Plantempus
import { Container } from '@novadi/core';
import { eventBus } from './core/EventBus';
import { CalendarConfig } from './core/CalendarConfig';
import { ConfigManager } from './configuration/ConfigManager';
import { Configuration } from './configuration/CalendarConfig';
import { URLManager } from './utils/URLManager';
import { IEventBus } from './types/CalendarTypes';
@ -19,7 +20,6 @@ import { ResizeHandleManager } from './managers/ResizeHandleManager';
import { EdgeScrollManager } from './managers/EdgeScrollManager';
import { DragHoverManager } from './managers/DragHoverManager';
import { HeaderManager } from './managers/HeaderManager';
import { ConfigManager } from './managers/ConfigManager';
// Import repositories
import { IEventRepository } from './repositories/IEventRepository';
@ -27,7 +27,7 @@ import { MockEventRepository } from './repositories/MockEventRepository';
// Import renderers
import { DateHeaderRenderer, type IHeaderRenderer } from './renderers/DateHeaderRenderer';
import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
import { DateColumnRenderer, type IColumnRenderer } from './renderers/ColumnRenderer';
import { DateEventRenderer, type IEventRenderer } from './renderers/EventRenderer';
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
import { GridRenderer } from './renderers/GridRenderer';
@ -70,8 +70,8 @@ async function handleDeepLinking(eventManager: EventManager, urlManager: URLMana
*/
async function initializeCalendar(): Promise<void> {
try {
// Initialize static calendar configuration
CalendarConfig.initialize();
// Load configuration from JSON
const config = await ConfigManager.load();
// Create NovaDI container
const container = new Container();
@ -80,21 +80,18 @@ async function initializeCalendar(): Promise<void> {
// Enable debug mode for development
eventBus.setDebug(true);
// Register CalendarConfig as singleton instance (static class, not instantiated)
builder.registerInstance(CalendarConfig).as<CalendarConfig>();
// Register ConfigManager for event-driven config updates
builder.registerType(ConfigManager).as<ConfigManager>();
// Bind core services as instances
builder.registerInstance(eventBus).as<IEventBus>();
// Register configuration instance
builder.registerInstance(config).as<Configuration>();
// Register repositories
builder.registerType(MockEventRepository).as<IEventRepository>();
// Register renderers
builder.registerType(DateHeaderRenderer).as<IHeaderRenderer>();
builder.registerType(DateColumnRenderer).as<ColumnRenderer>();
builder.registerType(DateColumnRenderer).as<IColumnRenderer>();
builder.registerType(DateEventRenderer).as<IEventRenderer>();
// Register core services and utilities
@ -130,7 +127,6 @@ async function initializeCalendar(): Promise<void> {
// Get managers from container
const eb = app.resolveType<IEventBus>();
const configManager = app.resolveType<ConfigManager>();
const calendarManager = app.resolveType<CalendarManager>();
const eventManager = app.resolveType<EventManager>();
const resizeHandleManager = app.resolveType<ResizeHandleManager>();
@ -143,9 +139,6 @@ async function initializeCalendar(): Promise<void> {
const allDayManager = app.resolveType<AllDayManager>();
const urlManager = app.resolveType<URLManager>();
// Initialize CSS variables before any rendering
configManager.initialize();
// Initialize managers
await calendarManager.initialize?.();
await resizeHandleManager.initialize?.();

View file

@ -1,21 +1,21 @@
// All-day row height management and animations
import { eventBus } from '../core/EventBus';
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
import { ALL_DAY_CONSTANTS } from '../configuration/CalendarConfig';
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { CalendarEvent } from '../types/CalendarTypes';
import { AllDayLayoutEngine, IEventLayout } from '../utils/AllDayLayoutEngine';
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { ICalendarEvent } from '../types/CalendarTypes';
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
import {
DragMouseEnterHeaderEventPayload,
DragStartEventPayload,
DragMoveEventPayload,
DragEndEventPayload,
DragColumnChangeEventPayload,
HeaderReadyEventPayload
IDragMouseEnterHeaderEventPayload,
IDragStartEventPayload,
IDragMoveEventPayload,
IDragEndEventPayload,
IDragColumnChangeEventPayload,
IHeaderReadyEventPayload
} from '../types/EventTypes';
import { DragOffset, MousePosition } from '../types/DragDropTypes';
import { IDragOffset, IMousePosition } from '../types/DragDropTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { EventManager } from './EventManager';
import { differenceInCalendarDays } from 'date-fns';
@ -33,10 +33,10 @@ export class AllDayManager {
private layoutEngine: AllDayLayoutEngine | null = null;
// State tracking for differential updates
private currentLayouts: EventLayout[] = [];
private currentAllDayEvents: CalendarEvent[] = [];
private currentWeekDates: ColumnBounds[] = [];
private newLayouts: EventLayout[] = [];
private currentLayouts: IEventLayout[] = [];
private currentAllDayEvents: ICalendarEvent[] = [];
private currentWeekDates: IColumnBounds[] = [];
private newLayouts: IEventLayout[] = [];
// Expand/collapse state
private isExpanded: boolean = false;
@ -62,7 +62,7 @@ export class AllDayManager {
*/
private setupEventListeners(): void {
eventBus.on('drag:mouseenter-header', (event) => {
const payload = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
const payload = (event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
if (payload.draggedClone.hasAttribute('data-allday'))
return;
@ -87,7 +87,7 @@ export class AllDayManager {
// Listen for drag operations on all-day events
eventBus.on('drag:start', (event) => {
let payload: DragStartEventPayload = (event as CustomEvent<DragStartEventPayload>).detail;
let payload: IDragStartEventPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
if (!payload.draggedClone?.hasAttribute('data-allday')) {
return;
@ -97,7 +97,7 @@ export class AllDayManager {
});
eventBus.on('drag:column-change', (event) => {
let payload: DragColumnChangeEventPayload = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
let payload: IDragColumnChangeEventPayload = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
if (!payload.draggedClone?.hasAttribute('data-allday')) {
return;
@ -107,7 +107,7 @@ export class AllDayManager {
});
eventBus.on('drag:end', (event) => {
let draggedElement: DragEndEventPayload = (event as CustomEvent<DragEndEventPayload>).detail;
let draggedElement: IDragEndEventPayload = (event as CustomEvent<IDragEndEventPayload>).detail;
if (draggedElement.target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
return;
@ -128,12 +128,12 @@ export class AllDayManager {
// Listen for header ready - when dates are populated with period data
eventBus.on('header:ready', (event: Event) => {
let headerReadyEventPayload = (event as CustomEvent<HeaderReadyEventPayload>).detail;
let headerReadyEventPayload = (event as CustomEvent<IHeaderReadyEventPayload>).detail;
let startDate = new Date(headerReadyEventPayload.headerElements.at(0)!.date);
let endDate = new Date(headerReadyEventPayload.headerElements.at(-1)!.date);
let events: CalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
let events: ICalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
// Filter for all-day events
const allDayEvents = events.filter(event => event.allDay);
@ -302,7 +302,7 @@ export class AllDayManager {
* Calculate layout for ALL all-day events using AllDayLayoutEngine
* This is the correct method that processes all events together for proper overlap detection
*/
private calculateAllDayEventsLayout(events: CalendarEvent[], dayHeaders: ColumnBounds[]): EventLayout[] {
private calculateAllDayEventsLayout(events: ICalendarEvent[], dayHeaders: IColumnBounds[]): IEventLayout[] {
// Store current state
this.currentAllDayEvents = events;
@ -316,12 +316,12 @@ export class AllDayManager {
}
private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void {
private handleConvertToAllDay(payload: IDragMouseEnterHeaderEventPayload): void {
let allDayContainer = this.getAllDayContainer();
if (!allDayContainer) return;
// Create SwpAllDayEventElement from CalendarEvent
// Create SwpAllDayEventElement from ICalendarEvent
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
// Apply grid positioning
@ -345,7 +345,7 @@ export class AllDayManager {
/**
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
*/
private handleColumnChange(dragColumnChangeEventPayload: DragColumnChangeEventPayload): void {
private handleColumnChange(dragColumnChangeEventPayload: IDragColumnChangeEventPayload): void {
let allDayContainer = this.getAllDayContainer();
if (!allDayContainer) return;
@ -380,7 +380,7 @@ export class AllDayManager {
}
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
private handleDragEnd(dragEndEvent: IDragEndEventPayload): void {
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
@ -433,7 +433,7 @@ export class AllDayManager {
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
const droppedEvent: CalendarEvent = {
const droppedEvent: ICalendarEvent = {
id: eventId,
title: dragEndEvent.draggedClone.dataset.title || '',
start: newStartDate,
@ -557,9 +557,9 @@ export class AllDayManager {
});
}
/**
* Count number of events in a specific column using ColumnBounds
* Count number of events in a specific column using IColumnBounds
*/
private countEventsInColumn(columnBounds: ColumnBounds): number {
private countEventsInColumn(columnBounds: IColumnBounds): number {
let columnIndex = columnBounds.index;
let count = 0;

View file

@ -1,5 +1,5 @@
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { EventManager } from './EventManager';
import { GridManager } from './GridManager';
@ -15,7 +15,7 @@ export class CalendarManager {
private gridManager: GridManager;
private eventRenderer: EventRenderingService;
private scrollManager: ScrollManager;
private config: CalendarConfig;
private config: Configuration;
private currentView: CalendarView = 'week';
private currentDate: Date = new Date();
private isInitialized: boolean = false;
@ -26,7 +26,7 @@ export class CalendarManager {
gridManager: GridManager,
eventRenderingService: EventRenderingService,
scrollManager: ScrollManager,
config: CalendarConfig
config: Configuration
) {
this.eventBus = eventBus;
this.eventManager = eventManager;

View file

@ -1,174 +0,0 @@
// Configuration manager - handles config updates with event emission
// Uses static CalendarConfig internally but adds event-driven updates
import { IEventBus, ICalendarConfig } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
/**
* Grid display settings interface (re-export from CalendarConfig)
*/
interface GridSettings {
dayStartHour: number;
dayEndHour: number;
workStartHour: number;
workEndHour: number;
hourHeight: number;
snapInterval: number;
fitToWidth: boolean;
scrollToHour: number | null;
gridStartThresholdMinutes: number;
showCurrentTime: boolean;
showWorkHours: boolean;
}
/**
* ConfigManager - Handles configuration updates with event emission
* Wraps static CalendarConfig with event-driven functionality for DI system
* Also manages CSS custom properties that reflect config values
*/
export class ConfigManager {
constructor(private eventBus: IEventBus) {}
/**
* Initialize CSS variables on startup
* Must be called after DOM is ready but before any rendering
*/
public initialize(): void {
this.updateCSSVariables();
}
/**
* Set a config value and emit event
*/
set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
const oldValue = CalendarConfig.get(key);
CalendarConfig.set(key, value);
// Update CSS variables to reflect config change
this.updateCSSVariables();
// Emit config update event
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key,
value,
oldValue
});
}
/**
* Update multiple config values and emit event
*/
update(updates: Partial<ICalendarConfig>): void {
Object.entries(updates).forEach(([key, value]) => {
this.set(key as keyof ICalendarConfig, value);
});
}
/**
* Update grid display settings and emit event
*/
updateGridSettings(updates: Partial<GridSettings>): void {
CalendarConfig.updateGridSettings(updates);
// Update CSS variables to reflect config change
this.updateCSSVariables();
// Emit event after update
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'gridSettings',
value: CalendarConfig.getGridSettings()
});
}
/**
* Set selected date and emit event
*/
setSelectedDate(date: Date): void {
const oldDate = CalendarConfig.getSelectedDate();
CalendarConfig.setSelectedDate(date);
// Emit date change event if it actually changed
if (!oldDate || oldDate.getTime() !== date.getTime()) {
this.eventBus.emit(CoreEvents.DATE_CHANGED, {
date,
oldDate
});
}
}
/**
* Set work week and emit event
*/
setWorkWeek(workWeekId: string): void {
const oldWorkWeek = CalendarConfig.getCurrentWorkWeek();
CalendarConfig.setWorkWeek(workWeekId);
// Update CSS variables to reflect config change
this.updateCSSVariables();
// Emit event if changed
if (oldWorkWeek !== workWeekId) {
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'workWeek',
value: workWeekId,
oldValue: oldWorkWeek
});
}
}
/**
* Update all CSS custom properties based on current config
* This keeps the DOM in sync with config values
*/
private updateCSSVariables(): void {
const root = document.documentElement;
const gridSettings = CalendarConfig.getGridSettings();
const calendar = document.querySelector('swp-calendar') as HTMLElement;
// Set time-related CSS variables
root.style.setProperty('--header-height', '80px'); // Fixed header height
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());
// Set column count based on view
const columnCount = this.calculateColumnCount();
root.style.setProperty('--grid-columns', columnCount.toString());
// Set column width based on fitToWidth setting
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
}
// Set fitToWidth data attribute for CSS targeting
if (calendar) {
calendar.setAttribute('data-fit-to-width', gridSettings.fitToWidth.toString());
}
}
/**
* Calculate number of columns based on view
*/
private calculateColumnCount(): number {
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;
}
}
}

View file

@ -134,33 +134,33 @@
import { IEventBus } from '../types/CalendarTypes';
import { PositionUtils } from '../utils/PositionUtils';
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
import {
DragStartEventPayload,
DragMoveEventPayload,
DragEndEventPayload,
DragMouseEnterHeaderEventPayload,
DragMouseLeaveHeaderEventPayload,
DragMouseEnterColumnEventPayload,
DragColumnChangeEventPayload
IDragStartEventPayload,
IDragMoveEventPayload,
IDragEndEventPayload,
IDragMouseEnterHeaderEventPayload,
IDragMouseLeaveHeaderEventPayload,
IDragMouseEnterColumnEventPayload,
IDragColumnChangeEventPayload
} from '../types/EventTypes';
import { MousePosition } from '../types/DragDropTypes';
import { IMousePosition } from '../types/DragDropTypes';
import { CoreEvents } from '../constants/CoreEvents';
export class DragDropManager {
private eventBus: IEventBus;
// Mouse tracking with optimized state
private mouseDownPosition: MousePosition = { x: 0, y: 0 };
private currentMousePosition: MousePosition = { x: 0, y: 0 };
private mouseOffset: MousePosition = { x: 0, y: 0 };
private mouseDownPosition: IMousePosition = { x: 0, y: 0 };
private currentMousePosition: IMousePosition = { x: 0, y: 0 };
private mouseOffset: IMousePosition = { x: 0, y: 0 };
// Drag state
private originalElement!: HTMLElement | null;
private draggedClone!: HTMLElement | null;
private currentColumn: ColumnBounds | null = null;
private previousColumn: ColumnBounds | null = null;
private currentColumn: IColumnBounds | null = null;
private previousColumn: IColumnBounds | null = null;
private isDragStarted = false;
// Movement threshold to distinguish click from drag
@ -176,7 +176,7 @@ export class DragDropManager {
private dragAnimationId: number | null = null;
private targetY = 0;
private currentY = 0;
private targetColumn: ColumnBounds | null = null;
private targetColumn: IColumnBounds | null = null;
private positionUtils: PositionUtils;
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
@ -336,7 +336,7 @@ export class DragDropManager {
* Try to initialize drag based on movement threshold
* Returns true if drag was initialized, false if not enough movement
*/
private initializeDrag(currentPosition: MousePosition): boolean {
private initializeDrag(currentPosition: IMousePosition): boolean {
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
@ -362,7 +362,7 @@ export class DragDropManager {
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
this.draggedClone = originalElement.createClone();
const dragStartPayload: DragStartEventPayload = {
const dragStartPayload: IDragStartEventPayload = {
originalElement: this.originalElement!,
draggedClone: this.draggedClone,
mousePosition: this.mouseDownPosition,
@ -375,7 +375,7 @@ export class DragDropManager {
}
private continueDrag(currentPosition: MousePosition): void {
private continueDrag(currentPosition: IMousePosition): void {
if (!this.draggedClone!.hasAttribute("data-allday")) {
// Calculate raw position from mouse (no snapping)
@ -405,7 +405,7 @@ export class DragDropManager {
/**
* Detect column change and emit event
*/
private detectColumnChange(currentPosition: MousePosition): void {
private detectColumnChange(currentPosition: IMousePosition): void {
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
if (newColumn == null) return;
@ -413,7 +413,7 @@ export class DragDropManager {
this.previousColumn = this.currentColumn;
this.currentColumn = newColumn;
const dragColumnChangePayload: DragColumnChangeEventPayload = {
const dragColumnChangePayload: IDragColumnChangeEventPayload = {
originalElement: this.originalElement!,
draggedClone: this.draggedClone!,
previousColumn: this.previousColumn,
@ -434,7 +434,7 @@ export class DragDropManager {
// Only emit drag:end if drag was actually started
if (this.isDragStarted) {
const mousePosition: MousePosition = { x: event.clientX, y: event.clientY };
const mousePosition: IMousePosition = { x: event.clientX, y: event.clientY };
// Snap to grid on mouse up (like ResizeHandleManager)
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
@ -455,7 +455,7 @@ export class DragDropManager {
if (!dropTarget)
throw "dropTarget is null";
const dragEndPayload: DragEndEventPayload = {
const dragEndPayload: IDragEndEventPayload = {
originalElement: this.originalElement,
draggedClone: this.draggedClone,
mousePosition,
@ -530,7 +530,7 @@ export class DragDropManager {
/**
* Optimized snap position calculation using PositionUtils
*/
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
private calculateSnapPosition(mouseY: number, column: IColumnBounds): number {
// Calculate where the event top would be (accounting for mouse offset)
const eventTopY = mouseY - this.mouseOffset.y;
@ -560,7 +560,7 @@ export class DragDropManager {
this.currentY += step;
// Emit drag:move event with current draggedClone reference
const dragMovePayload: DragMoveEventPayload = {
const dragMovePayload: IDragMoveEventPayload = {
originalElement: this.originalElement!,
draggedClone: this.draggedClone, // Always uses current reference
mousePosition: this.currentMousePosition, // Use current mouse position!
@ -576,7 +576,7 @@ export class DragDropManager {
this.currentY = this.targetY;
// Emit final position
const dragMovePayload: DragMoveEventPayload = {
const dragMovePayload: IDragMoveEventPayload = {
originalElement: this.originalElement!,
draggedClone: this.draggedClone,
mousePosition: this.currentMousePosition, // Use current mouse position!
@ -633,7 +633,7 @@ export class DragDropManager {
/**
* Detect drop target - whether dropped in swp-day-column or swp-day-header
*/
private detectDropTarget(position: MousePosition): 'swp-day-column' | 'swp-day-header' | null {
private detectDropTarget(position: IMousePosition): 'swp-day-column' | 'swp-day-header' | null {
// Traverse up the DOM tree to find the target container
let currentElement = this.draggedClone;
@ -659,13 +659,13 @@ export class DragDropManager {
return;
}
const position: MousePosition = { x: event.clientX, y: event.clientY };
const position: IMousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (targetColumn) {
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
const dragMouseEnterPayload: IDragMouseEnterHeaderEventPayload = {
targetColumn: targetColumn,
mousePosition: position,
originalElement: this.originalElement,
@ -689,7 +689,7 @@ export class DragDropManager {
return;
}
const position: MousePosition = { x: event.clientX, y: event.clientY };
const position: IMousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
@ -699,10 +699,10 @@ export class DragDropManager {
// Calculate snapped Y position
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
// Extract CalendarEvent from the dragged clone
// Extract ICalendarEvent from the dragged clone
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = {
const dragMouseEnterPayload: IDragMouseEnterColumnEventPayload = {
targetColumn: targetColumn,
mousePosition: position,
snappedY: snappedY,
@ -727,14 +727,14 @@ export class DragDropManager {
return;
}
const position: MousePosition = { x: event.clientX, y: event.clientY };
const position: IMousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
return;
}
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
const dragMouseLeavePayload: IDragMouseLeaveHeaderEventPayload = {
targetDate: targetColumn.date,
mousePosition: position,
originalElement: this.originalElement,

View file

@ -4,7 +4,7 @@
*/
import { IEventBus } from '../types/CalendarTypes';
import { DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
import { IDragMoveEventPayload, IDragStartEventPayload } from '../types/EventTypes';
export class EdgeScrollManager {
private scrollableContent: HTMLElement | null = null;

View file

@ -5,24 +5,24 @@
import { eventBus } from '../core/EventBus';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarEvent } from '../types/CalendarTypes';
import { ICalendarEvent } from '../types/CalendarTypes';
// Import Fuse.js from npm
import Fuse from 'fuse.js';
interface FuseResult {
item: CalendarEvent;
item: ICalendarEvent;
refIndex: number;
score?: number;
}
export class EventFilterManager {
private searchInput: HTMLInputElement | null = null;
private allEvents: CalendarEvent[] = [];
private allEvents: ICalendarEvent[] = [];
private matchingEventIds: Set<string> = new Set();
private isFilterActive: boolean = false;
private frameRequest: number | null = null;
private fuse: Fuse<CalendarEvent> | null = null;
private fuse: Fuse<ICalendarEvent> | null = null;
constructor() {
// Wait for DOM to be ready before initializing
@ -77,7 +77,7 @@ export class EventFilterManager {
});
}
private updateEventsList(events: CalendarEvent[]): void {
private updateEventsList(events: ICalendarEvent[]): void {
this.allEvents = events;
// Initialize Fuse with the new events list

View file

@ -5,35 +5,35 @@
* Calculates stack levels, groups events, and determines rendering strategy.
*/
import { CalendarEvent } from '../types/CalendarTypes';
import { EventStackManager, EventGroup, StackLink } from './EventStackManager';
import { ICalendarEvent } from '../types/CalendarTypes';
import { EventStackManager, IEventGroup, IStackLink } from './EventStackManager';
import { PositionUtils } from '../utils/PositionUtils';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
export interface GridGroupLayout {
events: CalendarEvent[];
export interface IGridGroupLayout {
events: ICalendarEvent[];
stackLevel: number;
position: { top: number };
columns: CalendarEvent[][]; // Events grouped by column (events in same array share a column)
columns: ICalendarEvent[][]; // Events grouped by column (events in same array share a column)
}
export interface StackedEventLayout {
event: CalendarEvent;
stackLink: StackLink;
export interface IStackedEventLayout {
event: ICalendarEvent;
stackLink: IStackLink;
position: { top: number; height: number };
}
export interface ColumnLayout {
gridGroups: GridGroupLayout[];
stackedEvents: StackedEventLayout[];
export interface IColumnLayout {
gridGroups: IGridGroupLayout[];
stackedEvents: IStackedEventLayout[];
}
export class EventLayoutCoordinator {
private stackManager: EventStackManager;
private config: CalendarConfig;
private config: Configuration;
private positionUtils: PositionUtils;
constructor(stackManager: EventStackManager, config: CalendarConfig, positionUtils: PositionUtils) {
constructor(stackManager: EventStackManager, config: Configuration, positionUtils: PositionUtils) {
this.stackManager = stackManager;
this.config = config;
this.positionUtils = positionUtils;
@ -42,14 +42,14 @@ export class EventLayoutCoordinator {
/**
* Calculate complete layout for a column of events (recursive approach)
*/
public calculateColumnLayout(columnEvents: CalendarEvent[]): ColumnLayout {
public calculateColumnLayout(columnEvents: ICalendarEvent[]): IColumnLayout {
if (columnEvents.length === 0) {
return { gridGroups: [], stackedEvents: [] };
}
const gridGroupLayouts: GridGroupLayout[] = [];
const stackedEventLayouts: StackedEventLayout[] = [];
const renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }> = [];
const gridGroupLayouts: IGridGroupLayout[] = [];
const stackedEventLayouts: IStackedEventLayout[] = [];
const renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }> = [];
let remaining = [...columnEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
// Process events recursively
@ -66,7 +66,7 @@ export class EventLayoutCoordinator {
const gridCandidates = this.expandGridCandidates(firstEvent, remaining, thresholdMinutes);
// Decide: should this group be GRID or STACK?
const group: EventGroup = {
const group: IEventGroup = {
events: gridCandidates,
containerType: 'NONE',
startTime: firstEvent.start
@ -129,8 +129,8 @@ export class EventLayoutCoordinator {
* Calculate stack level for a grid group based on already rendered events
*/
private calculateGridGroupStackLevelFromRendered(
gridEvents: CalendarEvent[],
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
gridEvents: ICalendarEvent[],
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
): number {
// Find highest stack level of any rendered event that overlaps with this grid
let maxOverlappingLevel = -1;
@ -150,8 +150,8 @@ export class EventLayoutCoordinator {
* Calculate stack level for a single stacked event based on already rendered events
*/
private calculateStackLevelFromRendered(
event: CalendarEvent,
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
event: ICalendarEvent,
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
): number {
// Find highest stack level of any rendered event that overlaps with this event
let maxOverlappingLevel = -1;
@ -173,7 +173,7 @@ export class EventLayoutCoordinator {
* @param thresholdMinutes - Threshold in minutes
* @returns true if events conflict
*/
private detectConflict(event1: CalendarEvent, event2: CalendarEvent, thresholdMinutes: number): boolean {
private detectConflict(event1: ICalendarEvent, event2: ICalendarEvent, thresholdMinutes: number): boolean {
// Check 1: Start-to-start conflict (starts within threshold)
const startToStartDiff = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60);
if (startToStartDiff <= thresholdMinutes && this.stackManager.doEventsOverlap(event1, event2)) {
@ -206,10 +206,10 @@ export class EventLayoutCoordinator {
* @returns Array of all events in the conflict chain
*/
private expandGridCandidates(
firstEvent: CalendarEvent,
remaining: CalendarEvent[],
firstEvent: ICalendarEvent,
remaining: ICalendarEvent[],
thresholdMinutes: number
): CalendarEvent[] {
): ICalendarEvent[] {
const gridCandidates = [firstEvent];
let candidatesChanged = true;
@ -246,11 +246,11 @@ export class EventLayoutCoordinator {
* @param events - Events in the grid group (should already be sorted by start time)
* @returns Array of columns, where each column is an array of events
*/
private allocateColumns(events: CalendarEvent[]): CalendarEvent[][] {
private allocateColumns(events: ICalendarEvent[]): ICalendarEvent[][] {
if (events.length === 0) return [];
if (events.length === 1) return [[events[0]]];
const columns: CalendarEvent[][] = [];
const columns: ICalendarEvent[][] = [];
// For each event, try to place it in an existing column where it doesn't overlap
for (const event of events) {

View file

@ -1,6 +1,6 @@
import { IEventBus, CalendarEvent } from '../types/CalendarTypes';
import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { DateService } from '../utils/DateService';
import { IEventRepository } from '../repositories/IEventRepository';
@ -10,15 +10,15 @@ import { IEventRepository } from '../repositories/IEventRepository';
*/
export class EventManager {
private events: CalendarEvent[] = [];
private events: ICalendarEvent[] = [];
private dateService: DateService;
private config: CalendarConfig;
private config: Configuration;
private repository: IEventRepository;
constructor(
private eventBus: IEventBus,
dateService: DateService,
config: CalendarConfig,
config: Configuration,
repository: IEventRepository
) {
this.dateService = dateService;
@ -42,14 +42,14 @@ export class EventManager {
/**
* Get events with optional copying for performance
*/
public getEvents(copy: boolean = false): CalendarEvent[] {
public getEvents(copy: boolean = false): ICalendarEvent[] {
return copy ? [...this.events] : this.events;
}
/**
* Optimized event lookup with early return
*/
public getEventById(id: string): CalendarEvent | undefined {
public getEventById(id: string): ICalendarEvent | undefined {
// Use find for better performance than filter + first
return this.events.find(event => event.id === id);
}
@ -59,7 +59,7 @@ export class EventManager {
* @param id Event ID to find
* @returns Event with navigation info or null if not found
*/
public getEventForNavigation(id: string): { event: CalendarEvent; eventDate: Date } | null {
public getEventForNavigation(id: string): { event: ICalendarEvent; eventDate: Date } | null {
const event = this.getEventById(id);
if (!event) {
return null;
@ -113,7 +113,7 @@ export class EventManager {
/**
* Get events that overlap with a given time period
*/
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
public getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[] {
// Event overlaps period if it starts before period ends AND ends after period starts
return this.events.filter(event => {
return event.start <= endDate && event.end >= startDate;
@ -123,8 +123,8 @@ export class EventManager {
/**
* Create a new event and add it to the calendar
*/
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
const newEvent: CalendarEvent = {
public addEvent(event: Omit<ICalendarEvent, 'id'>): ICalendarEvent {
const newEvent: ICalendarEvent = {
...event,
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
};
@ -141,7 +141,7 @@ export class EventManager {
/**
* Update an existing event
*/
public updateEvent(id: string, updates: Partial<CalendarEvent>): CalendarEvent | null {
public updateEvent(id: string, updates: Partial<ICalendarEvent>): ICalendarEvent | null {
const eventIndex = this.events.findIndex(event => event.id === id);
if (eventIndex === -1) return null;

View file

@ -13,26 +13,26 @@
* @see stacking-visualization.html for visual examples
*/
import { CalendarEvent } from '../types/CalendarTypes';
import { CalendarConfig } from '../core/CalendarConfig';
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
export interface StackLink {
export interface IStackLink {
prev?: string; // Event ID of previous event in stack
next?: string; // Event ID of next event in stack
stackLevel: number; // Position in stack (0 = base, 1 = first offset, etc.)
}
export interface EventGroup {
events: CalendarEvent[];
export interface IEventGroup {
events: ICalendarEvent[];
containerType: 'NONE' | 'GRID' | 'STACKING';
startTime: Date;
}
export class EventStackManager {
private static readonly STACK_OFFSET_PX = 15;
private config: CalendarConfig;
private config: Configuration;
constructor(config: CalendarConfig) {
constructor(config: Configuration) {
this.config = config;
}
@ -47,7 +47,7 @@ export class EventStackManager {
* 1. They start within ±threshold minutes of each other (start-to-start)
* 2. One event starts within threshold minutes before another ends (end-to-start conflict)
*/
public groupEventsByStartTime(events: CalendarEvent[]): EventGroup[] {
public groupEventsByStartTime(events: ICalendarEvent[]): IEventGroup[] {
if (events.length === 0) return [];
// Get threshold from config
@ -57,7 +57,7 @@ export class EventStackManager {
// Sort events by start time
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
const groups: EventGroup[] = [];
const groups: IEventGroup[] = [];
for (const event of sorted) {
// Find existing group that this event conflicts with
@ -112,7 +112,7 @@ export class EventStackManager {
* even if they overlap each other. This provides better visual indication that
* events start at the same time.
*/
public decideContainerType(group: EventGroup): 'NONE' | 'GRID' | 'STACKING' {
public decideContainerType(group: IEventGroup): 'NONE' | 'GRID' | 'STACKING' {
if (group.events.length === 1) {
return 'NONE';
}
@ -127,7 +127,7 @@ export class EventStackManager {
/**
* Check if two events overlap in time
*/
public doEventsOverlap(event1: CalendarEvent, event2: CalendarEvent): boolean {
public doEventsOverlap(event1: ICalendarEvent, event2: ICalendarEvent): boolean {
return event1.start < event2.end && event1.end > event2.start;
}
@ -139,8 +139,8 @@ export class EventStackManager {
/**
* Create optimized stack links (events share levels when possible)
*/
public createOptimizedStackLinks(events: CalendarEvent[]): Map<string, StackLink> {
const stackLinks = new Map<string, StackLink>();
public createOptimizedStackLinks(events: ICalendarEvent[]): Map<string, IStackLink> {
const stackLinks = new Map<string, IStackLink>();
if (events.length === 0) return stackLinks;
@ -218,14 +218,14 @@ export class EventStackManager {
/**
* Serialize stack link to JSON string
*/
public serializeStackLink(stackLink: StackLink): string {
public serializeStackLink(stackLink: IStackLink): string {
return JSON.stringify(stackLink);
}
/**
* Deserialize JSON string to stack link
*/
public deserializeStackLink(json: string): StackLink | null {
public deserializeStackLink(json: string): IStackLink | null {
try {
return JSON.parse(json);
} catch (e) {
@ -236,14 +236,14 @@ export class EventStackManager {
/**
* Apply stack link to DOM element
*/
public applyStackLinkToElement(element: HTMLElement, stackLink: StackLink): void {
public applyStackLinkToElement(element: HTMLElement, stackLink: IStackLink): void {
element.dataset.stackLink = this.serializeStackLink(stackLink);
}
/**
* Get stack link from DOM element
*/
public getStackLinkFromElement(element: HTMLElement): StackLink | null {
public getStackLinkFromElement(element: HTMLElement): IStackLink | null {
const data = element.dataset.stackLink;
if (!data) return null;
return this.deserializeStackLink(data);

View file

@ -1,8 +1,8 @@
import { eventBus } from '../core/EventBus';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
import { IHeaderRenderer, HeaderRenderContext } from '../renderers/DateHeaderRenderer';
import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
import { IHeaderRenderer, IHeaderRenderContext } from '../renderers/DateHeaderRenderer';
import { IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IHeaderReadyEventPayload } from '../types/EventTypes';
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
/**
@ -12,9 +12,9 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
*/
export class HeaderManager {
private headerRenderer: IHeaderRenderer;
private config: CalendarConfig;
private config: Configuration;
constructor(headerRenderer: IHeaderRenderer, config: CalendarConfig) {
constructor(headerRenderer: IHeaderRenderer, config: Configuration) {
this.headerRenderer = headerRenderer;
this.config = config;
@ -44,7 +44,7 @@ export class HeaderManager {
*/
private handleDragMouseEnterHeader(event: Event): void {
const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
(event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
(event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
targetDate,
@ -58,7 +58,7 @@ export class HeaderManager {
*/
private handleDragMouseLeaveHeader(event: Event): void {
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
(event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
(event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
console.log('🚪 HeaderManager: Received drag:mouseleave-header', {
targetDate,
@ -109,7 +109,7 @@ export class HeaderManager {
calendarHeader.innerHTML = '';
// Render new header content using injected renderer
const context: HeaderRenderContext = {
const context: IHeaderRenderContext = {
currentWeek: currentDate,
config: this.config
};
@ -120,7 +120,7 @@ export class HeaderManager {
this.setupHeaderDragListeners();
// Notify other managers that header is ready with period data
const payload: HeaderReadyEventPayload = {
const payload: IHeaderReadyEventPayload = {
headerElements: ColumnDetectionUtils.getHeaderColumns(),
};
eventBus.emit('header:ready', payload);

View file

@ -1,7 +1,7 @@
import { eventBus } from '../core/EventBus';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
import { ResizeEndEventPayload } from '../types/EventTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { IResizeEndEventPayload } from '../types/EventTypes';
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
@ -29,9 +29,9 @@ export class ResizeHandleManager {
private unsubscribers: Array<() => void> = [];
private pointerCaptured = false;
private prevZ?: string;
private config: CalendarConfig;
private config: Configuration;
constructor(config: CalendarConfig) {
constructor(config: Configuration) {
this.config = config;
const grid = this.config.getGridSettings();
this.hourHeightPx = grid.hourHeight;
@ -237,7 +237,7 @@ export class ResizeHandleManager {
// Emit resize:end event for re-stacking
const eventId = this.targetEl.dataset.eventId || '';
const resizeEndPayload: ResizeEndEventPayload = {
const resizeEndPayload: IResizeEndEventPayload = {
eventId,
element: this.targetEl,
finalHeight

View file

@ -1,15 +1,15 @@
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
export class ViewManager {
private eventBus: IEventBus;
private config: CalendarConfig;
private config: Configuration;
private currentView: CalendarView = 'week';
private buttonListeners: Map<Element, EventListener> = new Map();
constructor(eventBus: IEventBus, config: CalendarConfig) {
constructor(eventBus: IEventBus, config: Configuration) {
this.eventBus = eventBus;
this.config = config;
this.setupEventListeners();

View file

@ -1,13 +1,13 @@
// Work hours management for per-column scheduling
import { DateService } from '../utils/DateService';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { PositionUtils } from '../utils/PositionUtils';
/**
* Work hours for a specific day
*/
export interface DayWorkHours {
export interface IDayWorkHours {
start: number; // Hour (0-23)
end: number; // Hour (0-23)
}
@ -15,18 +15,18 @@ export interface DayWorkHours {
/**
* Work schedule configuration
*/
export interface WorkScheduleConfig {
export interface IWorkScheduleConfig {
weeklyDefault: {
monday: DayWorkHours | 'off';
tuesday: DayWorkHours | 'off';
wednesday: DayWorkHours | 'off';
thursday: DayWorkHours | 'off';
friday: DayWorkHours | 'off';
saturday: DayWorkHours | 'off';
sunday: DayWorkHours | 'off';
monday: IDayWorkHours | 'off';
tuesday: IDayWorkHours | 'off';
wednesday: IDayWorkHours | 'off';
thursday: IDayWorkHours | 'off';
friday: IDayWorkHours | 'off';
saturday: IDayWorkHours | 'off';
sunday: IDayWorkHours | 'off';
};
dateOverrides: {
[dateString: string]: DayWorkHours | 'off'; // YYYY-MM-DD format
[dateString: string]: IDayWorkHours | 'off'; // YYYY-MM-DD format
};
}
@ -35,11 +35,11 @@ export interface WorkScheduleConfig {
*/
export class WorkHoursManager {
private dateService: DateService;
private config: CalendarConfig;
private config: Configuration;
private positionUtils: PositionUtils;
private workSchedule: WorkScheduleConfig;
private workSchedule: IWorkScheduleConfig;
constructor(dateService: DateService, config: CalendarConfig, positionUtils: PositionUtils) {
constructor(dateService: DateService, config: Configuration, positionUtils: PositionUtils) {
this.dateService = dateService;
this.config = config;
this.positionUtils = positionUtils;
@ -66,7 +66,7 @@ export class WorkHoursManager {
/**
* Get work hours for a specific date
*/
getWorkHoursForDate(date: Date): DayWorkHours | 'off' {
getWorkHoursForDate(date: Date): IDayWorkHours | 'off' {
const dateString = this.dateService.formatISODate(date);
// Check for date-specific override first
@ -82,8 +82,8 @@ export class WorkHoursManager {
/**
* Get work hours for multiple dates (used by GridManager)
*/
getWorkHoursForDateRange(dates: Date[]): Map<string, DayWorkHours | 'off'> {
const workHoursMap = new Map<string, DayWorkHours | 'off'>();
getWorkHoursForDateRange(dates: Date[]): Map<string, IDayWorkHours | 'off'> {
const workHoursMap = new Map<string, IDayWorkHours | 'off'>();
dates.forEach(date => {
const dateString = this.dateService.formatISODate(date);
@ -97,7 +97,7 @@ export class WorkHoursManager {
/**
* Calculate CSS custom properties for non-work hour overlays using PositionUtils
*/
calculateNonWorkHoursStyle(workHours: DayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
calculateNonWorkHoursStyle(workHours: IDayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
if (workHours === 'off') {
return null; // Full day will be colored via CSS background
}
@ -121,7 +121,7 @@ export class WorkHoursManager {
/**
* Calculate CSS custom properties for work hours overlay using PositionUtils
*/
calculateWorkHoursStyle(workHours: DayWorkHours | 'off'): { top: number; height: number } | null {
calculateWorkHoursStyle(workHours: IDayWorkHours | 'off'): { top: number; height: number } | null {
if (workHours === 'off') {
return null;
}
@ -139,22 +139,22 @@ export class WorkHoursManager {
/**
* Load work schedule from JSON (future implementation)
*/
async loadWorkSchedule(jsonData: WorkScheduleConfig): Promise<void> {
async loadWorkSchedule(jsonData: IWorkScheduleConfig): Promise<void> {
this.workSchedule = jsonData;
}
/**
* Get current work schedule configuration
*/
getWorkSchedule(): WorkScheduleConfig {
getWorkSchedule(): IWorkScheduleConfig {
return this.workSchedule;
}
/**
* Convert Date to day name key
*/
private getDayName(date: Date): keyof WorkScheduleConfig['weeklyDefault'] {
const dayNames: (keyof WorkScheduleConfig['weeklyDefault'])[] = [
private getDayName(date: Date): keyof IWorkScheduleConfig['weeklyDefault'] {
const dayNames: (keyof IWorkScheduleConfig['weeklyDefault'])[] = [
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'
];
return dayNames[date.getDay()];

View file

@ -1,9 +1,9 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { ICalendarEvent } from '../types/CalendarTypes';
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
import { EventLayout } from '../utils/AllDayLayoutEngine';
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
import { IEventLayout } from '../utils/AllDayLayoutEngine';
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
import { EventManager } from '../managers/EventManager';
import { DragStartEventPayload } from '../types/EventTypes';
import { IDragStartEventPayload } from '../types/EventTypes';
import { IEventRenderer } from './EventRenderer';
export class AllDayEventRenderer {
@ -38,7 +38,7 @@ export class AllDayEventRenderer {
/**
* Handle drag start for all-day events
*/
public handleDragStart(payload: DragStartEventPayload): void {
public handleDragStart(payload: IDragStartEventPayload): void {
this.originalEvent = payload.originalElement;;
this.draggedClone = payload.draggedClone;
@ -70,8 +70,8 @@ export class AllDayEventRenderer {
* Render an all-day event with pre-calculated layout
*/
private renderAllDayEventWithLayout(
event: CalendarEvent,
layout: EventLayout
event: ICalendarEvent,
layout: IEventLayout
) {
const container = this.getContainer();
if (!container) return null;
@ -109,7 +109,7 @@ export class AllDayEventRenderer {
/**
* Render all-day events for specific period using AllDayEventRenderer
*/
public renderAllDayEventsForPeriod(eventLayouts: EventLayout[]): void {
public renderAllDayEventsForPeriod(eventLayouts: IEventLayout[]): void {
this.clearAllDayEvents();
eventLayouts.forEach(layout => {

View file

@ -1,28 +1,28 @@
// Column rendering strategy interface and implementations
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { DateService } from '../utils/DateService';
import { WorkHoursManager } from '../managers/WorkHoursManager';
/**
* Interface for column rendering strategies
*/
export interface ColumnRenderer {
render(columnContainer: HTMLElement, context: ColumnRenderContext): void;
export interface IColumnRenderer {
render(columnContainer: HTMLElement, context: IColumnRenderContext): void;
}
/**
* Context for column rendering
*/
export interface ColumnRenderContext {
export interface IColumnRenderContext {
currentWeek: Date;
config: CalendarConfig;
config: Configuration;
}
/**
* Date-based column renderer (original functionality)
*/
export class DateColumnRenderer implements ColumnRenderer {
export class DateColumnRenderer implements IColumnRenderer {
private dateService: DateService;
private workHoursManager: WorkHoursManager;
@ -34,7 +34,7 @@ export class DateColumnRenderer implements ColumnRenderer {
this.workHoursManager = workHoursManager;
}
render(columnContainer: HTMLElement, context: ColumnRenderContext): void {
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
const { currentWeek, config } = context;
const workWeekSettings = config.getWorkWeekSettings();

View file

@ -1,22 +1,22 @@
// Header rendering strategy interface and implementations
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { DateService } from '../utils/DateService';
/**
* Interface for header rendering strategies
*/
export interface IHeaderRenderer {
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void;
}
/**
* Context for header rendering
*/
export interface HeaderRenderContext {
export interface IHeaderRenderContext {
currentWeek: Date;
config: CalendarConfig;
config: Configuration;
}
/**
@ -25,7 +25,7 @@ export interface HeaderRenderContext {
export class DateHeaderRenderer implements IHeaderRenderer {
private dateService!: DateService;
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void {
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void {
const { currentWeek, config } = context;
// FIRST: Always create all-day container as part of standard header structure

View file

@ -1,29 +1,29 @@
// Event rendering strategy interface and implementations
import { CalendarEvent } from '../types/CalendarTypes';
import { CalendarConfig } from '../core/CalendarConfig';
import { ICalendarEvent } from '../types/CalendarTypes';
import { Configuration } from '../configuration/CalendarConfig';
import { SwpEventElement } from '../elements/SwpEventElement';
import { PositionUtils } from '../utils/PositionUtils';
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
import { DragColumnChangeEventPayload, DragMoveEventPayload, DragStartEventPayload, DragMouseEnterColumnEventPayload } from '../types/EventTypes';
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, GridGroupLayout, StackedEventLayout } from '../managers/EventLayoutCoordinator';
import { EventLayoutCoordinator, IGridGroupLayout, IStackedEventLayout } from '../managers/EventLayoutCoordinator';
/**
* Interface for event rendering strategies
*/
export interface IEventRenderer {
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
renderEvents(events: ICalendarEvent[], container: HTMLElement): void;
clearEvents(container?: HTMLElement): void;
handleDragStart?(payload: DragStartEventPayload): void;
handleDragMove?(payload: DragMoveEventPayload): void;
handleDragStart?(payload: IDragStartEventPayload): void;
handleDragMove?(payload: IDragMoveEventPayload): void;
handleDragAutoScroll?(eventId: string, snappedY: number): void;
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void;
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void;
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
handleColumnChange?(payload: DragColumnChangeEventPayload): void;
handleColumnChange?(payload: IDragColumnChangeEventPayload): void;
handleNavigationCompleted?(): void;
handleConvertAllDayToTimed?(payload: DragMouseEnterColumnEventPayload): void;
handleConvertAllDayToTimed?(payload: IDragMouseEnterColumnEventPayload): void;
}
/**
@ -34,7 +34,7 @@ export class DateEventRenderer implements IEventRenderer {
private dateService: DateService;
private stackManager: EventStackManager;
private layoutCoordinator: EventLayoutCoordinator;
private config: CalendarConfig;
private config: Configuration;
private positionUtils: PositionUtils;
private draggedClone: HTMLElement | null = null;
private originalEvent: HTMLElement | null = null;
@ -43,7 +43,7 @@ export class DateEventRenderer implements IEventRenderer {
dateService: DateService,
stackManager: EventStackManager,
layoutCoordinator: EventLayoutCoordinator,
config: CalendarConfig,
config: Configuration,
positionUtils: PositionUtils
) {
this.dateService = dateService;
@ -63,7 +63,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle drag start event
*/
public handleDragStart(payload: DragStartEventPayload): void {
public handleDragStart(payload: IDragStartEventPayload): void {
this.originalEvent = payload.originalElement;;
@ -98,7 +98,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle drag move event
*/
public handleDragMove(payload: DragMoveEventPayload): void {
public handleDragMove(payload: IDragMoveEventPayload): void {
const swpEvent = payload.draggedClone as SwpEventElement;
const columnDate = this.dateService.parseISO(payload.columnBounds!!.date);
@ -108,7 +108,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle column change during drag
*/
public handleColumnChange(payload: DragColumnChangeEventPayload): void {
public handleColumnChange(payload: IDragColumnChangeEventPayload): void {
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
@ -125,7 +125,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle conversion of all-day event to timed event
*/
public handleConvertAllDayToTimed(payload: DragMouseEnterColumnEventPayload): void {
public handleConvertAllDayToTimed(payload: IDragMouseEnterColumnEventPayload): void {
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
eventId: payload.calendarEvent.id,
@ -165,7 +165,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle drag end event
*/
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void {
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void {
if (!draggedClone || !originalElement) {
console.warn('Missing draggedClone or originalElement');
return;
@ -209,7 +209,7 @@ export class DateEventRenderer implements IEventRenderer {
}
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
renderEvents(events: ICalendarEvent[], container: HTMLElement): void {
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events.filter(event => !event.allDay);
@ -229,7 +229,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Render events in a column using combined stacking + grid algorithm
*/
private renderColumnEvents(columnEvents: CalendarEvent[], eventsLayer: HTMLElement): void {
private renderColumnEvents(columnEvents: ICalendarEvent[], eventsLayer: HTMLElement): void {
if (columnEvents.length === 0) return;
// Get layout from coordinator
@ -251,7 +251,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Render events in a grid container (side-by-side with column sharing)
*/
private renderGridGroup(gridGroup: GridGroupLayout, eventsLayer: HTMLElement): void {
private renderGridGroup(gridGroup: IGridGroupLayout, eventsLayer: HTMLElement): void {
const groupElement = document.createElement('swp-event-group');
// Add grid column class based on number of columns (not events)
@ -275,7 +275,7 @@ export class DateEventRenderer implements IEventRenderer {
// Render each column
const earliestEvent = gridGroup.events[0];
gridGroup.columns.forEach(columnEvents => {
gridGroup.columns.forEach((columnEvents: ICalendarEvent[]) => {
const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start);
groupElement.appendChild(columnContainer);
});
@ -287,7 +287,7 @@ export class DateEventRenderer implements IEventRenderer {
* Render a single column within a grid group
* Column may contain multiple events that don't overlap
*/
private renderGridColumn(columnEvents: CalendarEvent[], containerStart: Date): HTMLElement {
private renderGridColumn(columnEvents: ICalendarEvent[], containerStart: Date): HTMLElement {
const columnContainer = document.createElement('div');
columnContainer.style.position = 'relative';
@ -302,7 +302,7 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Render event within a grid container (absolute positioning within column)
*/
private renderEventInGrid(event: CalendarEvent, containerStart: Date): HTMLElement {
private renderEventInGrid(event: ICalendarEvent, containerStart: Date): HTMLElement {
const element = SwpEventElement.fromCalendarEvent(event);
// Calculate event height
@ -326,7 +326,7 @@ export class DateEventRenderer implements IEventRenderer {
}
private renderEvent(event: CalendarEvent): HTMLElement {
private renderEvent(event: ICalendarEvent): HTMLElement {
const element = SwpEventElement.fromCalendarEvent(event);
// Apply positioning (moved from SwpEventElement.applyPositioning)
@ -340,7 +340,7 @@ export class DateEventRenderer implements IEventRenderer {
return element;
}
protected calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
protected calculateEventPosition(event: ICalendarEvent): { top: number; height: number } {
// Delegate to PositionUtils for centralized position calculation
return this.positionUtils.calculateEventPosition(event.start, event.end);
}
@ -366,7 +366,7 @@ export class DateEventRenderer implements IEventRenderer {
return Array.from(columns) as HTMLElement[];
}
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
protected getEventsForColumn(column: HTMLElement, events: ICalendarEvent[]): ICalendarEvent[] {
const columnDate = column.dataset.date;
if (!columnDate) {
return [];

View file

@ -1,12 +1,12 @@
import { EventBus } from '../core/EventBus';
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
import { IEventBus, ICalendarEvent, IRenderContext } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { EventManager } from '../managers/EventManager';
import { IEventRenderer } from './EventRenderer';
import { SwpEventElement } from '../elements/SwpEventElement';
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragMouseEnterColumnEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload, ResizeEndEventPayload } from '../types/EventTypes';
import { IDragStartEventPayload, IDragMoveEventPayload, IDragEndEventPayload, IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IDragMouseEnterColumnEventPayload, IDragColumnChangeEventPayload, IHeaderReadyEventPayload, IResizeEndEventPayload } from '../types/EventTypes';
import { DateService } from '../utils/DateService';
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
/**
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
* Håndterer event positioning og overlap detection
@ -36,7 +36,7 @@ export class EventRenderingService {
/**
* Render events in a specific container for a given period
*/
public renderEvents(context: RenderContext): void {
public renderEvents(context: IRenderContext): void {
// Clear existing events in the specific container first
this.strategy.clearEvents(context.container);
@ -133,7 +133,7 @@ export class EventRenderingService {
private setupDragStartListener(): void {
this.eventBus.on('drag:start', (event: Event) => {
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
const dragStartPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
return;
@ -147,7 +147,7 @@ export class EventRenderingService {
private setupDragMoveListener(): void {
this.eventBus.on('drag:move', (event: Event) => {
let dragEvent = (event as CustomEvent<DragMoveEventPayload>).detail;
let dragEvent = (event as CustomEvent<IDragMoveEventPayload>).detail;
if (dragEvent.draggedClone.hasAttribute('data-allday')) {
return;
@ -161,7 +161,7 @@ export class EventRenderingService {
private setupDragEndListener(): void {
this.eventBus.on('drag:end', (event: Event) => {
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<IDragEndEventPayload>).detail;
const finalColumn = finalPosition.column;
const finalY = finalPosition.snappedY;
const eventId = draggedElement.dataset.eventId || '';
@ -207,7 +207,7 @@ export class EventRenderingService {
private setupDragColumnChangeListener(): void {
this.eventBus.on('drag:column-change', (event: Event) => {
let columnChangeEvent = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
let columnChangeEvent = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
@ -223,7 +223,7 @@ export class EventRenderingService {
private setupDragMouseLeaveHeaderListener(): void {
this.dragMouseLeaveHeaderListener = (event: Event) => {
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
if (cloneElement)
cloneElement.style.display = '';
@ -241,7 +241,7 @@ export class EventRenderingService {
private setupDragMouseEnterColumnListener(): void {
this.eventBus.on('drag:mouseenter-column', (event: Event) => {
const payload = (event as CustomEvent<DragMouseEnterColumnEventPayload>).detail;
const payload = (event as CustomEvent<IDragMouseEnterColumnEventPayload>).detail;
// Only handle if clone is an all-day event
if (!payload.draggedClone.hasAttribute('data-allday')) {
@ -263,7 +263,7 @@ export class EventRenderingService {
private setupResizeEndListener(): void {
this.eventBus.on('resize:end', (event: Event) => {
const { eventId, element } = (event as CustomEvent<ResizeEndEventPayload>).detail;
const { eventId, element } = (event as CustomEvent<IResizeEndEventPayload>).detail;
// Update event data in EventManager with new end time from resized element
const swpEvent = element as SwpEventElement;
@ -306,7 +306,7 @@ export class EventRenderingService {
/**
* Re-render affected columns after drag to recalculate stacking/grouping
*/
private reRenderAffectedColumns(sourceColumn: ColumnBounds | null, targetColumn: ColumnBounds | null): void {
private reRenderAffectedColumns(sourceColumn: IColumnBounds | null, targetColumn: IColumnBounds | null): void {
const columnsToRender = new Set<string>();
// Add source column if exists

View file

@ -1,6 +1,6 @@
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
import { CalendarView } from '../types/CalendarTypes';
import { ColumnRenderer, ColumnRenderContext } from './ColumnRenderer';
import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
import { eventBus } from '../core/EventBus';
import { DateService } from '../utils/DateService';
import { CoreEvents } from '../constants/CoreEvents';
@ -82,13 +82,13 @@ export class GridRenderer {
private cachedGridContainer: HTMLElement | null = null;
private cachedTimeAxis: HTMLElement | null = null;
private dateService: DateService;
private columnRenderer: ColumnRenderer;
private config: CalendarConfig;
private columnRenderer: IColumnRenderer;
private config: Configuration;
constructor(
columnRenderer: ColumnRenderer,
columnRenderer: IColumnRenderer,
dateService: DateService,
config: CalendarConfig
config: Configuration
) {
this.dateService = dateService;
this.columnRenderer = columnRenderer;
@ -255,7 +255,7 @@ export class GridRenderer {
currentDate: Date,
view: CalendarView
): void {
const context: ColumnRenderContext = {
const context: IColumnRenderContext = {
currentWeek: currentDate, // ColumnRenderer expects currentWeek property
config: this.config
};

View file

@ -1,4 +1,4 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { ICalendarEvent } from '../types/CalendarTypes';
/**
* IEventRepository - Interface for event data loading
@ -13,8 +13,8 @@ import { CalendarEvent } from '../types/CalendarTypes';
export interface IEventRepository {
/**
* Load all calendar events from the data source
* @returns Promise resolving to array of CalendarEvent objects
* @returns Promise resolving to array of ICalendarEvent objects
* @throws Error if loading fails
*/
loadEvents(): Promise<CalendarEvent[]>;
loadEvents(): Promise<ICalendarEvent[]>;
}

View file

@ -1,4 +1,4 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { ICalendarEvent } from '../types/CalendarTypes';
import { IEventRepository } from './IEventRepository';
interface RawEventData {
@ -23,7 +23,7 @@ interface RawEventData {
export class MockEventRepository implements IEventRepository {
private readonly dataUrl = 'data/mock-events.json';
public async loadEvents(): Promise<CalendarEvent[]> {
public async loadEvents(): Promise<ICalendarEvent[]> {
try {
const response = await fetch(this.dataUrl);
@ -40,8 +40,8 @@ export class MockEventRepository implements IEventRepository {
}
}
private processCalendarData(data: RawEventData[]): CalendarEvent[] {
return data.map((event): CalendarEvent => ({
private processCalendarData(data: RawEventData[]): ICalendarEvent[] {
return data.map((event): ICalendarEvent => ({
...event,
start: new Date(event.start),
end: new Date(event.end),

View file

@ -8,13 +8,13 @@ export type CalendarView = ViewPeriod;
export type SyncStatus = 'synced' | 'pending' | 'error';
export interface RenderContext {
export interface IRenderContext {
container: HTMLElement;
startDate: Date;
endDate: Date;
}
export interface CalendarEvent {
export interface ICalendarEvent {
id: string;
title: string;
start: Date;
@ -55,13 +55,13 @@ export interface ICalendarConfig {
maxEventDuration: number; // Minutes
}
export interface EventLogEntry {
export interface IEventLogEntry {
type: string;
detail: unknown;
timestamp: number;
}
export interface ListenerEntry {
export interface IListenerEntry {
eventType: string;
handler: EventListener;
options?: AddEventListenerOptions;
@ -72,6 +72,6 @@ export interface IEventBus {
once(eventType: string, handler: EventListener): () => void;
off(eventType: string, handler: EventListener): void;
emit(eventType: string, detail?: unknown): boolean;
getEventLog(eventType?: string): EventLogEntry[];
getEventLog(eventType?: string): IEventLogEntry[];
setDebug(enabled: boolean): void;
}

View file

@ -2,46 +2,46 @@
* Type definitions for drag and drop functionality
*/
export interface MousePosition {
export interface IMousePosition {
x: number;
y: number;
clientX?: number;
clientY?: number;
}
export interface DragOffset {
export interface IDragOffset {
x: number;
y: number;
offsetX?: number;
offsetY?: number;
}
export interface DragState {
export interface IDragState {
isDragging: boolean;
draggedElement: HTMLElement | null;
draggedClone: HTMLElement | null;
eventId: string | null;
startColumn: string | null;
currentColumn: string | null;
mouseOffset: DragOffset;
mouseOffset: IDragOffset;
}
export interface DragEndPosition {
export interface IDragEndPosition {
column: string;
y: number;
snappedY: number;
time?: Date;
}
export interface StackLinkData {
export interface IStackLinkData {
prev?: string;
next?: string;
isFirst?: boolean;
isLast?: boolean;
}
export interface DragEventHandlers {
handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: DragOffset, column: string): void;
handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: DragOffset): void;
export interface IDragEventHandlers {
handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: IDragOffset, column: string): void;
handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: IDragOffset): void;
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void;
}

View file

@ -2,8 +2,8 @@
* Type definitions for calendar events and drag operations
*/
import { ColumnBounds } from "../utils/ColumnDetectionUtils";
import { CalendarEvent } from "./CalendarTypes";
import { IColumnBounds } from "../utils/ColumnDetectionUtils";
import { ICalendarEvent } from "./CalendarTypes";
/**
* Drag Event Payload Interfaces
@ -11,89 +11,89 @@ import { CalendarEvent } from "./CalendarTypes";
*/
// Common position interface
export interface MousePosition {
export interface IMousePosition {
x: number;
y: number;
}
// Drag start event payload
export interface DragStartEventPayload {
export interface IDragStartEventPayload {
originalElement: HTMLElement;
draggedClone: HTMLElement | null;
mousePosition: MousePosition;
mouseOffset: MousePosition;
columnBounds: ColumnBounds | null;
mousePosition: IMousePosition;
mouseOffset: IMousePosition;
columnBounds: IColumnBounds | null;
}
// Drag move event payload
export interface DragMoveEventPayload {
export interface IDragMoveEventPayload {
originalElement: HTMLElement;
draggedClone: HTMLElement;
mousePosition: MousePosition;
mouseOffset: MousePosition;
columnBounds: ColumnBounds | null;
mousePosition: IMousePosition;
mouseOffset: IMousePosition;
columnBounds: IColumnBounds | null;
snappedY: number;
}
// Drag end event payload
export interface DragEndEventPayload {
export interface IDragEndEventPayload {
originalElement: HTMLElement;
draggedClone: HTMLElement | null;
mousePosition: MousePosition;
sourceColumn: ColumnBounds;
mousePosition: IMousePosition;
sourceColumn: IColumnBounds;
finalPosition: {
column: ColumnBounds | null; // Where drag ended
column: IColumnBounds | null; // Where drag ended
snappedY: number;
};
target: 'swp-day-column' | 'swp-day-header' | null;
}
// Drag mouse enter header event payload
export interface DragMouseEnterHeaderEventPayload {
targetColumn: ColumnBounds;
mousePosition: MousePosition;
export interface IDragMouseEnterHeaderEventPayload {
targetColumn: IColumnBounds;
mousePosition: IMousePosition;
originalElement: HTMLElement | null;
draggedClone: HTMLElement;
calendarEvent: CalendarEvent;
calendarEvent: ICalendarEvent;
replaceClone: (newClone: HTMLElement) => void;
}
// Drag mouse leave header event payload
export interface DragMouseLeaveHeaderEventPayload {
export interface IDragMouseLeaveHeaderEventPayload {
targetDate: string | null;
mousePosition: MousePosition;
mousePosition: IMousePosition;
originalElement: HTMLElement| null;
draggedClone: HTMLElement| null;
}
// Drag mouse enter column event payload
export interface DragMouseEnterColumnEventPayload {
targetColumn: ColumnBounds;
mousePosition: MousePosition;
export interface IDragMouseEnterColumnEventPayload {
targetColumn: IColumnBounds;
mousePosition: IMousePosition;
snappedY: number;
originalElement: HTMLElement | null;
draggedClone: HTMLElement;
calendarEvent: CalendarEvent;
calendarEvent: ICalendarEvent;
replaceClone: (newClone: HTMLElement) => void;
}
// Drag column change event payload
export interface DragColumnChangeEventPayload {
export interface IDragColumnChangeEventPayload {
originalElement: HTMLElement;
draggedClone: HTMLElement;
previousColumn: ColumnBounds | null;
newColumn: ColumnBounds;
mousePosition: MousePosition;
previousColumn: IColumnBounds | null;
newColumn: IColumnBounds;
mousePosition: IMousePosition;
}
// Header ready event payload
export interface HeaderReadyEventPayload {
headerElements: ColumnBounds[];
export interface IHeaderReadyEventPayload {
headerElements: IColumnBounds[];
}
// Resize end event payload
export interface ResizeEndEventPayload {
export interface IResizeEndEventPayload {
eventId: string;
element: HTMLElement;
finalHeight: number;

View file

@ -1,19 +1,19 @@
import { IEventBus, CalendarEvent, CalendarView } from './CalendarTypes';
import { IEventBus, ICalendarEvent, CalendarView } from './CalendarTypes';
/**
* Complete type definition for all managers returned by ManagerFactory
*/
export interface CalendarManagers {
eventManager: EventManager;
eventRenderer: EventRenderingService;
gridManager: GridManager;
scrollManager: ScrollManager;
export interface ICalendarManagers {
eventManager: IEventManager;
eventRenderer: IEventRenderingService;
gridManager: IGridManager;
scrollManager: IScrollManager;
navigationManager: unknown; // Avoid interface conflicts
viewManager: ViewManager;
calendarManager: CalendarManager;
viewManager: IViewManager;
calendarManager: ICalendarManager;
dragDropManager: unknown; // Avoid interface conflicts
allDayManager: unknown; // Avoid interface conflicts
resizeHandleManager: ResizeHandleManager;
resizeHandleManager: IResizeHandleManager;
edgeScrollManager: unknown; // Avoid interface conflicts
dragHoverManager: unknown; // Avoid interface conflicts
headerManager: unknown; // Avoid interface conflicts
@ -27,50 +27,50 @@ interface IManager {
refresh?(): void;
}
export interface EventManager extends IManager {
export interface IEventManager extends IManager {
loadData(): Promise<void>;
getEvents(): CalendarEvent[];
getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[];
getEvents(): ICalendarEvent[];
getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[];
navigateToEvent(eventId: string): boolean;
}
export interface EventRenderingService extends IManager {
export interface IEventRenderingService extends IManager {
// EventRenderingService doesn't have a render method in current implementation
}
export interface GridManager extends IManager {
export interface IGridManager extends IManager {
render(): Promise<void>;
getDisplayDates(): Date[];
}
export interface ScrollManager extends IManager {
export interface IScrollManager extends IManager {
scrollTo(scrollTop: number): void;
scrollToHour(hour: number): void;
}
// Use a more flexible interface that matches actual implementation
export interface NavigationManager extends IManager {
export interface INavigationManager extends IManager {
[key: string]: unknown; // Allow any properties from actual implementation
}
export interface ViewManager extends IManager {
export interface IViewManager extends IManager {
// ViewManager doesn't have setView in current implementation
getCurrentView?(): CalendarView;
}
export interface CalendarManager extends IManager {
export interface ICalendarManager extends IManager {
setView(view: CalendarView): void;
setCurrentDate(date: Date): void;
}
export interface DragDropManager extends IManager {
export interface IDragDropManager extends IManager {
// DragDropManager has different interface in current implementation
}
export interface AllDayManager extends IManager {
export interface IAllDayManager extends IManager {
[key: string]: unknown; // Allow any properties from actual implementation
}
export interface ResizeHandleManager extends IManager {
export interface IResizeHandleManager extends IManager {
// ResizeHandleManager handles hover effects for resize handles
}

View file

@ -1,7 +1,7 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { ICalendarEvent } from '../types/CalendarTypes';
export interface EventLayout {
calenderEvent: CalendarEvent;
export interface IEventLayout {
calenderEvent: ICalendarEvent;
gridArea: string; // "row-start / col-start / row-end / col-end"
startColumn: number;
endColumn: number;
@ -21,9 +21,9 @@ export class AllDayLayoutEngine {
/**
* Calculate layout for all events using clean day-based logic
*/
public calculateLayout(events: CalendarEvent[]): EventLayout[] {
public calculateLayout(events: ICalendarEvent[]): IEventLayout[] {
let layouts: EventLayout[] = [];
let layouts: IEventLayout[] = [];
// Reset tracks for new calculation
this.tracks = [new Array(this.weekDates.length).fill(false)];
@ -43,7 +43,7 @@ export class AllDayLayoutEngine {
this.tracks[track][day] = true;
}
const layout: EventLayout = {
const layout: IEventLayout = {
calenderEvent: event,
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
startColumn: startDay,
@ -89,7 +89,7 @@ export class AllDayLayoutEngine {
/**
* Get start day index for event (1-based, 0 if not visible)
*/
private getEventStartDay(event: CalendarEvent): number {
private getEventStartDay(event: ICalendarEvent): number {
const eventStartDate = this.formatDate(event.start);
const firstVisibleDate = this.weekDates[0];
@ -103,7 +103,7 @@ export class AllDayLayoutEngine {
/**
* Get end day index for event (1-based, 0 if not visible)
*/
private getEventEndDay(event: CalendarEvent): number {
private getEventEndDay(event: ICalendarEvent): number {
const eventEndDate = this.formatDate(event.end);
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
@ -117,7 +117,7 @@ export class AllDayLayoutEngine {
/**
* Check if event is visible in the current date range
*/
private isEventVisible(event: CalendarEvent): boolean {
private isEventVisible(event: ICalendarEvent): boolean {
if (this.weekDates.length === 0) return false;
const eventStartDate = this.formatDate(event.start);

View file

@ -3,10 +3,10 @@
* Used by both DragDropManager and AllDayManager for consistent column detection
*/
import { MousePosition } from "../types/DragDropTypes";
import { IMousePosition } from "../types/DragDropTypes";
export interface ColumnBounds {
export interface IColumnBounds {
date: string;
left: number;
right: number;
@ -16,7 +16,7 @@ export interface ColumnBounds {
}
export class ColumnDetectionUtils {
private static columnBoundsCache: ColumnBounds[] = [];
private static columnBoundsCache: IColumnBounds[] = [];
/**
* Update column bounds cache for coordinate-based column detection
@ -52,7 +52,7 @@ export class ColumnDetectionUtils {
/**
* Get column date from X coordinate using cached bounds
*/
public static getColumnBounds(position: MousePosition): ColumnBounds | null{
public static getColumnBounds(position: IMousePosition): IColumnBounds | null{
if (this.columnBoundsCache.length === 0) {
this.updateColumnBoundsCache();
}
@ -70,7 +70,7 @@ export class ColumnDetectionUtils {
/**
* Get column bounds by Date
*/
public static getColumnBoundsByDate(date: Date): ColumnBounds | null {
public static getColumnBoundsByDate(date: Date): IColumnBounds | null {
if (this.columnBoundsCache.length === 0) {
this.updateColumnBoundsCache();
}
@ -84,12 +84,12 @@ export class ColumnDetectionUtils {
}
public static getColumns(): ColumnBounds[] {
public static getColumns(): IColumnBounds[] {
return [...this.columnBoundsCache];
}
public static getHeaderColumns(): ColumnBounds[] {
public static getHeaderColumns(): IColumnBounds[] {
let dayHeaders: ColumnBounds[] = [];
let dayHeaders: IColumnBounds[] = [];
const dayColumns = document.querySelectorAll('swp-calendar-header swp-day-header');
let index = 1;

View file

@ -29,12 +29,12 @@ import {
fromZonedTime,
formatInTimeZone
} from 'date-fns-tz';
import { CalendarConfig } from '../core/CalendarConfig';
import { Configuration } from '../configuration/CalendarConfig';
export class DateService {
private timezone: string;
constructor(config: CalendarConfig) {
constructor(config: Configuration) {
this.timezone = config.getTimezone();
}

View file

@ -1,5 +1,5 @@
import { CalendarConfig } from '../core/CalendarConfig';
import { ColumnBounds } from './ColumnDetectionUtils';
import { Configuration } from '../configuration/CalendarConfig';
import { IColumnBounds } from './ColumnDetectionUtils';
import { DateService } from './DateService';
import { TimeFormatter } from './TimeFormatter';
@ -11,9 +11,9 @@ import { TimeFormatter } from './TimeFormatter';
*/
export class PositionUtils {
private dateService: DateService;
private config: CalendarConfig;
private config: Configuration;
constructor(dateService: DateService, config: CalendarConfig) {
constructor(dateService: DateService, config: Configuration) {
this.dateService = dateService;
this.config = config;
}
@ -169,7 +169,7 @@ export class PositionUtils {
/**
* Beregn Y position fra mouse/touch koordinat
*/
public getPositionFromCoordinate(clientY: number, column: ColumnBounds): number {
public getPositionFromCoordinate(clientY: number, column: IColumnBounds): number {
const relativeY = clientY - column.boundingClientRect.top;

View file

@ -10,7 +10,7 @@
import { DateService } from './DateService';
export interface TimeFormatSettings {
export interface ITimeFormatSettings {
timezone: string;
use24HourFormat: boolean;
locale: string;
@ -19,7 +19,7 @@ export interface TimeFormatSettings {
}
export class TimeFormatter {
private static settings: TimeFormatSettings = {
private static settings: ITimeFormatSettings = {
timezone: 'Europe/Copenhagen', // Default to Denmark
use24HourFormat: true, // 24-hour format standard in Denmark
locale: 'da-DK', // Danish locale
@ -44,7 +44,7 @@ export class TimeFormatter {
/**
* Configure time formatting settings
*/
static configure(settings: Partial<TimeFormatSettings>): void {
static configure(settings: Partial<ITimeFormatSettings>): void {
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
// Reset DateService to pick up new timezone
TimeFormatter.dateService = null;

View file

@ -0,0 +1,87 @@
{
"gridSettings": {
"hourHeight": 80,
"dayStartHour": 6,
"dayEndHour": 22,
"workStartHour": 8,
"workEndHour": 17,
"snapInterval": 15,
"gridStartThresholdMinutes": 30,
"showCurrentTime": true,
"showWorkHours": true,
"fitToWidth": false,
"scrollToHour": 8
},
"dateViewSettings": {
"period": "week",
"weekDays": 7,
"firstDayOfWeek": 1,
"showAllDay": true
},
"timeFormatConfig": {
"timezone": "Europe/Copenhagen",
"use24HourFormat": true,
"locale": "da-DK",
"dateFormat": "technical",
"showSeconds": false
},
"workWeekPresets": {
"standard": {
"id": "standard",
"workDays": [1, 2, 3, 4, 5],
"totalDays": 5,
"firstWorkDay": 1
},
"compressed": {
"id": "compressed",
"workDays": [1, 2, 3, 4],
"totalDays": 4,
"firstWorkDay": 1
},
"midweek": {
"id": "midweek",
"workDays": [3, 4, 5],
"totalDays": 3,
"firstWorkDay": 3
},
"weekend": {
"id": "weekend",
"workDays": [6, 7],
"totalDays": 2,
"firstWorkDay": 6
},
"fullweek": {
"id": "fullweek",
"workDays": [1, 2, 3, 4, 5, 6, 7],
"totalDays": 7,
"firstWorkDay": 1
}
},
"currentWorkWeek": "standard",
"scrollbar": {
"width": 16,
"color": "#666",
"trackColor": "#f0f0f0",
"hoverColor": "#b53f7aff",
"borderRadius": 6
},
"interaction": {
"allowDrag": true,
"allowResize": true,
"allowCreate": true
},
"api": {
"endpoint": "/api/events",
"dateFormat": "YYYY-MM-DD",
"timeFormat": "HH:mm"
},
"features": {
"enableSearch": true,
"enableTouch": true
},
"eventDefaults": {
"defaultEventDuration": 60,
"minEventDuration": 15,
"maxEventDuration": 480
}
}

View file

@ -15,7 +15,7 @@
</head>
<body>
<div class="calendar-wrapper">
<swp-calendar data-view="week" data-week-days="7" data-snap-interval="15" data-day-start-hour="6" data-day-end-hour="22" data-hour-height="80">
<swp-calendar>
<!-- Navigation Bar -->
<swp-calendar-nav>
<swp-nav-group>