UNPKG

synthetic-datepicker

Version:

This is a fast, simple and beauty datepicker library

302 lines (263 loc) 12.6 kB
import pickerUi from '../../infrastructure/view/components/picker/picker.ui' import { DEFAULT_CONFIG } from "../settings/constants" export default class SynDatePicker extends EventTarget { constructor(config: any, $element: Element | null) { super() this.config = Object.assign({}, DEFAULT_CONFIG, config) this.$element = $element if (this.$element !== null) this.init() else throw new Error('') } private initData() { this.MONTHS_MAP.set(0, { monthAbbrev: 'jan', monthLabel: 'January' }) this.MONTHS_MAP.set(1, { monthAbbrev: 'feb', monthLabel: 'February' }) this.MONTHS_MAP.set(2, { monthAbbrev: 'mar', monthLabel: 'March' }) this.MONTHS_MAP.set(3, { monthAbbrev: 'apr', monthLabel: 'April' }) this.MONTHS_MAP.set(4, { monthAbbrev: 'may', monthLabel: 'May' }) this.MONTHS_MAP.set(5, { monthAbbrev: 'jun', monthLabel: 'Juny' }) this.MONTHS_MAP.set(6, { monthAbbrev: 'jul', monthLabel: 'July' }) this.MONTHS_MAP.set(7, { monthAbbrev: 'aug', monthLabel: 'August' }) this.MONTHS_MAP.set(8, { monthAbbrev: 'sep', monthLabel: 'September' }) this.MONTHS_MAP.set(9, { monthAbbrev: 'oct', monthLabel: 'October' }) this.MONTHS_MAP.set(10, { monthAbbrev: 'nov', monthLabel: 'November' }) this.MONTHS_MAP.set(11, { monthAbbrev: 'dec', monthLabel: 'December' }) this.setSelectedDate(this.config.selectedDate) const currentDate = new Date() this.maxYear = currentDate.getFullYear() this.minYear = this.maxYear - 80 } private init() { this.clearElement() this.createInitialDOMComponents() this.attachHandlers() this.initData() } private clearElement() { if (this.$element !== null) this.$element.innerHTML = '' } private createInitialDOMComponents() { this.$datepickerInput = document.createElement('input') as HTMLInputElement this.$datepickerInput.type = 'text' this.$datepickerInput.readOnly = true this.$datepickerInput.className = 'synDatepicker__input' if (this.$element !== null) this.$element.insertAdjacentElement('beforeend', this.$datepickerInput) } private attachHandlers() { this.handlerFocusInput() } private hidePicker() { this.setIsPickerVisible(false) this.$picker.remove() } private showPicker() { this.currentCalendarPosition = {} as any this.setCurrentCalendarPosition(this.getSelectedDate().getMonth(), this.getSelectedDate().getFullYear()) this.setIsPickerVisible(true) this.$picker = document.createElement('div') this.$picker.className = 'synDatepicker__picker' this.$picker.innerHTML = pickerUi if (this.$element !== null) { this.$element.insertAdjacentElement('beforeend', this.$picker) this.renderMonthDates(this.getSelectedDate()) this.attachPickersControlls() } this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month) } private handleChangeMonth(order: number) { const { month, year } = this.getCurrentCalendarPosition() const selectedMonthYear = new Date(year, month + (1 * order)) if(selectedMonthYear.getFullYear() >= this.minYear && selectedMonthYear.getFullYear() <= this.maxYear){ this.renderMonthDates(selectedMonthYear) this.setCurrentCalendarPosition(selectedMonthYear.getMonth(), selectedMonthYear.getFullYear()) this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month) this.changeYearSelectedOption(this.getCurrentCalendarPosition().year) } } private setCurrentCalendarPosition(month: number, year: number) { this.getCurrentCalendarPosition().month = month this.getCurrentCalendarPosition().year = year } private renderYearOptionsCombo() { for (let index = this.minYear; index <= this.maxYear; index++) { this.$datepickerYearCombo?.insertAdjacentHTML('beforeend', `<option value="${index}">${index}</option>`) } } private attachPickersControlls() { const $controls = this.$picker.querySelectorAll('.synDatepicker__control') ?? [] $controls.forEach(($control: Element) => { $control.addEventListener('click', () => { const isNext = $control.closest('.synDatepicker__control--next') !== null this.handleChangeMonth(isNext ? 1 : -1) }) }) setTimeout(() => { this.$datepickerMonthCombo = this.$picker.querySelector<HTMLSelectElement>('.synDatepicker__combo--month') ?? document.createElement('select') this.$datepickerYearCombo = this.$picker.querySelector<HTMLSelectElement>('.synDatepicker__combo--year') ?? document.createElement('select') this.renderYearOptionsCombo() this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month) this.changeYearSelectedOption(this.getCurrentCalendarPosition().year) this.$datepickerMonthCombo.addEventListener('change', () => { if (this.$datepickerMonthCombo !== null) { const selectedMonth = parseInt(this.$datepickerMonthCombo.value) ?? 0 const year = new Date(this.getSelectedDate().getTime()).getFullYear() this.setCurrentCalendarPosition(selectedMonth, year) const monthYearSelected = new Date(year, selectedMonth) this.renderMonthDates(monthYearSelected) } }) this.$datepickerYearCombo.addEventListener('change', () => { if (this.$datepickerYearCombo !== null) { const selectedYear = parseInt(this.$datepickerYearCombo.value) ?? 0 const month = new Date(this.getSelectedDate().getTime()).getMonth() this.setCurrentCalendarPosition(month, selectedYear) const monthYearSelected = new Date(selectedYear, month) this.renderMonthDates(monthYearSelected) } }) }) } private handlerFocusInput() { this.$datepickerInput.addEventListener('focus', (e: FocusEvent) => { if (e.target === e.currentTarget && !(this.isPickerVisible())) this.showPicker() }) document.addEventListener('mousedown', (e: MouseEvent) => { if (this.isPickerVisible() && this.$element !== null && e.target !== this.$element && !(this.$element.contains(e.target as Node))) this.hidePicker() }) } private handleClickDay($cellDay: Element) { const timestamp = window.parseInt($cellDay.getAttribute('data-timestamp') ?? '') this.setSelectedDate(new Date(timestamp)) if (this.getSelectedDate().getMonth() < this.getCurrentCalendarPosition().month) { this.handleChangeMonth(-1) } else if (this.getSelectedDate().getMonth() > this.getCurrentCalendarPosition().month) { this.handleChangeMonth(1) } else { if (this.$selectedDayCell !== null) this.$selectedDayCell.classList.toggle('syncDatepickerCalendar__day--selected', false) $cellDay.classList.toggle('syncDatepickerCalendar__day--selected') this.$selectedDayCell = $cellDay } this.hidePicker() } private getFormattedDates(date: Date): string { return date.toLocaleDateString() } private renderMonthDates(date: Date) { const $tableBody = this.$picker.querySelector('.synDatepicker__body') ?? document.createElement('tbody') const dateCalendarArray = this.getArrayMonthDates(date) const selectedMonth = date.getMonth() $tableBody.addEventListener('click', (e: Event) => { if ((e.target as Element).closest('.syncDatepickerCalendar__day') !== null) this.handleClickDay((e.target as Element).closest('.syncDatepickerCalendar__day') as Element) }) $tableBody.innerHTML = '' dateCalendarArray.forEach((dateWeek: Date[]) => { const $rowFragment = document.createDocumentFragment() $rowFragment.append(...dateWeek.map((date: Date) => { const $tableCell = document.createElement('td') if (this.cellIsSelected(date)) this.$selectedDayCell = $tableCell $tableCell.textContent = `${date.getDate()}` $tableCell.setAttribute('data-timestamp', `${date.getTime()}`) $tableCell.className = `syncDatepicker__cell syncDatepickerCalendar__day ${this.cellIsSelected(date) ? 'syncDatepickerCalendar__day--selected' : ('')} syncDatepicker__cell--body${selectedMonth !== date.getMonth() ? ' syncDatepickerCalendar__day--diff' : ''}` return $tableCell })) const $tableRow = document.createElement('tr') $tableRow.className = 'synDatepicker__row synDatepicker__row--body' $tableRow.append($rowFragment) $tableBody.append($tableRow) }) } private getArrayMonthDates(calendar: Date) { const selectedMonthCalendar = new Date(calendar.getFullYear(), calendar.getMonth(), 1) selectedMonthCalendar.setDate(1) const month = selectedMonthCalendar.getMonth() const year = selectedMonthCalendar.getFullYear() const nextMonthCalendar = new Date(year, month + 1, 1) nextMonthCalendar.setDate(nextMonthCalendar.getDate() - 1) const lastDayOfMonth = nextMonthCalendar.getDate() nextMonthCalendar.setDate(nextMonthCalendar.getDate() + 1) let dateArray: Date[][] = [new Array(7)] let firstMonthDayWeek = 0 let lastMonthDayWeek = 6 let week = 0 for (let index = 1; index <= lastDayOfMonth; index++) { const monthDate = new Date(year, month, index) const weekDay = monthDate.getDay() if (index === 1) firstMonthDayWeek = weekDay dateArray[week][weekDay] = monthDate if (weekDay === 6 && index < lastDayOfMonth) { week++ dateArray[week] = new Array(7) } if (index === lastDayOfMonth) lastMonthDayWeek = weekDay } const previousMonthCalendar = new Date(selectedMonthCalendar.setDate(selectedMonthCalendar.getDate() - firstMonthDayWeek)) for (let index = 0; index < firstMonthDayWeek; index++) { dateArray[0][index] = new Date(previousMonthCalendar.getTime()) previousMonthCalendar.setDate(previousMonthCalendar.getDate() + 1) } for (let index = lastMonthDayWeek + 1; index < 7; index++) { dateArray[week][index] = new Date(nextMonthCalendar.getTime()) nextMonthCalendar.setDate(nextMonthCalendar.getDate() + 1) } return dateArray } public onChange(handler: EventListenerOrEventListenerObject) { this.addEventListener('datepicker-changing', handler) } private renderSelectedDates() { this.$datepickerInput.value = this.getFormattedDates(this.getSelectedDate()) } private isSelectedDate(date: Date) { return this.selectedDate === undefined || (this.getSelectedDate().getDate() === date.getDate() && this.getSelectedDate().getMonth() === date.getMonth() && this.getSelectedDate().getFullYear() === date.getFullYear()) } private cellIsSelected(date: Date) { return this.isSelectedDate(date) } private dispatchDatepickerEvent() { const datepickerChangingEvent = new CustomEvent('datepicker-changing') this.dispatchEvent(datepickerChangingEvent) } private changeMonthSelectedOption(selectedMonth: number) { if (this.$datepickerMonthCombo !== null) { this.$datepickerMonthCombo.value = `${selectedMonth}` } } private changeYearSelectedOption(selectedYear: number) { if (this.$datepickerYearCombo !== null) { this.$datepickerYearCombo.value = `${selectedYear}` } } public setSelectedDate(selectedDate: Date) { if (!this.isSelectedDate(selectedDate)) { this.dispatchDatepickerEvent() } this.selectedDate = selectedDate this.renderSelectedDates() } public getSelectedDate() { return this.selectedDate } public isPickerVisible() { return this.visible } private setIsPickerVisible(visible: boolean) { this.visible = visible } private getCurrentCalendarPosition() { return this.currentCalendarPosition } private MONTHS_MAP: Map<number, { monthLabel: string, monthAbbrev: string }> = new Map() private visible: boolean = false private config: any private $picker!: Element private minYear!: number private maxYear!: number private selectedDate!: Date private $datepickerMonthCombo: HTMLSelectElement | null = null private $datepickerYearCombo: HTMLSelectElement | null = null private $selectedDayCell: Element | null = null private currentCalendarPosition!: { month: number, year: number } private $datepickerInput!: HTMLInputElement private $element: Element | null }