UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

930 lines (763 loc) 30.4 kB
// MusPE UI Enterprise - Advanced Calendar Component import { MusPE } from '../../core/muspe.js'; class MusPECalendar extends MusPE { constructor(options = {}) { super(); this.options = { mode: 'month', // 'month', 'week', 'day', 'year' locale: 'en-US', firstDayOfWeek: 0, // 0 = Sunday, 1 = Monday showWeekNumbers: false, showOtherMonths: true, selectableRange: null, // { start: Date, end: Date } disabledDates: [], // Array of dates or date ranges holidays: [], // Array of holiday objects events: [], // Array of event objects selectable: true, multiSelect: false, inline: false, showHeader: true, showNavigation: true, showToday: true, theme: 'default', size: 'medium', animations: true, weekendHighlight: true, ...options }; this.state = this.reactive({ currentDate: new Date(), selectedDates: new Set(), viewDate: new Date(), events: new Map(), holidays: new Map(), mode: this.options.mode }); this.monthNames = []; this.dayNames = []; this.weekdayNames = []; this.init(); } init() { this.initializeLocale(); this.processEvents(); this.processHolidays(); this.setupCalendar(); this.bindEvents(); this.render(); } initializeLocale() { const date = new Date(2024, 0, 1); // January 1, 2024 (Monday) // Month names this.monthNames = Array.from({ length: 12 }, (_, i) => { return new Intl.DateTimeFormat(this.options.locale, { month: 'long' }) .format(new Date(2024, i, 1)); }); // Day names (full) this.dayNames = Array.from({ length: 7 }, (_, i) => { const day = new Date(2024, 0, i + 1); // Start from Jan 1 (Monday) return new Intl.DateTimeFormat(this.options.locale, { weekday: 'long' }) .format(day); }); // Weekday names (short) this.weekdayNames = Array.from({ length: 7 }, (_, i) => { const day = new Date(2024, 0, i + 1); return new Intl.DateTimeFormat(this.options.locale, { weekday: 'short' }) .format(day); }); // Adjust for first day of week if (this.options.firstDayOfWeek > 0) { this.dayNames = [ ...this.dayNames.slice(this.options.firstDayOfWeek), ...this.dayNames.slice(0, this.options.firstDayOfWeek) ]; this.weekdayNames = [ ...this.weekdayNames.slice(this.options.firstDayOfWeek), ...this.weekdayNames.slice(0, this.options.firstDayOfWeek) ]; } } processEvents() { this.state.events.clear(); this.options.events.forEach(event => { const dateKey = this.getDateKey(new Date(event.date)); if (!this.state.events.has(dateKey)) { this.state.events.set(dateKey, []); } this.state.events.get(dateKey).push({ id: event.id || this.generateId(), title: event.title, description: event.description || '', color: event.color || '#007bff', time: event.time || null, allDay: event.allDay !== false, recurring: event.recurring || null, category: event.category || 'default' }); }); } processHolidays() { this.state.holidays.clear(); this.options.holidays.forEach(holiday => { const dateKey = this.getDateKey(new Date(holiday.date)); this.state.holidays.set(dateKey, { name: holiday.name, type: holiday.type || 'public', color: holiday.color || '#ff6b6b' }); }); } setupCalendar() { this.element = document.createElement('div'); this.element.className = `muspe-calendar muspe-calendar--${this.options.theme} muspe-calendar--${this.options.size}`; if (this.options.inline) { this.element.classList.add('muspe-calendar--inline'); } this.element.innerHTML = ` <div class="muspe-calendar__header"> <div class="muspe-calendar__navigation"> <button class="muspe-calendar__nav-button muspe-calendar__nav-button--prev" type="button"> <span class="muspe-calendar__nav-icon">‹</span> </button> <div class="muspe-calendar__current"> <button class="muspe-calendar__month-year" type="button"> <span class="muspe-calendar__current-text"></span> </button> </div> <button class="muspe-calendar__nav-button muspe-calendar__nav-button--next" type="button"> <span class="muspe-calendar__nav-icon">›</span> </button> </div> <div class="muspe-calendar__view-controls"> <div class="muspe-calendar__view-buttons"> <button class="muspe-calendar__view-button" data-view="month" type="button">Month</button> <button class="muspe-calendar__view-button" data-view="week" type="button">Week</button> <button class="muspe-calendar__view-button" data-view="day" type="button">Day</button> </div> ${this.options.showToday ? '<button class="muspe-calendar__today-button" type="button">Today</button>' : ''} </div> </div> <div class="muspe-calendar__content"> <div class="muspe-calendar__grid"></div> <div class="muspe-calendar__year-grid" style="display: none;"></div> </div> <div class="muspe-calendar__sidebar"> <div class="muspe-calendar__legend"> <h4 class="muspe-calendar__legend-title">Legend</h4> <div class="muspe-calendar__legend-items"></div> </div> <div class="muspe-calendar__mini-calendar"> <div class="muspe-calendar__mini-grid"></div> </div> </div> `; this.headerElement = this.element.querySelector('.muspe-calendar__header'); this.navigationElement = this.element.querySelector('.muspe-calendar__navigation'); this.currentTextElement = this.element.querySelector('.muspe-calendar__current-text'); this.gridElement = this.element.querySelector('.muspe-calendar__grid'); this.yearGridElement = this.element.querySelector('.muspe-calendar__year-grid'); this.viewButtonsElement = this.element.querySelector('.muspe-calendar__view-buttons'); this.legendElement = this.element.querySelector('.muspe-calendar__legend-items'); this.miniCalendarElement = this.element.querySelector('.muspe-calendar__mini-grid'); this.updateViewButtons(); this.renderLegend(); } bindEvents() { // Navigation this.element.querySelector('.muspe-calendar__nav-button--prev').addEventListener('click', () => { this.navigate(-1); }); this.element.querySelector('.muspe-calendar__nav-button--next').addEventListener('click', () => { this.navigate(1); }); // Month/Year selector this.element.querySelector('.muspe-calendar__month-year').addEventListener('click', () => { this.showYearView(); }); // View controls this.viewButtonsElement.addEventListener('click', (e) => { if (e.target.classList.contains('muspe-calendar__view-button')) { this.setView(e.target.dataset.view); } }); // Today button const todayButton = this.element.querySelector('.muspe-calendar__today-button'); if (todayButton) { todayButton.addEventListener('click', () => { this.goToToday(); }); } // Grid interactions this.gridElement.addEventListener('click', (e) => { const dateCell = e.target.closest('.muspe-calendar__date'); if (dateCell && !dateCell.classList.contains('muspe-calendar__date--disabled')) { this.handleDateClick(dateCell); } }); // Year grid interactions this.yearGridElement.addEventListener('click', (e) => { const yearCell = e.target.closest('.muspe-calendar__year'); const monthCell = e.target.closest('.muspe-calendar__month'); if (yearCell) { this.handleYearClick(parseInt(yearCell.dataset.year)); } else if (monthCell) { this.handleMonthClick(parseInt(monthCell.dataset.month)); } }); // Keyboard navigation this.element.addEventListener('keydown', (e) => { this.handleKeyboard(e); }); } navigate(direction) { const newDate = new Date(this.state.viewDate); switch (this.state.mode) { case 'month': newDate.setMonth(newDate.getMonth() + direction); break; case 'week': newDate.setDate(newDate.getDate() + (direction * 7)); break; case 'day': newDate.setDate(newDate.getDate() + direction); break; case 'year': newDate.setFullYear(newDate.getFullYear() + direction); break; } this.state.viewDate = newDate; this.render(); this.emit('viewChange', { date: newDate, mode: this.state.mode }); } setView(mode) { this.state.mode = mode; this.updateViewButtons(); this.render(); this.emit('viewModeChange', { mode }); } updateViewButtons() { this.viewButtonsElement.querySelectorAll('.muspe-calendar__view-button').forEach(button => { button.classList.toggle('muspe-calendar__view-button--active', button.dataset.view === this.state.mode); }); } showYearView() { this.gridElement.style.display = 'none'; this.yearGridElement.style.display = 'grid'; this.renderYearView(); } hideYearView() { this.gridElement.style.display = 'grid'; this.yearGridElement.style.display = 'none'; } goToToday() { this.state.viewDate = new Date(); this.state.currentDate = new Date(); this.hideYearView(); this.render(); this.emit('todayClick'); } handleDateClick(dateCell) { const date = new Date(dateCell.dataset.date); const dateKey = this.getDateKey(date); if (!this.options.selectable) { this.emit('dateClick', { date, events: this.state.events.get(dateKey) || [] }); return; } if (this.options.multiSelect) { if (this.state.selectedDates.has(dateKey)) { this.state.selectedDates.delete(dateKey); } else { this.state.selectedDates.add(dateKey); } } else { this.state.selectedDates.clear(); this.state.selectedDates.add(dateKey); } this.updateSelectedDates(); this.emit('dateSelect', { date, selected: this.state.selectedDates.has(dateKey), selectedDates: Array.from(this.state.selectedDates).map(key => this.parseDate(key)) }); } handleYearClick(year) { this.state.viewDate.setFullYear(year); this.renderMonthView(); } handleMonthClick(month) { this.state.viewDate.setMonth(month); this.hideYearView(); this.render(); } handleKeyboard(e) { const { key } = e; switch (key) { case 'ArrowLeft': e.preventDefault(); this.navigate(-1); break; case 'ArrowRight': e.preventDefault(); this.navigate(1); break; case 'Home': e.preventDefault(); this.goToToday(); break; case 'Escape': e.preventDefault(); this.hideYearView(); break; } } render() { this.updateCurrentText(); switch (this.state.mode) { case 'month': this.renderMonthView(); break; case 'week': this.renderWeekView(); break; case 'day': this.renderDayView(); break; } this.renderMiniCalendar(); } updateCurrentText() { const formatter = new Intl.DateTimeFormat(this.options.locale, { year: 'numeric', month: 'long' }); this.currentTextElement.textContent = formatter.format(this.state.viewDate); } renderMonthView() { this.gridElement.className = 'muspe-calendar__grid muspe-calendar__grid--month'; this.gridElement.innerHTML = ''; // Weekday headers const weekdayHeader = document.createElement('div'); weekdayHeader.className = 'muspe-calendar__weekdays'; if (this.options.showWeekNumbers) { const weekNumberHeader = document.createElement('div'); weekNumberHeader.className = 'muspe-calendar__weekday muspe-calendar__weekday--week-number'; weekNumberHeader.textContent = 'Wk'; weekdayHeader.appendChild(weekNumberHeader); } this.weekdayNames.forEach(name => { const weekday = document.createElement('div'); weekday.className = 'muspe-calendar__weekday'; weekday.textContent = name; weekdayHeader.appendChild(weekday); }); this.gridElement.appendChild(weekdayHeader); // Calendar dates const datesContainer = document.createElement('div'); datesContainer.className = 'muspe-calendar__dates'; const monthStart = new Date(this.state.viewDate.getFullYear(), this.state.viewDate.getMonth(), 1); const monthEnd = new Date(this.state.viewDate.getFullYear(), this.state.viewDate.getMonth() + 1, 0); // Calculate start and end dates for the grid const startDate = new Date(monthStart); const dayOfWeek = (monthStart.getDay() - this.options.firstDayOfWeek + 7) % 7; startDate.setDate(startDate.getDate() - dayOfWeek); const endDate = new Date(monthEnd); const endDayOfWeek = (monthEnd.getDay() - this.options.firstDayOfWeek + 7) % 7; endDate.setDate(endDate.getDate() + (6 - endDayOfWeek)); const currentDate = new Date(startDate); let weekNumber = this.getWeekNumber(currentDate); while (currentDate <= endDate) { const weekRow = document.createElement('div'); weekRow.className = 'muspe-calendar__week'; if (this.options.showWeekNumbers) { const weekNumberCell = document.createElement('div'); weekNumberCell.className = 'muspe-calendar__week-number'; weekNumberCell.textContent = weekNumber; weekRow.appendChild(weekNumberCell); } for (let i = 0; i < 7; i++) { const dateCell = this.createDateCell(new Date(currentDate)); weekRow.appendChild(dateCell); currentDate.setDate(currentDate.getDate() + 1); } datesContainer.appendChild(weekRow); weekNumber++; } this.gridElement.appendChild(datesContainer); } renderWeekView() { this.gridElement.className = 'muspe-calendar__grid muspe-calendar__grid--week'; this.gridElement.innerHTML = ''; const weekStart = this.getWeekStart(this.state.viewDate); const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 6); // Week header const weekHeader = document.createElement('div'); weekHeader.className = 'muspe-calendar__week-header'; const currentDate = new Date(weekStart); for (let i = 0; i < 7; i++) { const dayHeader = document.createElement('div'); dayHeader.className = 'muspe-calendar__day-header'; dayHeader.innerHTML = ` <div class="muspe-calendar__day-name">${this.weekdayNames[i]}</div> <div class="muspe-calendar__day-number">${currentDate.getDate()}</div> `; weekHeader.appendChild(dayHeader); currentDate.setDate(currentDate.getDate() + 1); } this.gridElement.appendChild(weekHeader); // Time slots const timeSlotsContainer = document.createElement('div'); timeSlotsContainer.className = 'muspe-calendar__time-slots'; for (let hour = 0; hour < 24; hour++) { const timeSlot = document.createElement('div'); timeSlot.className = 'muspe-calendar__time-slot'; const timeLabel = document.createElement('div'); timeLabel.className = 'muspe-calendar__time-label'; timeLabel.textContent = `${hour.toString().padStart(2, '0')}:00`; timeSlot.appendChild(timeLabel); const timeColumns = document.createElement('div'); timeColumns.className = 'muspe-calendar__time-columns'; const weekCurrentDate = new Date(weekStart); for (let i = 0; i < 7; i++) { const timeColumn = document.createElement('div'); timeColumn.className = 'muspe-calendar__time-column'; timeColumn.dataset.date = this.getDateKey(weekCurrentDate); timeColumn.dataset.hour = hour; // Add events for this time slot this.addEventsToTimeSlot(timeColumn, weekCurrentDate, hour); timeColumns.appendChild(timeColumn); weekCurrentDate.setDate(weekCurrentDate.getDate() + 1); } timeSlot.appendChild(timeColumns); timeSlotsContainer.appendChild(timeSlot); } this.gridElement.appendChild(timeSlotsContainer); } renderDayView() { this.gridElement.className = 'muspe-calendar__grid muspe-calendar__grid--day'; this.gridElement.innerHTML = ''; // Day header const dayHeader = document.createElement('div'); dayHeader.className = 'muspe-calendar__day-header'; dayHeader.innerHTML = ` <div class="muspe-calendar__day-name">${this.dayNames[this.state.viewDate.getDay()]}</div> <div class="muspe-calendar__day-number">${this.state.viewDate.getDate()}</div> <div class="muspe-calendar__day-month">${this.monthNames[this.state.viewDate.getMonth()]}</div> `; this.gridElement.appendChild(dayHeader); // Time slots const timeSlotsContainer = document.createElement('div'); timeSlotsContainer.className = 'muspe-calendar__time-slots muspe-calendar__time-slots--day'; for (let hour = 0; hour < 24; hour++) { const timeSlot = document.createElement('div'); timeSlot.className = 'muspe-calendar__time-slot'; timeSlot.dataset.date = this.getDateKey(this.state.viewDate); timeSlot.dataset.hour = hour; const timeLabel = document.createElement('div'); timeLabel.className = 'muspe-calendar__time-label'; timeLabel.textContent = `${hour.toString().padStart(2, '0')}:00`; timeSlot.appendChild(timeLabel); const timeContent = document.createElement('div'); timeContent.className = 'muspe-calendar__time-content'; // Add events for this time slot this.addEventsToTimeSlot(timeContent, this.state.viewDate, hour); timeSlot.appendChild(timeContent); timeSlotsContainer.appendChild(timeSlot); } this.gridElement.appendChild(timeSlotsContainer); } renderYearView() { this.yearGridElement.innerHTML = ''; const year = this.state.viewDate.getFullYear(); // Year selector const yearSelector = document.createElement('div'); yearSelector.className = 'muspe-calendar__year-selector'; for (let y = year - 5; y <= year + 5; y++) { const yearButton = document.createElement('button'); yearButton.className = 'muspe-calendar__year'; yearButton.dataset.year = y; yearButton.textContent = y; if (y === year) { yearButton.classList.add('muspe-calendar__year--current'); } yearSelector.appendChild(yearButton); } this.yearGridElement.appendChild(yearSelector); // Month selector this.renderMonthSelector(); } renderMonthSelector() { const monthSelector = document.createElement('div'); monthSelector.className = 'muspe-calendar__month-selector'; this.monthNames.forEach((name, index) => { const monthButton = document.createElement('button'); monthButton.className = 'muspe-calendar__month'; monthButton.dataset.month = index; monthButton.textContent = name; if (index === this.state.viewDate.getMonth()) { monthButton.classList.add('muspe-calendar__month--current'); } monthSelector.appendChild(monthButton); }); this.yearGridElement.appendChild(monthSelector); } createDateCell(date) { const dateCell = document.createElement('div'); dateCell.className = 'muspe-calendar__date'; dateCell.dataset.date = date.toISOString().split('T')[0]; const isCurrentMonth = date.getMonth() === this.state.viewDate.getMonth(); const isToday = this.isSameDay(date, new Date()); const isSelected = this.state.selectedDates.has(this.getDateKey(date)); const isWeekend = this.isWeekend(date); const isDisabled = this.isDateDisabled(date); const hasEvents = this.state.events.has(this.getDateKey(date)); const hasHoliday = this.state.holidays.has(this.getDateKey(date)); // Apply classes if (!isCurrentMonth && !this.options.showOtherMonths) { dateCell.classList.add('muspe-calendar__date--hidden'); } else if (!isCurrentMonth) { dateCell.classList.add('muspe-calendar__date--other-month'); } if (isToday) dateCell.classList.add('muspe-calendar__date--today'); if (isSelected) dateCell.classList.add('muspe-calendar__date--selected'); if (isWeekend && this.options.weekendHighlight) dateCell.classList.add('muspe-calendar__date--weekend'); if (isDisabled) dateCell.classList.add('muspe-calendar__date--disabled'); if (hasEvents) dateCell.classList.add('muspe-calendar__date--has-events'); if (hasHoliday) dateCell.classList.add('muspe-calendar__date--holiday'); // Date content const dateContent = document.createElement('div'); dateContent.className = 'muspe-calendar__date-content'; const dateNumber = document.createElement('span'); dateNumber.className = 'muspe-calendar__date-number'; dateNumber.textContent = date.getDate(); dateContent.appendChild(dateNumber); // Events indicator if (hasEvents) { const eventsIndicator = document.createElement('div'); eventsIndicator.className = 'muspe-calendar__events-indicator'; const events = this.state.events.get(this.getDateKey(date)); events.slice(0, 3).forEach(event => { const eventDot = document.createElement('span'); eventDot.className = 'muspe-calendar__event-dot'; eventDot.style.backgroundColor = event.color; eventDot.title = event.title; eventsIndicator.appendChild(eventDot); }); if (events.length > 3) { const moreIndicator = document.createElement('span'); moreIndicator.className = 'muspe-calendar__event-more'; moreIndicator.textContent = `+${events.length - 3}`; eventsIndicator.appendChild(moreIndicator); } dateContent.appendChild(eventsIndicator); } // Holiday indicator if (hasHoliday) { const holiday = this.state.holidays.get(this.getDateKey(date)); const holidayIndicator = document.createElement('div'); holidayIndicator.className = 'muspe-calendar__holiday-indicator'; holidayIndicator.style.backgroundColor = holiday.color; holidayIndicator.title = holiday.name; dateContent.appendChild(holidayIndicator); } dateCell.appendChild(dateContent); return dateCell; } addEventsToTimeSlot(container, date, hour) { const dateKey = this.getDateKey(date); const events = this.state.events.get(dateKey) || []; events.forEach(event => { if (event.allDay) return; const eventHour = event.time ? parseInt(event.time.split(':')[0]) : null; if (eventHour === hour) { const eventElement = document.createElement('div'); eventElement.className = 'muspe-calendar__event'; eventElement.style.backgroundColor = event.color; eventElement.innerHTML = ` <div class="muspe-calendar__event-title">${event.title}</div> <div class="muspe-calendar__event-time">${event.time}</div> `; container.appendChild(eventElement); } }); } renderMiniCalendar() { if (!this.miniCalendarElement) return; this.miniCalendarElement.innerHTML = ''; const miniDate = new Date(this.state.currentDate); const monthStart = new Date(miniDate.getFullYear(), miniDate.getMonth(), 1); const monthEnd = new Date(miniDate.getFullYear(), miniDate.getMonth() + 1, 0); // Mini weekday headers const miniWeekdays = document.createElement('div'); miniWeekdays.className = 'muspe-calendar__mini-weekdays'; this.weekdayNames.forEach(name => { const miniWeekday = document.createElement('div'); miniWeekday.className = 'muspe-calendar__mini-weekday'; miniWeekday.textContent = name.charAt(0); miniWeekdays.appendChild(miniWeekday); }); this.miniCalendarElement.appendChild(miniWeekdays); // Mini dates const miniDates = document.createElement('div'); miniDates.className = 'muspe-calendar__mini-dates'; const startDate = new Date(monthStart); const dayOfWeek = (monthStart.getDay() - this.options.firstDayOfWeek + 7) % 7; startDate.setDate(startDate.getDate() - dayOfWeek); const endDate = new Date(monthEnd); const endDayOfWeek = (monthEnd.getDay() - this.options.firstDayOfWeek + 7) % 7; endDate.setDate(endDate.getDate() + (6 - endDayOfWeek)); const currentDate = new Date(startDate); while (currentDate <= endDate) { const miniDate = document.createElement('div'); miniDate.className = 'muspe-calendar__mini-date'; miniDate.textContent = currentDate.getDate(); const isCurrentMonth = currentDate.getMonth() === miniDate.getMonth(); const isToday = this.isSameDay(currentDate, new Date()); if (!isCurrentMonth) { miniDate.classList.add('muspe-calendar__mini-date--other-month'); } if (isToday) { miniDate.classList.add('muspe-calendar__mini-date--today'); } miniDates.appendChild(miniDate); currentDate.setDate(currentDate.getDate() + 1); } this.miniCalendarElement.appendChild(miniDates); } renderLegend() { if (!this.legendElement) return; this.legendElement.innerHTML = ''; const legendItems = [ { color: '#007bff', label: 'Events' }, { color: '#ff6b6b', label: 'Holidays' }, { color: '#28a745', label: 'Today' } ]; legendItems.forEach(item => { const legendItem = document.createElement('div'); legendItem.className = 'muspe-calendar__legend-item'; legendItem.innerHTML = ` <span class="muspe-calendar__legend-color" style="background-color: ${item.color}"></span> <span class="muspe-calendar__legend-label">${item.label}</span> `; this.legendElement.appendChild(legendItem); }); } updateSelectedDates() { this.element.querySelectorAll('.muspe-calendar__date--selected').forEach(cell => { cell.classList.remove('muspe-calendar__date--selected'); }); this.state.selectedDates.forEach(dateKey => { const cell = this.element.querySelector(`[data-date="${dateKey}"]`); if (cell) { cell.classList.add('muspe-calendar__date--selected'); } }); } // Utility methods getDateKey(date) { return date.toISOString().split('T')[0]; } parseDate(dateKey) { return new Date(dateKey); } isSameDay(date1, date2) { return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate(); } isWeekend(date) { const day = date.getDay(); return day === 0 || day === 6; // Sunday or Saturday } isDateDisabled(date) { if (this.options.selectableRange) { const { start, end } = this.options.selectableRange; if (date < start || date > end) return true; } return this.options.disabledDates.some(disabledDate => { if (disabledDate instanceof Date) { return this.isSameDay(date, disabledDate); } if (disabledDate.start && disabledDate.end) { return date >= disabledDate.start && date <= disabledDate.end; } return false; }); } getWeekStart(date) { const weekStart = new Date(date); const dayOfWeek = (date.getDay() - this.options.firstDayOfWeek + 7) % 7; weekStart.setDate(weekStart.getDate() - dayOfWeek); return weekStart; } getWeekNumber(date) { const firstDayOfYear = new Date(date.getFullYear(), 0, 1); const pastDaysOfYear = (date - firstDayOfYear) / 86400000; return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); } generateId() { return Math.random().toString(36).substr(2, 9); } // Public API methods addEvent(event) { const dateKey = this.getDateKey(new Date(event.date)); if (!this.state.events.has(dateKey)) { this.state.events.set(dateKey, []); } const newEvent = { id: event.id || this.generateId(), title: event.title, description: event.description || '', color: event.color || '#007bff', time: event.time || null, allDay: event.allDay !== false, recurring: event.recurring || null, category: event.category || 'default' }; this.state.events.get(dateKey).push(newEvent); this.render(); this.emit('eventAdd', newEvent); } removeEvent(eventId) { this.state.events.forEach((events, dateKey) => { const index = events.findIndex(event => event.id === eventId); if (index !== -1) { const removedEvent = events.splice(index, 1)[0]; this.emit('eventRemove', removedEvent); } }); this.render(); } getSelectedDates() { return Array.from(this.state.selectedDates).map(key => this.parseDate(key)); } selectDate(date) { const dateKey = this.getDateKey(date); this.state.selectedDates.add(dateKey); this.updateSelectedDates(); } deselectDate(date) { const dateKey = this.getDateKey(date); this.state.selectedDates.delete(dateKey); this.updateSelectedDates(); } clearSelection() { this.state.selectedDates.clear(); this.updateSelectedDates(); } goToDate(date) { this.state.viewDate = new Date(date); this.render(); } getEvents(date) { const dateKey = this.getDateKey(date); return this.state.events.get(dateKey) || []; } render() { return this.element; } destroy() { super.destroy(); } } export default MusPECalendar;