kalendly 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,12 +51,28 @@ function generateYears(minYear, maxYear) {
51
51
  const max = maxYear ?? currentYear + 10;
52
52
  return Array.from({ length: max - min + 1 }, (_, i) => min + i);
53
53
  }
54
+ function eventCoversDate(event, date) {
55
+ const start = normalizeDate(new Date(event.date)).getTime();
56
+ const target = normalizeDate(date).getTime();
57
+ if (Number.isNaN(start)) return false;
58
+ if (event.endDate === void 0 || event.endDate === null) {
59
+ return start === target;
60
+ }
61
+ const end = normalizeDate(new Date(event.endDate)).getTime();
62
+ if (Number.isNaN(end)) {
63
+ throw new Error(
64
+ `<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
65
+ );
66
+ }
67
+ if (end < start) {
68
+ throw new Error(
69
+ `<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
70
+ );
71
+ }
72
+ return target >= start && target <= end;
73
+ }
54
74
  function getEventsForDate(events, date) {
55
- const normalizedTargetDate = normalizeDate(date);
56
- return events.filter((event) => {
57
- const eventDate = normalizeDate(new Date(event.date));
58
- return eventDate.getTime() === normalizedTargetDate.getTime();
59
- });
75
+ return events.filter((event) => eventCoversDate(event, date));
60
76
  }
61
77
  function hasEvents(events, date) {
62
78
  return getEventsForDate(events, date).length > 0;
@@ -215,6 +231,68 @@ function safeColor(value) {
215
231
  const trimmed = String(value).trim();
216
232
  return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
217
233
  }
234
+ var MINUTES_PER_DAY = 1440;
235
+ var DEFAULT_SLOT_DURATION = 60;
236
+ function parseTimeToMinutes(time) {
237
+ if (typeof time !== "string") return null;
238
+ const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
239
+ if (!match) return null;
240
+ const hours = Number(match[1]);
241
+ const mins = Number(match[2]);
242
+ if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
243
+ return hours * 60 + mins;
244
+ }
245
+ function formatMinutes(total) {
246
+ const hours = Math.floor(total / 60);
247
+ const mins = total % 60;
248
+ return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
249
+ }
250
+ function eventInterval(event, slotDuration, onDay) {
251
+ const day = normalizeDate(onDay).getTime();
252
+ if (Number.isNaN(day)) return null;
253
+ const base = day / 6e4;
254
+ const wholeDay = [base, base + MINUTES_PER_DAY];
255
+ if (event.allDay || !event.startTime) return wholeDay;
256
+ const from = parseTimeToMinutes(event.startTime);
257
+ if (from === null) return wholeDay;
258
+ if (event.endTime === void 0 || event.endTime === null) {
259
+ return [base + from, base + from + slotDuration];
260
+ }
261
+ const to = parseTimeToMinutes(event.endTime);
262
+ if (to === null) return wholeDay;
263
+ return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
264
+ }
265
+ function mergeIntervals(intervals) {
266
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
267
+ return sorted.reduce((merged, [start, end]) => {
268
+ const last = merged[merged.length - 1];
269
+ if (last && start <= last[1]) last[1] = Math.max(last[1], end);
270
+ else merged.push([start, end]);
271
+ return merged;
272
+ }, []);
273
+ }
274
+ function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
275
+ const base = normalizeDate(date).getTime() / 6e4;
276
+ const previousDay = new Date(date);
277
+ previousDay.setDate(previousDay.getDate() - 1);
278
+ const intervals = [];
279
+ for (const event of new Set(events)) {
280
+ for (const day of [previousDay, date]) {
281
+ if (!eventCoversDate(event, day)) continue;
282
+ const interval = eventInterval(event, slotDuration, day);
283
+ if (interval) intervals.push(interval);
284
+ }
285
+ }
286
+ const merged = mergeIntervals(intervals);
287
+ return Array.from(
288
+ { length: Math.floor(MINUTES_PER_DAY / slotDuration) },
289
+ (_, index) => {
290
+ const start = base + index * slotDuration;
291
+ const end = start + slotDuration;
292
+ return merged.some(([from, to]) => start < to && end > from);
293
+ }
294
+ );
295
+ }
218
296
 
219
297
  // src/core/calendar-engine.ts
