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
JavaScript
// 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;