import { addEventListeners } from "./modules/addEventListeners"; import { configureStylePreferences } from "./modules/stylePreference"; import * as picker from "./modules/picker/picker"; import * as monthPicker from "./modules/picker/month/monthPicker"; import * as yearPicker from "./modules/picker/year/yearPicker"; import * as header from "./modules/header/header"; import * as weekday from "./modules/weekday/weekday"; import * as day from "./modules/day/day"; import * as events from "./modules/events/events"; import { CalendarSize, LayoutModifier, CalendarOptions, EventData, Day, MonthDisplayType, WeekdayDisplayType, Weekdays, StartWeekday, } from "./types.d"; export default class Calendar { /* Constants */ readonly CAL_NAME = 'color-calendar'; readonly DAYS_TO_DISPLAY = 42; /* Options */ id: string; start?: Date; end?: Date; selectInitialDate?: boolean; calendarSize: CalendarSize; layoutModifiers: LayoutModifier[]; eventsData: EventData[]; theme: string; primaryColor?: string; headerColor?: string; headerBackgroundColor?: string; weekdaysColor?: string; weekdayDisplayType: WeekdayDisplayType; monthDisplayType: MonthDisplayType; startWeekday: StartWeekday; fontFamilyHeader?: string; fontFamilyWeekdays?: string; fontFamilyBody?: string; dropShadow?: string; border?: string; borderRadius?: string; disableMonthYearPickers: boolean; disableDayClick: boolean; disableMonthArrowClick: boolean; customMonthValues?: string[]; customWeekdayValues?: string[]; monthChanged?: (currentDate?: Date, filteredMonthEvents?: EventData[]) => void; dateChanged?: (currentDate?: Date, filteredDateEvents?: EventData[]) => void; selectedDateClicked?: (currentDate?: Date, filteredDateEvents?: EventData[]) => void; /* State */ weekdayDisplayTypeOptions = { "short": ["S", "M", "T", "W", "T", "F", "S"] as Weekdays, "long-lower": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as Weekdays, "long-upper": ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"] as Weekdays, } weekdays: Weekdays; today: Date; currentDate: Date; pickerType: string; eventDayMap: any; oldSelectedNode: [HTMLElement, number] | null; filteredEventsThisMonth: EventData[]; daysIn_PrevMonth: Day[]; daysIn_CurrentMonth: Day[]; daysIn_NextMonth: Day[]; firstDay_PrevMonth: StartWeekday; firstDay_CurrentMonth: StartWeekday; firstDay_NextMonth: StartWeekday; numOfDays_PrevMonth: number; numOfDays_CurrentMonth: number; numOfDays_NextMonth: number; yearPickerOffset: number; yearPickerOffsetTemporary: number; /* Elements */ calendar: HTMLElement; calendarRoot: HTMLElement; calendarHeader: HTMLElement; calendarWeekdays: HTMLElement; calendarDays: HTMLElement; prevButton: HTMLElement; nextButton: HTMLElement; pickerContainer: HTMLElement; pickerMonthContainer: HTMLElement; pickerYearContainer: HTMLElement; yearPickerChevronLeft: HTMLElement; yearPickerChevronRight: HTMLElement; monthyearDisplay: HTMLElement; monthDisplay: HTMLElement; yearDisplay: HTMLElement; /* Methods */ // Event Listeners addEventListeners!: () => void; // Style Preference configureStylePreferences!: () => void; // Picker togglePicker!: (shouldOpen?: boolean) => void; // Picker - Month handleMonthPickerClick!: (e: any) => void; updateMonthPickerSelection!: (newMonthValue: number) => void; removeMonthPickerSelection!: () => void; // Picker - Year handleYearPickerClick!: (e: any) => void; updateYearPickerSelection!: (newYearValue: number, newYearIndex?: number) => void; updateYearPickerTodaySelection!: () => void; removeYearPickerSelection!: () => void; generatePickerYears!: () => void; handleYearChevronLeftClick!: () => void; handleYearChevronRightClick!: () => void; // Header setMonthDisplayType!: (monthDisplayType: MonthDisplayType) => void; handleMonthYearDisplayClick!: (e: any) => void; handlePrevMonthButtonClick!: () => void; handleNextMonthButtonClick!: () => void; updateMonthYear!: () => void; // Weekday setWeekdayDisplayType!: (weekdayDisplayType: WeekdayDisplayType) => void; generateWeekdays!: () => void; // Day setDate!: (date: Date) => void; getSelectedDate!: () => Date; clearCalendarDays!: () => void; updateCalendar!: (isMonthChanged?: boolean) => void; setOldSelectedNode!: () => void; selectDayInitial!: (setDate?: boolean) => void; handleCalendarDayClick!: (e: any) => void; removeOldDaySelection!: () => void; updateCurrentDate!: (monthOffset: number, newDay?: number, newMonth?: number, newYear?: number) => void; generateDays!: () => void; renderDays!: () => void; rerenderSelectedDay!: (element: HTMLElement, dayNum: number, storeOldSelected?: boolean) => void; // Methods getEventsData!: () => any; setEventsData!: (events: EventData[]) => number; addEventsData!: (newEvents?: EventData[]) => number; getDateEvents!: (date: Date) => EventData[]; getMonthEvents!: () => EventData[]; stepInfo = { next: { year: true, month: true }, previous: { year: true, month: true } }; constructor(options: CalendarOptions = {}) { /* Initialize Options */ this.id = options.id ?? "#color-calendar"; this.selectInitialDate = options.selectInitialDate ?? true; this.calendarSize = (options.calendarSize ?? "large") as CalendarSize; this.layoutModifiers = options.layoutModifiers ?? []; this.eventsData = options.eventsData ?? []; this.theme = options.theme ?? "basic"; this.primaryColor = options.primaryColor; this.headerColor = options.headerColor; this.headerBackgroundColor = options.headerBackgroundColor; this.weekdaysColor = options.weekdaysColor; this.weekdayDisplayType = (options.weekdayDisplayType ?? "long-lower") as WeekdayDisplayType; this.monthDisplayType = (options.monthDisplayType ?? "long") as MonthDisplayType; this.startWeekday = options.startWeekday ?? 0; // 0 (Sun), 1 (Mon), 2 (Tues), 3 (Wed), 4 (Thu), 5 (Fri), 6 (Sat) this.fontFamilyHeader = options.fontFamilyHeader; this.fontFamilyWeekdays = options.fontFamilyWeekdays; this.fontFamilyBody = options.fontFamilyBody; this.dropShadow = options.dropShadow; this.border = options.border; this.borderRadius = options.borderRadius; this.disableMonthYearPickers = options.disableMonthYearPickers ?? false; this.disableDayClick = options.disableDayClick ?? false; this.disableMonthArrowClick = options.disableMonthArrowClick ?? false; this.customMonthValues = options.customMonthValues; this.customWeekdayValues = options.customWeekdayValues; this.monthChanged = options.monthChanged; this.dateChanged = options.dateChanged; this.selectedDateClicked = options.selectedDateClicked; /* Initialize State */ if (this.customWeekdayValues && this.customWeekdayValues.length === 7) { this.weekdays = this.customWeekdayValues as Weekdays; } else { this.weekdays = this.weekdayDisplayTypeOptions[this.weekdayDisplayType] ?? this.weekdayDisplayTypeOptions["short"]; } this.today = new Date(); this.currentDate = options.currentDate || new Date(); // TODO range: // this.range: days number [-7, 7] this.start = options.startMonth ? new Date(options.startMonth.getFullYear(), options.startMonth.getMonth() - 1, 1) : undefined this.end = options.endMonth ? new Date(options.endMonth.getFullYear(), options.endMonth.getMonth(), 0) : undefined if (this.start && (this.start > this.currentDate)) throw new Error('The current date cannot be less than the starting point') if (this.end && (this.end < this.currentDate)) throw new Error('The current date cannot be greater than the endpoint'); ['start', 'end'].forEach((it: string) => { const prop = it as keyof Calendar; if (this[prop]) { if (this.currentDate.getFullYear() == this[prop].getFullYear()) { const switchKey = prop == 'start' ? 'previous' : 'next'; this.stepInfo[switchKey].year = false if (this.currentDate.getMonth() == this[prop].getMonth()) this.stepInfo[switchKey].month = false } } }, this) this.pickerType = 'month'; this.eventDayMap = {}; this.oldSelectedNode = null; this.filteredEventsThisMonth = []; this.daysIn_PrevMonth = []; this.daysIn_CurrentMonth = []; this.daysIn_NextMonth = []; this.firstDay_PrevMonth = 0; this.firstDay_CurrentMonth = 0; this.firstDay_NextMonth = 0; this.numOfDays_PrevMonth = 0; this.numOfDays_CurrentMonth = 0; this.numOfDays_NextMonth = 0; this.yearPickerOffset = 0; this.yearPickerOffsetTemporary = 0; // Check if HTML element with given selector exists in DOM this.calendar = document.querySelector(this.id) as HTMLElement; if (!this.calendar) { throw new Error(`[COLOR-CALENDAR] Element with selector '${this.id}' not found`); } // Initialize initial HTML layout this.calendar.innerHTML = `