220
298
  var CalendarEngine = class {
@@ -461,11 +539,17 @@ export {
461
539
  CalendarEngine,
462
540
  DAYS,
463
541
  DEFAULT_CATEGORY_COLORS,
542
+ DEFAULT_SLOT_DURATION,
543
+ MINUTES_PER_DAY,
464
544
  MONTHS,
465
545
  MONTHS_FULL,
546
+ bookedSlots,
466
547
  escapeHtml,
548
+ eventCoversDate,
549
+ eventInterval,
467
550
  formatAttendees,
468
551
  formatDateForDisplay,
552
+ formatMinutes,
469
553
  formatTimeRange,
470
554
  generateCalendarDates,
471
555
  generateYears,
@@ -479,7 +563,9 @@ export {
479
563
  isToday,
480
564
  isValidHexColor,
481
565
  mergeCategoryColors,
566
+ mergeIntervals,
482
567
  normalizeDate,
568
+ parseTimeToMinutes,
483
569
  safeColor,
484
570
  safeUrl,
485
571
  slugifyToken,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/utils.ts","../../src/core/calendar-engine.ts"],"sourcesContent":["import { CalendarEvent, CalendarDate, CategoryColorMap } from './types';\n\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport const MONTHS_FULL = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const DAYS = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n];\n\nexport function normalizeDate(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);\n}\n\nexport function isSameDay(date1: Date, date2: Date): boolean {\n return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();\n}\n\nexport function isToday(date: Date): boolean {\n return isSameDay(date, new Date());\n}\n\nexport function generateYears(minYear?: number, maxYear?: number): number[] {\n const currentYear = new Date().getFullYear();\n const min = minYear ?? currentYear - 30;\n const max = maxYear ?? currentYear + 10;\n\n return Array.from({ length: max - min + 1 }, (_, i) => min + i);\n}\n\nexport function getEventsForDate(\n events: CalendarEvent[],\n date: Date\n): CalendarEvent[] {\n const normalizedTargetDate = normalizeDate(date);\n\n return events.filter(event => {\n const eventDate = normalizeDate(new Date(event.date));\n return eventDate.getTime() === normalizedTargetDate.getTime();\n });\n}\n\nexport function hasEvents(events: CalendarEvent[], date: Date): boolean {\n return getEventsForDate(events, date).length > 0;\n}\n\nexport function generateCalendarDates(\n year: number,\n month: number,\n events: CalendarEvent[] = [],\n weekStartsOn: 0 | 1 = 0\n): CalendarDate[][] {\n const firstDay = new Date(year, month, 1);\n const lastDay = new Date(year, month + 1, 0);\n const daysInMonth = lastDay.getDate();\n const prevMonthLastDay = new Date(year, month, 0).getDate();\n\n let firstDayOfWeek = firstDay.getDay();\n if (weekStartsOn === 1) {\n firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;\n }\n\n const dates: CalendarDate[][] = [];\n let day = 1;\n let nextMonthDay = 1;\n\n for (let week = 0; week < 6; week++) {\n const weekDates: CalendarDate[] = [];\n\n for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {\n if (week === 0 && dayOfWeek < firstDayOfWeek) {\n // Previous month days\n const prevDay = prevMonthLastDay - firstDayOfWeek + dayOfWeek + 1;\n const prevDate = new Date(year, month - 1, prevDay);\n const dateEvents = getEventsForDate(events, prevDate);\n weekDates.push({\n date: prevDate,\n isCurrentMonth: false,\n isToday: isToday(prevDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n } else if (day > daysInMonth) {\n // Next month days\n const nextDate = new Date(year, month + 1, nextMonthDay);\n const dateEvents = getEventsForDate(events, nextDate);\n weekDates.push({\n date: nextDate,\n isCurrentMonth: false,\n isToday: isToday(nextDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n nextMonthDay++;\n } else {\n // Current month days\n const currentDate = new Date(year, month, day);\n const dateEvents = getEventsForDate(events, currentDate);\n weekDates.push({\n date: currentDate,\n isCurrentMonth: true,\n isToday: isToday(currentDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n day++;\n }\n }\n\n dates.push(weekDates);\n }\n\n return dates;\n}\n\nexport function getCellClasses(calendarDate: CalendarDate): string[] {\n const classes: string[] = [];\n\n if (!calendarDate.isCurrentMonth) {\n classes.push('calendar-cell-other-month');\n }\n\n if (calendarDate.isToday) {\n classes.push('calendar-cell-today');\n }\n\n if (calendarDate.hasEvents) {\n classes.push('calendar-cell-has-event');\n }\n\n return classes;\n}\n\nexport function formatDateForDisplay(date: Date): string {\n return `${DAYS[date.getDay()]} ${date.getDate()}`;\n}\n\nexport function getMonthYearText(year: number, month: number): string {\n return `${MONTHS_FULL[month]} ${year}`;\n}\n\nexport function formatTimeRange(event: CalendarEvent): string {\n if (event.allDay || (!event.startTime && !event.endTime)) {\n return 'All day';\n }\n\n if (event.startTime && event.endTime) {\n return `${event.startTime} - ${event.endTime}`;\n }\n\n if (event.startTime) {\n return `${event.startTime}`;\n }\n\n return '';\n}\n\nexport function formatAttendees(attendees?: string[]): string {\n if (!attendees || attendees.length === 0) return '';\n\n if (attendees.length === 1) return attendees[0];\n if (attendees.length === 2) return attendees.join(' and ');\n\n return `${attendees.slice(0, -1).join(', ')}, and ${attendees[attendees.length - 1]}`;\n}\n\nexport function sortEventsByTime(events: CalendarEvent[]): CalendarEvent[] {\n return [...events].sort((a, b) => {\n const aAllDay = a.allDay || (!a.startTime && !a.endTime);\n const bAllDay = b.allDay || (!b.startTime && !b.endTime);\n\n if (aAllDay && !bAllDay) return -1;\n if (!aAllDay && bAllDay) return 1;\n\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.localeCompare(b.startTime);\n });\n}\n\nexport const DEFAULT_CATEGORY_COLORS: CategoryColorMap = {\n work: '#3b82f6',\n personal: '#8b5cf6',\n meeting: '#10b981',\n deadline: '#ef4444',\n appointment: '#f59e0b',\n other: '#6b7280',\n};\n\n// Dynamic function that accepts custom colors\nexport function getDefaultEventColor(\n category?: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = customColors || DEFAULT_CATEGORY_COLORS;\n\n if (category && colorMap[category]) {\n return colorMap[category];\n }\n\n // Default fallback color\n return '#fc8917';\n}\n\n// Helper to merge custom colors with defaults\nexport function mergeCategoryColors(\n customColors?: CategoryColorMap\n): CategoryColorMap {\n return {\n ...DEFAULT_CATEGORY_COLORS,\n ...customColors,\n };\n}\n\n// Validate if a color is a valid hex color\nexport function isValidHexColor(color: string): boolean {\n return /^#([0-9A-F]{3}){1,2}$/i.test(color);\n}\n\n// Get color for a category with validation\nexport function getCategoryColor(\n category: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = mergeCategoryColors(customColors);\n const color = colorMap[category] || colorMap.other || '#fc8917';\n\n return isValidHexColor(color) ? color : '#fc8917';\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\n// Escape a value for interpolation into HTML text or a quoted attribute\nexport function escapeHtml(value: unknown): string {\n if (value === null || value === undefined) return '';\n\n return String(value).replace(/[&<>\"']/g, char => HTML_ESCAPES[char]);\n}\n\n// Reduce a caller-supplied value to a single safe CSS class token\nexport function slugifyToken(value: string): string {\n return String(value)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n// Allow http(s), mailto and relative URLs; anything else (javascript:, data:,\n// vbscript:) collapses to a harmless anchor\nexport function safeUrl(value: string): string {\n // Browsers ignore control characters when resolving a scheme, so\n // `java\\0script:` and `java\\tscript:` both navigate. Strip them before\n // testing rather than after.\n const normalized = String(value)\n .split('')\n .filter(char => char.charCodeAt(0) > 0x20)\n .join('');\n\n if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;\n\n // Relative URLs have no scheme at all — a colon before the first slash,\n // question mark or hash means someone is naming one\n const schemeless = !/^[^/?#]*:/.test(normalized);\n\n return schemeless ? normalized : '#';\n}\n\n// Accept hex colors and bare CSS color keywords; reject anything that could\n// terminate the declaration or escape the style attribute\nexport function safeColor(value: string): string {\n const trimmed = String(value).trim();\n\n return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed)\n ? trimmed\n : '#3b82f6';\n}\n","import {\n CalendarEvent,\n CalendarState,\n CalendarConfig,\n CalendarActions,\n CalendarViewModel,\n CalendarPane,\n CategoryColorMap,\n} from './types';\nimport {\n generateCalendarDates,\n getEventsForDate,\n generateYears,\n getMonthYearText,\n formatDateForDisplay,\n mergeCategoryColors,\n getCategoryColor,\n MONTHS,\n DAYS,\n} from './utils';\n\nexport class CalendarEngine {\n private state: CalendarState;\n private config: CalendarConfig;\n private listeners: Set<() => void> = new Set();\n private categoryColors: CategoryColorMap;\n\n constructor(config: CalendarConfig) {\n this.config = config;\n this.categoryColors = mergeCategoryColors(config.categoryColors);\n\n const initialDate = config.initialDate || new Date();\n this.state = {\n currentYear: initialDate.getFullYear(),\n currentMonth: initialDate.getMonth(),\n currentDate: initialDate.getDate(),\n selectedDate: null,\n selectedDayIndex: null,\n tasks: [],\n };\n }\n\n /**\n * Subscribe to state changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Notify all listeners of state changes\n */\n private notify(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Get current state\n */\n getState(): CalendarState {\n return { ...this.state };\n }\n\n /**\n * Get view model with computed properties\n */\n getViewModel(): CalendarViewModel {\n const panes: CalendarPane[] = Array.from(\n { length: Math.max(1, this.config.monthCount ?? 1) },\n (_, offset) => {\n const anchor = new Date(\n this.state.currentYear,\n this.state.currentMonth + offset,\n 1\n );\n const year = anchor.getFullYear();\n const month = anchor.getMonth();\n\n return {\n year,\n month,\n monthAndYearText: getMonthYearText(year, month),\n calendarDates: generateCalendarDates(\n year,\n month,\n this.config.events,\n this.config.weekStartsOn\n ),\n };\n }\n );\n\n return {\n ...this.state,\n months: MONTHS,\n days: DAYS,\n years: generateYears(this.config.minYear, this.config.maxYear),\n monthAndYearText: panes[0].monthAndYearText,\n scheduleDay: this.state.selectedDate\n ? formatDateForDisplay(this.state.selectedDate)\n : '',\n panes,\n calendarDates: panes[0].calendarDates,\n };\n }\n\n /**\n * Get actions object\n */\n getActions(): CalendarActions {\n return {\n next: this.next.bind(this),\n previous: this.previous.bind(this),\n jump: this.jump.bind(this),\n goToToday: this.goToToday.bind(this),\n isCurrentMonth: this.isCurrentMonth.bind(this),\n selectDate: this.selectDate.bind(this),\n updateTasks: this.updateTasks.bind(this),\n };\n }\n\n /**\n * Navigate to next month\n */\n private next(): void {\n if (this.state.currentMonth === 11) {\n this.state.currentMonth = 0;\n this.state.currentYear++;\n } else {\n this.state.currentMonth++;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to previous month\n */\n private previous(): void {\n if (this.state.currentMonth === 0) {\n this.state.currentMonth = 11;\n this.state.currentYear--;\n } else {\n this.state.currentMonth--;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Jump to specific month and year\n */\n private jump(year: number, month: number): void {\n this.state.currentYear = year;\n this.state.currentMonth = month;\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to current month (today)\n */\n private goToToday(): void {\n const today = new Date();\n this.state.currentYear = today.getFullYear();\n this.state.currentMonth = today.getMonth();\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Check if currently viewing today's month\n */\n private isCurrentMonth(): boolean {\n const today = new Date();\n return (\n this.state.currentYear === today.getFullYear() &&\n this.state.currentMonth === today.getMonth()\n );\n }\n\n /**\n * Select a specific date\n */\n private selectDate(date: Date, dayIndex?: number): void {\n this.state.selectedDate = date;\n this.state.selectedDayIndex = dayIndex ?? null;\n this.state.currentDate = date.getDate();\n this.state.currentMonth = date.getMonth();\n this.state.currentYear = date.getFullYear();\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Update tasks for the currently selected date\n */\n private updateTasks(): void {\n if (!this.state.selectedDate) {\n this.state.tasks = [];\n return;\n }\n\n this.state.tasks = getEventsForDate(\n this.config.events,\n this.state.selectedDate\n );\n }\n\n /**\n * Update events configuration\n */\n updateEvents(events: CalendarEvent[]): void {\n this.config.events = events;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Handle date cell click\n */\n handleDateClick(date: Date, dayIndex?: number): void {\n this.selectDate(date, dayIndex);\n }\n\n /**\n * Check if date has events\n */\n hasEventsForDate(date: Date): boolean {\n return getEventsForDate(this.config.events, date).length > 0;\n }\n\n /**\n * Get events for a specific date\n */\n getEventsForDate(date: Date): CalendarEvent[] {\n return getEventsForDate(this.config.events, date);\n }\n\n /**\n * Clear selected date\n */\n clearSelection(): void {\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.state.tasks = [];\n this.notify();\n }\n\n /**\n * Get category color (with custom colors support)\n */\n getCategoryColor(category?: string): string {\n if (!category) return '#fc8917';\n return getCategoryColor(category, this.categoryColors);\n }\n\n /**\n * Update category colors dynamically\n */\n updateCategoryColors(colors: CategoryColorMap): void {\n this.categoryColors = mergeCategoryColors(colors);\n this.notify();\n }\n\n /**\n * Get all category colors\n */\n getCategoryColors(): CategoryColorMap {\n return { ...this.categoryColors };\n }\n\n /**\n * Register a new category with color\n */\n registerCategory(name: string, color: string): void {\n this.categoryColors[name] = color;\n this.notify();\n }\n\n /**\n * Destroy the engine and cleanup listeners\n */\n destroy(): void {\n this.listeners.clear();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/core/utils.ts","../../src/core/calendar-engine.ts"],"sourcesContent":["import { CalendarEvent, CalendarDate, CategoryColorMap } from './types';\n\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport const MONTHS_FULL = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const DAYS = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n];\n\nexport function normalizeDate(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);\n}\n\nexport function isSameDay(date1: Date, date2: Date): boolean {\n return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();\n}\n\nexport function isToday(date: Date): boolean {\n return isSameDay(date, new Date());\n}\n\nexport function generateYears(minYear?: number, maxYear?: number): number[] {\n const currentYear = new Date().getFullYear();\n const min = minYear ?? currentYear - 30;\n const max = maxYear ?? currentYear + 10;\n\n return Array.from({ length: max - min + 1 }, (_, i) => min + i);\n}\n\n// endDate is inclusive, matching the endDate cal-availability-select emits, so\n// a selection can be handed straight back as one event.\nexport function eventCoversDate(event: CalendarEvent, date: Date): boolean {\n const start = normalizeDate(new Date(event.date)).getTime();\n const target = normalizeDate(date).getTime();\n if (Number.isNaN(start)) return false;\n\n if (event.endDate === undefined || event.endDate === null) {\n return start === target;\n }\n\n const end = normalizeDate(new Date(event.endDate)).getTime();\n if (Number.isNaN(end)) {\n throw new Error(\n `<kal-calendar> event ${event.id} has an unreadable endDate: ` +\n `${String(event.endDate)}.`\n );\n }\n if (end < start) {\n throw new Error(\n `<kal-calendar> event ${event.id} has an endDate before its date: ` +\n `${String(event.endDate)} < ${String(event.date)}.`\n );\n }\n\n return target >= start && target <= end;\n}\n\nexport function getEventsForDate(\n events: CalendarEvent[],\n date: Date\n): CalendarEvent[] {\n return events.filter(event => eventCoversDate(event, date));\n}\n\nexport function hasEvents(events: CalendarEvent[], date: Date): boolean {\n return getEventsForDate(events, date).length > 0;\n}\n\nexport function generateCalendarDates(\n year: number,\n month: number,\n events: CalendarEvent[] = [],\n weekStartsOn: 0 | 1 = 0\n): CalendarDate[][] {\n const firstDay = new Date(year, month, 1);\n const lastDay = new Date(year, month + 1, 0);\n const daysInMonth = lastDay.getDate();\n const prevMonthLastDay = new Date(year, month, 0).getDate();\n\n let firstDayOfWeek = firstDay.getDay();\n if (weekStartsOn === 1) {\n firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;\n }\n\n const dates: CalendarDate[][] = [];\n let day = 1;\n let nextMonthDay = 1;\n\n for (let week = 0; week < 6; week++) {\n const weekDates: CalendarDate[] = [];\n\n for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {\n if (week === 0 && dayOfWeek < firstDayOfWeek) {\n // Previous month days\n const prevDay = prevMonthLastDay - firstDayOfWeek + dayOfWeek + 1;\n const prevDate = new Date(year, month - 1, prevDay);\n const dateEvents = getEventsForDate(events, prevDate);\n weekDates.push({\n date: prevDate,\n isCurrentMonth: false,\n isToday: isToday(prevDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n } else if (day > daysInMonth) {\n // Next month days\n const nextDate = new Date(year, month + 1, nextMonthDay);\n const dateEvents = getEventsForDate(events, nextDate);\n weekDates.push({\n date: nextDate,\n isCurrentMonth: false,\n isToday: isToday(nextDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n nextMonthDay++;\n } else {\n // Current month days\n const currentDate = new Date(year, month, day);\n const dateEvents = getEventsForDate(events, currentDate);\n weekDates.push({\n date: currentDate,\n isCurrentMonth: true,\n isToday: isToday(currentDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n day++;\n }\n }\n\n dates.push(weekDates);\n }\n\n return dates;\n}\n\nexport function getCellClasses(calendarDate: CalendarDate): string[] {\n const classes: string[] = [];\n\n if (!calendarDate.isCurrentMonth) {\n classes.push('calendar-cell-other-month');\n }\n\n if (calendarDate.isToday) {\n classes.push('calendar-cell-today');\n }\n\n if (calendarDate.hasEvents) {\n classes.push('calendar-cell-has-event');\n }\n\n return classes;\n}\n\nexport function formatDateForDisplay(date: Date): string {\n return `${DAYS[date.getDay()]} ${date.getDate()}`;\n}\n\nexport function getMonthYearText(year: number, month: number): string {\n return `${MONTHS_FULL[month]} ${year}`;\n}\n\nexport function formatTimeRange(event: CalendarEvent): string {\n if (event.allDay || (!event.startTime && !event.endTime)) {\n return 'All day';\n }\n\n if (event.startTime && event.endTime) {\n return `${event.startTime} - ${event.endTime}`;\n }\n\n if (event.startTime) {\n return `${event.startTime}`;\n }\n\n return '';\n}\n\nexport function formatAttendees(attendees?: string[]): string {\n if (!attendees || attendees.length === 0) return '';\n\n if (attendees.length === 1) return attendees[0];\n if (attendees.length === 2) return attendees.join(' and ');\n\n return `${attendees.slice(0, -1).join(', ')}, and ${attendees[attendees.length - 1]}`;\n}\n\nexport function sortEventsByTime(events: CalendarEvent[]): CalendarEvent[] {\n return [...events].sort((a, b) => {\n const aAllDay = a.allDay || (!a.startTime && !a.endTime);\n const bAllDay = b.allDay || (!b.startTime && !b.endTime);\n\n if (aAllDay && !bAllDay) return -1;\n if (!aAllDay && bAllDay) return 1;\n\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.localeCompare(b.startTime);\n });\n}\n\nexport const DEFAULT_CATEGORY_COLORS: CategoryColorMap = {\n work: '#3b82f6',\n personal: '#8b5cf6',\n meeting: '#10b981',\n deadline: '#ef4444',\n appointment: '#f59e0b',\n other: '#6b7280',\n};\n\n// Dynamic function that accepts custom colors\nexport function getDefaultEventColor(\n category?: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = customColors || DEFAULT_CATEGORY_COLORS;\n\n if (category && colorMap[category]) {\n return colorMap[category];\n }\n\n // Default fallback color\n return '#fc8917';\n}\n\n// Helper to merge custom colors with defaults\nexport function mergeCategoryColors(\n customColors?: CategoryColorMap\n): CategoryColorMap {\n return {\n ...DEFAULT_CATEGORY_COLORS,\n ...customColors,\n };\n}\n\n// Validate if a color is a valid hex color\nexport function isValidHexColor(color: string): boolean {\n return /^#([0-9A-F]{3}){1,2}$/i.test(color);\n}\n\n// Get color for a category with validation\nexport function getCategoryColor(\n category: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = mergeCategoryColors(customColors);\n const color = colorMap[category] || colorMap.other || '#fc8917';\n\n return isValidHexColor(color) ? color : '#fc8917';\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\n// Escape a value for interpolation into HTML text or a quoted attribute\nexport function escapeHtml(value: unknown): string {\n if (value === null || value === undefined) return '';\n\n return String(value).replace(/[&<>\"']/g, char => HTML_ESCAPES[char]);\n}\n\n// Reduce a caller-supplied value to a single safe CSS class token\nexport function slugifyToken(value: string): string {\n return String(value)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n// Allow http(s), mailto and relative URLs; anything else (javascript:, data:,\n// vbscript:) collapses to a harmless anchor\nexport function safeUrl(value: string): string {\n // Browsers ignore control characters when resolving a scheme, so\n // `java\\0script:` and `java\\tscript:` both navigate. Strip them before\n // testing rather than after.\n const normalized = String(value)\n .split('')\n .filter(char => char.charCodeAt(0) > 0x20)\n .join('');\n\n if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;\n\n // Relative URLs have no scheme at all — a colon before the first slash,\n // question mark or hash means someone is naming one\n const schemeless = !/^[^/?#]*:/.test(normalized);\n\n return schemeless ? normalized : '#';\n}\n\n// Accept hex colors and bare CSS color keywords; reject anything that could\n// terminate the declaration or escape the style attribute\nexport function safeColor(value: string): string {\n const trimmed = String(value).trim();\n\n return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed)\n ? trimmed\n : '#3b82f6';\n}\n\nexport const MINUTES_PER_DAY = 1440;\nexport const DEFAULT_SLOT_DURATION = 60;\n\nexport function parseTimeToMinutes(time: unknown): number | null {\n if (typeof time !== 'string') return null;\n\n const match = /^(\\d{1,2}):(\\d{2})$/.exec(time.trim());\n if (!match) return null;\n\n const hours = Number(match[1]);\n const mins = Number(match[2]);\n if (hours > 24 || mins > 59 || (hours === 24 && mins > 0)) return null;\n\n return hours * 60 + mins;\n}\n\nexport function formatMinutes(total: number): string {\n const hours = Math.floor(total / 60);\n const mins = total % 60;\n return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;\n}\n\n// Absolute [start, end) in minutes. An end at or before the start is on the\n// following day, so a booking that crosses midnight is one interval rather than\n// two half-days. An unreadable time occupies the whole day — a booking surface\n// that cannot parse a time must not offer the slot.\nexport function eventInterval(\n event: CalendarEvent,\n slotDuration: number,\n onDay: Date\n): [number, number] | null {\n const day = normalizeDate(onDay).getTime();\n if (Number.isNaN(day)) return null;\n\n const base = day / 60000;\n const wholeDay: [number, number] = [base, base + MINUTES_PER_DAY];\n\n if (event.allDay || !event.startTime) return wholeDay;\n\n const from = parseTimeToMinutes(event.startTime);\n if (from === null) return wholeDay;\n\n if (event.endTime === undefined || event.endTime === null) {\n return [base + from, base + from + slotDuration];\n }\n\n const to = parseTimeToMinutes(event.endTime);\n if (to === null) return wholeDay;\n\n return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];\n}\n\nexport function mergeIntervals(\n intervals: Array<[number, number]>\n): Array<[number, number]> {\n const sorted = [...intervals].sort((a, b) => a[0] - b[0]);\n\n return sorted.reduce<Array<[number, number]>>((merged, [start, end]) => {\n const last = merged[merged.length - 1];\n if (last && start <= last[1]) last[1] = Math.max(last[1], end);\n else merged.push([start, end]);\n return merged;\n }, []);\n}\n\n// Which of the day's slots any booking overlaps. Events from the previous day\n// belong in `events` so a booking crossing midnight marks the morning it runs into.\nexport function bookedSlots(\n events: CalendarEvent[],\n date: Date,\n slotDuration: number = DEFAULT_SLOT_DURATION\n): boolean[] {\n const base = normalizeDate(date).getTime() / 60000;\n\n const previousDay = new Date(date);\n previousDay.setDate(previousDay.getDate() - 1);\n\n // A span's times repeat on each day it covers, so the window is anchored to\n // the day being drawn rather than the day the span began\n const intervals: Array<[number, number]> = [];\n for (const event of new Set(events)) {\n for (const day of [previousDay, date]) {\n if (!eventCoversDate(event, day)) continue;\n const interval = eventInterval(event, slotDuration, day);\n if (interval) intervals.push(interval);\n }\n }\n\n const merged = mergeIntervals(intervals);\n\n return Array.from(\n { length: Math.floor(MINUTES_PER_DAY / slotDuration) },\n (_, index) => {\n const start = base + index * slotDuration;\n const end = start + slotDuration;\n return merged.some(([from, to]) => start < to && end > from);\n }\n );\n}\n","import {\n CalendarEvent,\n CalendarState,\n CalendarConfig,\n CalendarActions,\n CalendarViewModel,\n CalendarPane,\n CategoryColorMap,\n} from './types';\nimport {\n generateCalendarDates,\n getEventsForDate,\n generateYears,\n getMonthYearText,\n formatDateForDisplay,\n mergeCategoryColors,\n getCategoryColor,\n MONTHS,\n DAYS,\n} from './utils';\n\nexport class CalendarEngine {\n private state: CalendarState;\n private config: CalendarConfig;\n private listeners: Set<() => void> = new Set();\n private categoryColors: CategoryColorMap;\n\n constructor(config: CalendarConfig) {\n this.config = config;\n this.categoryColors = mergeCategoryColors(config.categoryColors);\n\n const initialDate = config.initialDate || new Date();\n this.state = {\n currentYear: initialDate.getFullYear(),\n currentMonth: initialDate.getMonth(),\n currentDate: initialDate.getDate(),\n selectedDate: null,\n selectedDayIndex: null,\n tasks: [],\n };\n }\n\n /**\n * Subscribe to state changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Notify all listeners of state changes\n */\n private notify(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Get current state\n */\n getState(): CalendarState {\n return { ...this.state };\n }\n\n /**\n * Get view model with computed properties\n */\n getViewModel(): CalendarViewModel {\n const panes: CalendarPane[] = Array.from(\n { length: Math.max(1, this.config.monthCount ?? 1) },\n (_, offset) => {\n const anchor = new Date(\n this.state.currentYear,\n this.state.currentMonth + offset,\n 1\n );\n const year = anchor.getFullYear();\n const month = anchor.getMonth();\n\n return {\n year,\n month,\n monthAndYearText: getMonthYearText(year, month),\n calendarDates: generateCalendarDates(\n year,\n month,\n this.config.events,\n this.config.weekStartsOn\n ),\n };\n }\n );\n\n return {\n ...this.state,\n months: MONTHS,\n days: DAYS,\n years: generateYears(this.config.minYear, this.config.maxYear),\n monthAndYearText: panes[0].monthAndYearText,\n scheduleDay: this.state.selectedDate\n ? formatDateForDisplay(this.state.selectedDate)\n : '',\n panes,\n calendarDates: panes[0].calendarDates,\n };\n }\n\n /**\n * Get actions object\n */\n getActions(): CalendarActions {\n return {\n next: this.next.bind(this),\n previous: this.previous.bind(this),\n jump: this.jump.bind(this),\n goToToday: this.goToToday.bind(this),\n isCurrentMonth: this.isCurrentMonth.bind(this),\n selectDate: this.selectDate.bind(this),\n updateTasks: this.updateTasks.bind(this),\n };\n }\n\n /**\n * Navigate to next month\n */\n private next(): void {\n if (this.state.currentMonth === 11) {\n this.state.currentMonth = 0;\n this.state.currentYear++;\n } else {\n this.state.currentMonth++;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to previous month\n */\n private previous(): void {\n if (this.state.currentMonth === 0) {\n this.state.currentMonth = 11;\n this.state.currentYear--;\n } else {\n this.state.currentMonth--;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Jump to specific month and year\n */\n private jump(year: number, month: number): void {\n this.state.currentYear = year;\n this.state.currentMonth = month;\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to current month (today)\n */\n private goToToday(): void {\n const today = new Date();\n this.state.currentYear = today.getFullYear();\n this.state.currentMonth = today.getMonth();\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Check if currently viewing today's month\n */\n private isCurrentMonth(): boolean {\n const today = new Date();\n return (\n this.state.currentYear === today.getFullYear() &&\n this.state.currentMonth === today.getMonth()\n );\n }\n\n /**\n * Select a specific date\n */\n private selectDate(date: Date, dayIndex?: number): void {\n this.state.selectedDate = date;\n this.state.selectedDayIndex = dayIndex ?? null;\n this.state.currentDate = date.getDate();\n this.state.currentMonth = date.getMonth();\n this.state.currentYear = date.getFullYear();\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Update tasks for the currently selected date\n */\n private updateTasks(): void {\n if (!this.state.selectedDate) {\n this.state.tasks = [];\n return;\n }\n\n this.state.tasks = getEventsForDate(\n this.config.events,\n this.state.selectedDate\n );\n }\n\n /**\n * Update events configuration\n */\n updateEvents(events: CalendarEvent[]): void {\n this.config.events = events;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Handle date cell click\n */\n handleDateClick(date: Date, dayIndex?: number): void {\n this.selectDate(date, dayIndex);\n }\n\n /**\n * Check if date has events\n */\n hasEventsForDate(date: Date): boolean {\n return getEventsForDate(this.config.events, date).length > 0;\n }\n\n /**\n * Get events for a specific date\n */\n getEventsForDate(date: Date): CalendarEvent[] {\n return getEventsForDate(this.config.events, date);\n }\n\n /**\n * Clear selected date\n */\n clearSelection(): void {\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.state.tasks = [];\n this.notify();\n }\n\n /**\n * Get category color (with custom colors support)\n */\n getCategoryColor(category?: string): string {\n if (!category) return '#fc8917';\n return getCategoryColor(category, this.categoryColors);\n }\n\n /**\n * Update category colors dynamically\n */\n updateCategoryColors(colors: CategoryColorMap): void {\n this.categoryColors = mergeCategoryColors(colors);\n this.notify();\n }\n\n /**\n * Get all category colors\n */\n getCategoryColors(): CategoryColorMap {\n return { ...this.categoryColors };\n }\n\n /**\n * Register a new category with color\n */\n registerCategory(name: string, color: string): void {\n this.categoryColors[name] = color;\n this.notify();\n }\n\n /**\n * Destroy the engine and cleanup listeners\n */\n destroy(): void {\n this.listeners.clear();\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.mts CHANGED
@@ -3,6 +3,7 @@ interface CalendarEvent {
3
3
  id: string | number;
4
4
  name: string;
5
5
  date: string | Date;
6
+ endDate?: string | Date;
6
7
  startTime?: string;
7
8
  endTime?: string;
8
9
  allDay?: boolean;
@@ -145,6 +146,7 @@ declare function normalizeDate(date: Date): Date;
145
146
  declare function isSameDay(date1: Date, date2: Date): boolean;
146
147
  declare function isToday(date: Date): boolean;
147
148
  declare function generateYears(minYear?: number, maxYear?: number): number[];
149
+ declare function eventCoversDate(event: CalendarEvent, date: Date): boolean;
148
150
  declare function getEventsForDate(events: CalendarEvent[], date: Date): CalendarEvent[];
149
151
  declare function hasEvents(events: CalendarEvent[], date: Date): boolean;
150
152
  declare function generateCalendarDates(year: number, month: number, events?: CalendarEvent[], weekStartsOn?: 0 | 1): CalendarDate[][];
@@ -163,6 +165,13 @@ declare function escapeHtml(value: unknown): string;
163
165
  declare function slugifyToken(value: string): string;
164
166
  declare function safeUrl(value: string): string;
165
167
  declare function safeColor(value: string): string;
168
+ declare const MINUTES_PER_DAY = 1440;
169
+ declare const DEFAULT_SLOT_DURATION = 60;
170
+ declare function parseTimeToMinutes(time: unknown): number | null;
171
+ declare function formatMinutes(total: number): string;
172
+ declare function eventInterval(event: CalendarEvent, slotDuration: number, onDay: Date): [number, number] | null;
173
+ declare function mergeIntervals(intervals: Array<[number, number]>): Array<[number, number]>;
174
+ declare function bookedSlots(events: CalendarEvent[], date: Date, slotDuration?: number): boolean[];
166
175
 
167
176
  declare class CalendarEngine {
168
177
  private state;
@@ -307,6 +316,9 @@ declare class CalendarElement extends HTMLElement {
307
316
  disconnectedCallback(): void;
308
317
  attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
309
318
  private get headingText();
319
+ private static warnedMissingEnd;
320
+ private get slotDuration();
321
+ private warnMissingEndTime;
310
322
  private get monthCount();
311
323
  private get minYear();
312
324
  private get maxYear();
@@ -371,4 +383,4 @@ interface CalendarElementInstance {
371
383
  getEngine: () => CalendarEngine;
372
384
  }
373
385
 
374
- export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarElement, type CalendarElementInstance, type CalendarElementProps, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, MONTHS, MONTHS_FULL, defineCalendarElement, escapeHtml, formatAttendees, formatDateForDisplay, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, normalizeDate, safeColor, safeUrl, slugifyToken, sortEventsByTime };
386
+ export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarElement, type CalendarElementInstance, type CalendarElementProps, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, DEFAULT_SLOT_DURATION, MINUTES_PER_DAY, MONTHS, MONTHS_FULL, bookedSlots, defineCalendarElement, escapeHtml, eventCoversDate, eventInterval, formatAttendees, formatDateForDisplay, formatMinutes, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, mergeIntervals, normalizeDate, parseTimeToMinutes, safeColor, safeUrl, slugifyToken, sortEventsByTime };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ interface CalendarEvent {
3
3
  id: string | number;
4
4
  name: string;
5
5
  date: string | Date;
6
+ endDate?: string | Date;
6
7
  startTime?: string;
7
8
  endTime?: string;
8
9
  allDay?: boolean;
@@ -145,6 +146,7 @@ declare function normalizeDate(date: Date): Date;
145
146
  declare function isSameDay(date1: Date, date2: Date): boolean;
146
147
  declare function isToday(date: Date): boolean;
147
148
  declare function generateYears(minYear?: number, maxYear?: number): number[];
149
+ declare function eventCoversDate(event: CalendarEvent, date: Date): boolean;
148
150
  declare function getEventsForDate(events: CalendarEvent[], date: Date): CalendarEvent[];
149
151
  declare function hasEvents(events: CalendarEvent[], date: Date): boolean;
150
152
  declare function generateCalendarDates(year: number, month: number, events?: CalendarEvent[], weekStartsOn?: 0 | 1): CalendarDate[][];
@@ -163,6 +165,13 @@ declare function escapeHtml(value: unknown): string;
163
165
  declare function slugifyToken(value: string): string;
164
166
  declare function safeUrl(value: string): string;
165
167
  declare function safeColor(value: string): string;
168
+ declare const MINUTES_PER_DAY = 1440;
169
+ declare const DEFAULT_SLOT_DURATION = 60;
170
+ declare function parseTimeToMinutes(time: unknown): number | null;
171
+ declare function formatMinutes(total: number): string;
172
+ declare function eventInterval(event: CalendarEvent, slotDuration: number, onDay: Date): [number, number] | null;
173
+ declare function mergeIntervals(intervals: Array<[number, number]>): Array<[number, number]>;
174
+ declare function bookedSlots(events: CalendarEvent[], date: Date, slotDuration?: number): boolean[];
166
175
 
167
176
  declare class CalendarEngine {
168
177
  private state;
@@ -307,6 +316,9 @@ declare class CalendarElement extends HTMLElement {
307
316
  disconnectedCallback(): void;
308
317
  attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
309
318
  private get headingText();
319
+ private static warnedMissingEnd;
320
+ private get slotDuration();
321
+ private warnMissingEndTime;
310
322
  private get monthCount();
311
323
  private get minYear();
312
324
  private get maxYear();
@@ -371,4 +383,4 @@ interface CalendarElementInstance {
371
383
  getEngine: () => CalendarEngine;
372
384
  }
373
385
 
374
- export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarElement, type CalendarElementInstance, type CalendarElementProps, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, MONTHS, MONTHS_FULL, defineCalendarElement, escapeHtml, formatAttendees, formatDateForDisplay, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, normalizeDate, safeColor, safeUrl, slugifyToken, sortEventsByTime };
386
+ export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarElement, type CalendarElementInstance, type CalendarElementProps, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, DEFAULT_SLOT_DURATION, MINUTES_PER_DAY, MONTHS, MONTHS_FULL, bookedSlots, defineCalendarElement, escapeHtml, eventCoversDate, eventInterval, formatAttendees, formatDateForDisplay, formatMinutes, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, mergeIntervals, normalizeDate, parseTimeToMinutes, safeColor, safeUrl, slugifyToken, sortEventsByTime };