`;
// Replace the container div (slot's parent) with our wrapper
const slot = root.querySelector("slot");
const containerDiv = slot?.parentElement as HTMLElement;
if (containerDiv) {
containerDiv.replaceWith(wrapper);
}
// Get element references
this.#daysContainer = wrapper.querySelector(".days");
this.#monthYearEl = wrapper.querySelector(".month-year");
this.#eventsListEl = wrapper.querySelector(".events-list");
const prevBtn = wrapper.querySelector(".prev-btn") as HTMLButtonElement;
const nextBtn = wrapper.querySelector(".next-btn") as HTMLButtonElement;
const closeBtn = wrapper.querySelector(".close-btn") as HTMLButtonElement;
// Navigation
prevBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.#currentMonth = new Date(
this.#currentMonth.getFullYear(),
this.#currentMonth.getMonth() - 1,
1
);
this.#render();
});
nextBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.#currentMonth = new Date(
this.#currentMonth.getFullYear(),
this.#currentMonth.getMonth() + 1,
1
);
this.#render();
});
closeBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.dispatchEvent(new CustomEvent("close"));
});
// Initial render
this.#render();
return root;
}
#render() {
if (!this.#daysContainer || !this.#monthYearEl || !this.#eventsListEl) return;
const year = this.#currentMonth.getFullYear();
const month = this.#currentMonth.getMonth();
// Update month/year display
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
this.#monthYearEl.textContent = `${monthNames[month]} ${year}`;
// Calculate days
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startPadding = firstDay.getDay();
const daysInMonth = lastDay.getDate();
const today = new Date();
today.setHours(0, 0, 0, 0);
// Generate day cells
let html = "";
// Previous month padding
const prevMonthLastDay = new Date(year, month, 0).getDate();
for (let i = startPadding - 1; i >= 0; i--) {
const day = prevMonthLastDay - i;
html += `
${day}
`;
}
// Current month days
for (let day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
date.setHours(0, 0, 0, 0);
const classes = ["day"];
if (date.getTime() === today.getTime()) classes.push("today");
if (this.#selectedDate && date.getTime() === this.#selectedDate.getTime()) {
classes.push("selected");
}
if (this.#hasEventOnDate(date)) classes.push("has-event");
html += `
${day}
`;
}
// Next month padding
const totalCells = startPadding + daysInMonth;
const nextPadding = totalCells <= 35 ? 35 - totalCells : 42 - totalCells;
for (let day = 1; day <= nextPadding; day++) {
html += `
${day}
`;
}
this.#daysContainer.innerHTML = html;
// Add click handlers
this.#daysContainer.querySelectorAll(".day").forEach((el) => {
el.addEventListener("click", (e) => {
e.stopPropagation();
const dateStr = (el as HTMLElement).dataset.date;
if (dateStr) {
const [y, m, d] = dateStr.split("-").map(Number);
this.selectedDate = new Date(y, m, d);
}
});
});
// Render events for selected date
this.#renderEvents();
}
#hasEventOnDate(date: Date): boolean {
return this.#events.some((event) => {
const eventDate = new Date(event.date);
eventDate.setHours(0, 0, 0, 0);
return eventDate.getTime() === date.getTime();
});
}
#renderEvents() {
if (!this.#eventsListEl) return;
if (!this.#selectedDate) {
this.#eventsListEl.style.display = "none";
return;
}
const dayEvents = this.#events.filter((event) => {
const eventDate = new Date(event.date);
eventDate.setHours(0, 0, 0, 0);
const selected = new Date(this.#selectedDate!);
selected.setHours(0, 0, 0, 0);
return eventDate.getTime() === selected.getTime();
});
if (dayEvents.length === 0) {
this.#eventsListEl.innerHTML = '