Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/ui/date-picker/src/ |
Upload File : |
import { Dom, Tag, Loc, Text } from 'main.core'; import { DateTimeFormat } from 'main.date'; import { MemoryCache } from 'main.core.cache'; import { BasePicker } from './base-picker'; import { addDate } from './helpers/add-date'; import { ceilDate } from './helpers/ceil-date'; import { cloneDate } from './helpers/clone-date'; import { createUtcDate } from './helpers/create-utc-date'; import { floorDate } from './helpers/floor-date'; import { isDatesEqual } from './helpers/is-dates-equal'; import { type BaseCache } from 'main.core.cache'; import { type DayMark } from './date-picker-options'; export type DayPickerMonth = { weeks: Array<DayPickerWeek>, date: Date, }; export type DayPickerWeek = Array<DayPickerDay>; export type DayPickerDay = { day: number, month: number, year: number, date: Date, outside: boolean, hidden: boolean, current: boolean, selected: boolean, dayOff: boolean, rangeFrom: boolean, rangeTo: boolean, rangeIn: boolean, rangeInStart: boolean, rangeInEnd: boolean, rangeInSelected: boolean, rangeSelected: boolean, focused: boolean, tabIndex: number, bgColor: string | null, textColor: string | null, marks: string[], }; import './css/day-picker.css'; export class DayPicker extends BasePicker { #refs: BaseCache<HTMLElement> = new MemoryCache(); #weekdays: string[] = null; #mouseOutTimeout: number = null; getContainer(): HTMLElement { return this.#refs.remember('container', () => { return Tag.render` <div class="ui-day-picker${this.getDatePicker().isFullYear() ? ' --full-year' : ''}"> ${this.getHeader()} ${this.getContentContainer(this.getMonthContainer())} ${ this.getDatePicker().isTimeEnabled() ? (this.getDatePicker().isRangeMode() ? this.getTimeRangeContainer() : this.getTimeContainer()) : null } </div> `; }); } getHeader(): HTMLElement { const numberOfMonths = this.getDatePicker().getNumberOfMonths(); if (this.getDatePicker().isFullYear()) { return this.getHeaderContainer( this.getPrevBtn(), Tag.render` <div class="ui-date-picker-header-title"> ${this.getFullYearHeader()} </div> `, this.getNextBtn(), ); } return this.getHeaderContainer( this.getPrevBtn(), ...Array.from({ length: numberOfMonths }).map((_, monthNumber: number) => { return Tag.render` <div class="ui-date-picker-header-title"> ${this.getHeaderMonth(monthNumber)} ${this.getHeaderYear(monthNumber)} </div> `; }), this.getNextBtn(), ); } getFullYearHeader(): HTMLElement { return this.#refs.remember('header-full-year', () => { return Tag.render` <span class="ui-date-picker-header-full-year"></span> `; }); } getHeaderMonth(monthNumber: number): HTMLElement { return this.#refs.remember(`header-month-${monthNumber}`, () => { return Tag.render` <button type="button" class="ui-date-picker-header-month" onclick="${this.#handleMonthClick.bind(this)}"></button> `; }); } getMonthContainer(): HTMLElement { return this.#refs.remember('month-container', () => { return Tag.render` <div class="ui-day-picker-content" onclick="${this.#handleDayClick.bind(this)}" onmouseover="${this.#handleDayMouseOver.bind(this)}" onmouseout="${this.#handleDayMouseOut.bind(this)}" ></div> `; }); } getHeaderYear(monthNumber: number): HTMLElement { return this.#refs.remember(`header-year-${monthNumber}`, () => { return Tag.render` <button type="button" class="ui-date-picker-header-year" onclick="${this.#handleYearClick.bind(this)}"></button> `; }); } getTimeContainer(): HTMLElement { return this.#refs.remember('date-time-container', () => { return Tag.render` <div class="ui-date-picker-time-container"> <button type="button" class="ui-date-picker-time-box" onclick="${this.#handleTimeClick.bind(this)}"> <span class="ui-date-picker-time-clock"></span> ${this.getTimeValueContainer()} </button> </div> `; }); } getTimeRangeContainer(): HTMLElement { return this.#refs.remember('range-time-container', () => { return Tag.render` <div class="ui-date-picker-time-container --range"> <div class="ui-date-picker-time-range-slot"> <button type="button" class="ui-date-picker-time-box --range-start" onclick="${this.#handleTimeRangeStartClick.bind(this)}" > <span class="ui-date-picker-time-clock"></span> ${this.getTimeRangeStartContainer()} </button> </div> <div class="ui-date-picker-time-range-slot"> <button type="button" class="ui-date-picker-time-box --range-end" onclick="${this.#handleTimeRangeEndClick.bind(this)}" > <span class="ui-date-picker-time-clock"></span> ${this.getTimeRangeEndContainer()} </button> </div> </div> `; }); } getTimeValueContainer(): HTMLElement { return this.#refs.remember('time-value', () => { return Tag.render`<div class="ui-date-picker-time-value"></div>`; }); } getTimeRangeStartContainer(): HTMLElement { return this.#refs.remember('time-range-start', () => { return Tag.render`<div class="ui-date-picker-time-value"></div>`; }); } getTimeRangeEndContainer(): HTMLElement { return this.#refs.remember('time-range-end', () => { return Tag.render`<div class="ui-date-picker-time-value"></div>`; }); } getWeekDays(): string[] { if (this.#weekdays !== null) { return this.#weekdays; } const firstWeekDay: number = this.getDatePicker().getFirstWeekDay(); const weekDays: string[] = [ Loc.getMessage('DOW_0'), Loc.getMessage('DOW_1'), Loc.getMessage('DOW_2'), Loc.getMessage('DOW_3'), Loc.getMessage('DOW_4'), Loc.getMessage('DOW_5'), Loc.getMessage('DOW_6'), ]; this.#weekdays = [ ...[...weekDays].slice(firstWeekDay), ...[...weekDays].splice(0, firstWeekDay), ]; return this.#weekdays; } #renderMonthContainer(monthNumber: number): HTMLElement { const cacheId = `month-${monthNumber}`; if (!this.#refs.has(cacheId)) { const monthContainer = Tag.render`<div class="ui-day-picker-month"></div>`; this.#refs.set(cacheId, monthContainer); Dom.append(monthContainer, this.getMonthContainer()); } return this.#refs.get(cacheId); } #renderMonthHeader(monthNumber: number, monthContainer: HTMLElement): HTMLElement { return this.#refs.remember(`month-header-${monthNumber}`, () => { const monthName = DateTimeFormat.format('f', createUtcDate(2000, monthNumber), null, true); const container = Tag.render`<div class="ui-day-picker-month-header">${Text.encode(monthName)}</div>`; Dom.append(container, monthContainer); return container; }); } #renderWeekDays(monthNumber: number, monthContainer: HTMLElement): HTMLElement { return this.#refs.remember(`week-day-${monthNumber}`, () => { const weekDayContainer = Tag.render`<div class="ui-day-picker-week --week-days"></div>`; Dom.append(weekDayContainer, monthContainer); if (this.getDatePicker().shouldShowWeekNumbers()) { const dayContainer = Tag.render`<div class="ui-day-picker-week-day"></div>`; Dom.append(dayContainer, weekDayContainer); } this.getWeekDays().forEach((weekDayName: string) => { const dayContainer = Tag.render`<div class="ui-day-picker-week-day">${Text.encode(weekDayName)}</div>`; Dom.append(dayContainer, weekDayContainer); }); return weekDayContainer; }); } #renderWeek(monthNumber: number, weekNumber: number, monthContainer: HTMLElement): HTMLElement { return this.#refs.remember(`week-${monthNumber}-${weekNumber}`, () => { const weekContainer = Tag.render`<div class="ui-day-picker-week"></div>`; Dom.append(weekContainer, monthContainer); return weekContainer; }); } #renderWeekNumber(monthNumber: number, weekNumber: number, week: DayPickerWeek, weekContainer: HTMLElement): void { const container = this.#refs.remember(`week-number-${monthNumber}-${weekNumber}`, () => { const weekNumberContainer = Tag.render`<div class="ui-day-picker-week-number">${ DateTimeFormat.format('W', week[0].date, null, true) }</div>` ; Dom.append(weekNumberContainer, weekContainer); return weekNumberContainer; }); container.textContent = DateTimeFormat.format('W', week[0].date, null, true); } #renderDay(id: string, day: DayPickerDay, weekContainer: HTMLElement): HTMLElement { const button: HTMLElement = this.#refs.remember(id, () => { const dayContainer = Tag.render` <button type="button" class="ui-day-picker-day" data-day="${day.day}" data-month="${day.month}" data-year="${day.year}" data-tab-priority="true" role="gridcell" > <span class="ui-day-picker-day-inner">${day.day}</span> <span class="ui-day-picker-day-marks"></span> </button> `; Dom.append(dayContainer, weekContainer); return dayContainer; }); const currentDay: number = Number(button.dataset.day); const currentMonth: number = Number(button.dataset.month); const currentYear: number = Number(button.dataset.year); if (currentDay !== day.day || currentMonth !== day.month || currentYear !== day.year) { button.dataset.day = day.day; button.dataset.month = day.month; button.dataset.year = day.year; button.firstElementChild.textContent = day.day; } const statuses = { '--outside': day.outside, '--current': !day.outside && day.current, '--day-off': !day.outside && day.dayOff, '--selected': day.selected, '--hidden': day.hidden, '--range-from': day.rangeFrom, '--range-to': day.rangeTo, '--range-in': day.rangeIn, '--range-in-start': day.rangeInStart, '--range-in-end': day.rangeInEnd, '--range-in-selected': day.rangeInSelected, '--range-selected': day.rangeSelected, '--focused': day.focused, }; let classNames = 'ui-day-picker-day'; for (const [className, enabled] of Object.entries(statuses)) { if (enabled) { classNames = `${classNames} ${className}`; } } if (button.className !== classNames) { button.className = classNames; } // Day Colors const currentBgColor: string | null = button.dataset.bgColor || null; const currentTextColor: string | null = button.dataset.textColor || null; if (currentBgColor !== day.bgColor) { Dom.style(button.firstElementChild, '--ui-day-picker-day-bg-color', day.bgColor); Dom.attr(button, 'data-bg-color', day.bgColor); } if (currentTextColor !== day.textColor) { Dom.style(button.firstElementChild, '--ui-day-picker-day-text-color', day.textColor); Dom.attr(button, 'data-text-color', day.textColor); } // Day Marks const currentMarks: string = button.dataset.marks || ''; if (currentMarks !== day.marks.toString()) { Dom.clean(button.lastElementChild); if (day.marks.length > 0) { for (const mark of day.marks) { Dom.append( Tag.render` <span class="ui-day-picker-day-mark" style="background-color: ${mark}"></span> `, button.lastElementChild, ); } } Dom.attr(button, 'data-marks', day.marks.toString()); } button.tabIndex = day.tabIndex; return button; } #renderTime(): void { if (this.getDatePicker().isRangeMode()) { const rangeStart = this.getDatePicker().getRangeStart(); const startBtn: HTMLButtonElement = this.getTimeRangeStartContainer().parentNode; if (rangeStart === null) { Dom.removeClass(this.getTimeRangeContainer(), '--range-start-set'); startBtn.disabled = true; } else { Dom.addClass(this.getTimeRangeContainer(), '--range-start-set'); startBtn.disabled = false; this.getTimeRangeStartContainer().textContent = this.getDatePicker().formatTime(rangeStart); } const rangeEnd = this.getDatePicker().getRangeEnd(); const endBtn: HTMLButtonElement = this.getTimeRangeEndContainer().parentNode; if (rangeEnd === null) { Dom.removeClass(this.getTimeRangeContainer(), '--range-end-set'); endBtn.disabled = true; } else { Dom.addClass(this.getTimeRangeContainer(), '--range-end-set'); endBtn.disabled = false; this.getTimeRangeEndContainer().textContent = this.getDatePicker().formatTime(rangeEnd); } } else { const selectedDate = this.getDatePicker().getSelectedDate(); const button: HTMLButtonElement = this.getTimeContainer().firstElementChild; if (selectedDate === null) { Dom.removeClass(this.getTimeContainer(), '--time-set'); button.disabled = true; } else { Dom.addClass(this.getTimeContainer(), '--time-set'); button.disabled = false; this.getTimeValueContainer().textContent = this.getDatePicker().formatTime(selectedDate); } } } render(): void { let focusButton: HTMLElement = null; const isFocused = this.getDatePicker().isFocused(); this.getMonths().forEach((month: DayPickerMonth, monthNumber: number) => { if (this.getDatePicker().isFullYear()) { this.getFullYearHeader().textContent = DateTimeFormat.format('Y', month.date, null, true); } else { this.getHeaderMonth(monthNumber).textContent = DateTimeFormat.format('f', month.date, null, true); this.getHeaderYear(monthNumber).textContent = DateTimeFormat.format('Y', month.date, null, true); } const monthContainer = this.#renderMonthContainer(monthNumber); if (this.getDatePicker().isFullYear()) { this.#renderMonthHeader(monthNumber, monthContainer); } if (this.getDatePicker().shouldShowWeekDays()) { this.#renderWeekDays(monthNumber, monthContainer); } month.weeks.forEach((week: DayPickerWeek, weekNumber) => { const weekContainer = this.#renderWeek(monthNumber, weekNumber, monthContainer); if (this.getDatePicker().shouldShowWeekNumbers()) { this.#renderWeekNumber(monthNumber, weekNumber, week, weekContainer); } week.forEach((day: DayPickerDay, dayIndex) => { const id = `day-${monthNumber}-${weekNumber}-${dayIndex}`; const button = this.#renderDay(id, day, weekContainer); if (day.focused) { focusButton = button; } }); }); }); if (focusButton !== null && isFocused) { focusButton.focus({ preventScroll: true }); } if (this.getDatePicker().isTimeEnabled()) { this.#renderTime(); } } getMonths(): DayPickerMonth[] { const months = []; const picker = this.getDatePicker(); let date = picker.getViewDate(); const numberOfMonths = picker.getNumberOfMonths(); const today = picker.getToday(); const focusDate = picker.getFocusDate(); const initialFocusDate = this.getDatePicker().getInitialFocusDate(); const showOutsideDays = picker.shouldShowOutsideDays(); const { year, month } = picker.getViewDateParts(); const firstAvailableDay = createUtcDate(year, month); const lastAvailableDay = ceilDate(createUtcDate(year, month + numberOfMonths - 1), 'month'); const [from, to] = this.#getRangeDates(); const rangeSelected = ( picker.isRangeMode() && picker.getRangeStart() !== null && picker.getRangeEnd() !== null ); for (let index = 0; index < numberOfMonths; index++) { const weeks = []; const firstMonthDay = floorDate(date, 'month'); const currentMonthIndex = date.getUTCMonth(); date = this.#getStartMonthDate(date); for (let weekIndex = 0; weekIndex < 6; weekIndex++) { const week = []; let prevDay: DayPickerDay = null; for (let weekDay = 0; weekDay < 7; weekDay++) { let available = true; const outside = date.getUTCMonth() !== currentMonthIndex; if (outside) { if (showOutsideDays && numberOfMonths > 1) { available = date.getTime() < firstAvailableDay || date.getTime() >= lastAvailableDay; } else if (!showOutsideDays) { available = false; } } const selected = available && picker.isDateSelected(date, 'day'); const rangeFrom = available && from && to && isDatesEqual(date, from); const rangeTo = available && from && to && isDatesEqual(date, to); const rangeIn = ( available && from && to && (rangeFrom || rangeTo || (date.getTime() >= from.getTime() && date.getTime() <= to.getTime())) ); const rangeInStart = rangeIn && (weekDay === 0 || !prevDay.rangeIn); const rangeInEnd = rangeIn && weekDay === 6; if (!rangeIn && prevDay && prevDay.rangeIn) { prevDay.rangeInEnd = true; } const rangeInSelected = selected && rangeIn && !rangeFrom && !rangeTo; const focused = available && isDatesEqual(date, focusDate, 'day'); const tabIndex = ( available && (isDatesEqual(date, focusDate, 'day') || isDatesEqual(date, initialFocusDate, 'day')) ? 0 : -1 ); const dayColor = this.getDatePicker().getDayColor(date); const marks = this.getDatePicker().getDayMarks(date).map( (dayMark: DayMark): string => { return dayMark.bgColor; }, ); const day: DayPickerDay = { date: cloneDate(date), day: date.getUTCDate(), month: date.getUTCMonth(), year: date.getUTCFullYear(), outside, current: isDatesEqual(date, today, 'day'), selected, hidden: outside && !showOutsideDays, dayOff: picker.isDayOff(date), rangeSelected: selected && rangeSelected, focused, tabIndex, rangeFrom, rangeTo, rangeIn, rangeInStart, rangeInEnd, rangeInSelected, bgColor: dayColor === null ? null : dayColor.bgColor, textColor: dayColor === null ? null : dayColor.textColor, marks, }; week.push(day); prevDay = day; date = addDate(date, 'day', 1); } weeks.push(week); } months.push({ weeks, date: firstMonthDay }); } return months; } #getStartMonthDate(date: Date): Date { const picker = this.getDatePicker(); const firstWeekDay: number = picker.getFirstWeekDay(); const firstMonthDay = floorDate(date, 'month'); let daysFromPrevMonth = firstMonthDay.getUTCDay() - firstWeekDay; daysFromPrevMonth = daysFromPrevMonth < 0 ? daysFromPrevMonth + 7 : daysFromPrevMonth; return addDate(firstMonthDay, 'day', -daysFromPrevMonth); } getFirstDay(): DayPickerDay { const viewDate = this.getDatePicker().getViewDate(); const currentMonthIndex = viewDate.getUTCMonth(); const showOutsideDays = this.getDatePicker().shouldShowOutsideDays(); const firstViewDay = this.#getStartMonthDate(this.getDatePicker().getViewDate()); const outside = firstViewDay.getUTCMonth() !== currentMonthIndex; if (outside && !showOutsideDays) { return floorDate(viewDate, 'month'); } return firstViewDay; } getLastDay(): DayPickerDay | null { const numberOfMonths = this.getDatePicker().getNumberOfMonths(); const showOutsideDays = this.getDatePicker().shouldShowOutsideDays(); const { year, month } = this.getDatePicker().getViewDateParts(); let lastAvailableDay = ceilDate(createUtcDate(year, month + numberOfMonths - 1), 'month'); if (showOutsideDays) { const firstAvailableDay = createUtcDate(year, month + numberOfMonths - 1); const firstViewDay = this.#getStartMonthDate(firstAvailableDay); lastAvailableDay = addDate(firstViewDay, 'day', 6 * 7); } return lastAvailableDay; } #getRangeDates(): Array { let from: Date = null; let to: Date = null; const focusDate = this.getDatePicker().getFocusDate(); if (this.getDatePicker().isRangeMode()) { const range = this.getDatePicker().getSelectedDates(); from = range[0] || null; to = range[1] || null; if (focusDate !== null) { if (range.length === 1) { if (focusDate > from.getTime()) { to = focusDate; } else { to = from; from = focusDate; } } /* else if (range.length === 2) { if (focusDate > to.getTime()) { to = focusDate; } else if (focusDate < from.getTime()) { from = focusDate; } } */ } } return [from, to]; } #handleDayClick(event: MouseEvent): void { const dayElement = event.target.closest('.ui-day-picker-day'); if (dayElement === null) { return; } const dataset = dayElement.dataset; const year = Text.toInteger(dataset.year); const month = Text.toInteger(dataset.month); const day = Text.toInteger(dataset.day); this.emit('onSelect', { year, month, day }); } #handleDayMouseOver(event: MouseEvent): void { const dayElement = event.target.closest('.ui-day-picker-day'); if (dayElement === null) { const weekElement = event.target.closest('.ui-day-picker-week'); if ( weekElement !== null && this.#mouseOutTimeout !== null && this.getDatePicker().getSelectedDates().length === 1 ) { clearTimeout(this.#mouseOutTimeout); } return; } if (this.#mouseOutTimeout !== null) { clearTimeout(this.#mouseOutTimeout); } const dataset = dayElement.dataset; const year = Text.toInteger(dataset.year); const month = Text.toInteger(dataset.month); const day = Text.toInteger(dataset.day); this.emit('onFocus', { year, month, day }); } #handleDayMouseOut(event: MouseEvent): void { if (this.#mouseOutTimeout !== null) { clearTimeout(this.#mouseOutTimeout); } this.#mouseOutTimeout = setTimeout(() => { this.emit('onBlur'); this.#mouseOutTimeout = null; }, 100); } #handleMonthClick(): void { this.emit('onMonthClick'); } #handleYearClick(): void { this.emit('onYearClick'); } #handleTimeClick(): void { const selectedDate = this.getDatePicker().getSelectedDate(); if (selectedDate !== null) { this.emit('onTimeClick'); } } #handleTimeRangeStartClick(): void { const rangeStart = this.getDatePicker().getRangeStart(); if (rangeStart !== null) { this.emit('onRangeStartClick'); } } #handleTimeRangeEndClick(): void { const rangeEnd = this.getDatePicker().getRangeEnd(); if (rangeEnd !== null) { this.emit('onRangeEndClick'); } } }