UNPKG

@schedule-x/date-picker

Version:

Schedule-X date picker component

1,320 lines (1,264 loc) 92.7 kB
'use strict'; var preact = require('preact'); var jsxRuntime = require('preact/jsx-runtime'); var hooks = require('preact/hooks'); var compat = require('preact/compat'); var signals = require('@preact/signals'); const AppContext = preact.createContext({}); var img = "data:image/svg+xml,%3c%3fxml version='1.0' encoding='utf-8'%3f%3e%3c!-- Uploaded to: SVG Repo%2c www.svgrepo.com%2c Generator: SVG Repo Mixer Tools --%3e%3csvg width='800px' height='800px' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M6 9L12 15L18 9' stroke='%23B8B5B8' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"; /** * Can be used for generating a random id for an entity * Should, however, never be used in potentially resource intense loops, * since the performance cost of this compared to new Date().getTime() is ca x4 in v8 * */ const randomStringId = () => 's' + Math.random().toString(36).substring(2, 11); const isKeyEnterOrSpace = (keyboardEvent) => keyboardEvent.key === 'Enter' || keyboardEvent.key === ' '; function AppInput() { const datePickerInputId = randomStringId(); const datePickerLabelId = randomStringId(); const inputWrapperId = randomStringId(); const $app = hooks.useContext(AppContext); const [wrapperClasses, setWrapperClasses] = hooks.useState([]); const setInputElement = () => { const inputWrapperEl = document.getElementById(inputWrapperId); $app.datePickerState.inputWrapperElement.value = inputWrapperEl instanceof HTMLDivElement ? inputWrapperEl : undefined; }; hooks.useEffect(() => { if ($app.config.teleportTo) setInputElement(); const newClasses = ['sx__date-input-wrapper']; if ($app.datePickerState.isOpen.value) newClasses.push('sx__date-input--active'); setWrapperClasses(newClasses); }, [$app.datePickerState.isOpen.value]); const handleKeyUp = (event) => { if (event.key === 'Enter') handleInputValue(event); }; const handleInputValue = (event) => { event.stopPropagation(); // prevent date picker from closing try { $app.datePickerState.handleInput(event.target.value); $app.datePickerState.close(); } catch (e) { console.log('Error setting input value:' + e); } }; hooks.useEffect(() => { const inputElement = typeof document !== 'undefined' && document.getElementById(datePickerInputId); if (typeof HTMLElement === 'undefined' || !(inputElement instanceof HTMLElement)) return; inputElement.addEventListener('change', handleInputValue); // Preact onChange triggers on every input return () => inputElement.removeEventListener('change', handleInputValue); }); hooks.useEffect(() => { if ($app.config.hasPlaceholder) { $app.datePickerState.inputDisplayedValue.value = $app.translate('MM/DD/YYYY'); } }, []); const handleClick = () => { $app.datePickerState.open(); }; const handleButtonKeyDown = (keyboardEvent) => { if (isKeyEnterOrSpace(keyboardEvent)) { keyboardEvent.preventDefault(); $app.datePickerState.open(); setTimeout(() => { const element = document.querySelector('[data-focus="true"]'); if (element instanceof HTMLElement) element.focus(); }, 50); } }; return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("div", { className: wrapperClasses.join(' '), id: inputWrapperId, children: [jsxRuntime.jsx("label", { for: datePickerInputId, id: datePickerLabelId, className: "sx__date-input-label", children: $app.config.label || $app.translate('Date') }), jsxRuntime.jsx("input", { id: datePickerInputId, tabIndex: $app.datePickerState.isDisabled.value ? -1 : 0, name: $app.config.name || 'date', "aria-describedby": datePickerLabelId, value: $app.datePickerState.inputDisplayedValue.value, "data-testid": "date-picker-input", className: "sx__date-input", onClick: handleClick, onKeyUp: handleKeyUp, type: "text" }), jsxRuntime.jsx("button", { type: "button", tabIndex: $app.datePickerState.isDisabled.value ? -1 : 0, "aria-label": $app.translate('Choose Date'), onKeyDown: handleButtonKeyDown, onClick: () => $app.datePickerState.open(), className: "sx__button sx__date-input-chevron-wrapper", children: jsxRuntime.jsx("img", { className: "sx__date-input-chevron", src: img, alt: "" }) })] }) })); } var DatePickerView; (function (DatePickerView) { DatePickerView["MONTH_DAYS"] = "month-days"; DatePickerView["YEARS"] = "years"; })(DatePickerView || (DatePickerView = {})); const YEARS_VIEW = 'years-view'; const MONTH_VIEW = 'months-view'; const DATE_PICKER_WEEK = 'date-picker-week'; const toLocalizedMonth = (date, locale) => { return date.toLocaleString(locale, { month: 'long' }); }; const toLocalizedDateString = (date, locale) => { return date.toLocaleString(locale, { month: 'numeric', day: 'numeric', year: 'numeric', }); }; const getOneLetterDayNames = (week, locale) => { return week.map((date) => { return date.toLocaleString(locale, { weekday: 'short' }).charAt(0); }); }; const getDayNameShort = (date, locale) => { if (locale === 'he-IL') { return date.toLocaleString(locale, { weekday: 'narrow' }); } return date.toLocaleString(locale, { weekday: 'short' }); }; const getDayNamesShort = (week, locale) => { return week.map((date) => getDayNameShort(date, locale)); }; const getOneLetterOrShortDayNames = (week, locale) => { if (['zh-cn', 'zh-tw', 'ca-es', 'he-il'].includes(locale.toLowerCase())) { return getDayNamesShort(week, locale); } return getOneLetterDayNames(week, locale); }; const toIntegers = (dateTimeSpecification) => { const hours = dateTimeSpecification.slice(11, 13), minutes = dateTimeSpecification.slice(14, 16); return { year: Number(dateTimeSpecification.slice(0, 4)), month: Number(dateTimeSpecification.slice(5, 7)) - 1, date: Number(dateTimeSpecification.slice(8, 10)), hours: hours !== '' ? Number(hours) : undefined, minutes: minutes !== '' ? Number(minutes) : undefined, }; }; class NumberRangeError extends Error { constructor(min, max) { super(`Number must be between ${min} and ${max}.`); Object.defineProperty(this, "min", { enumerable: true, configurable: true, writable: true, value: min }); Object.defineProperty(this, "max", { enumerable: true, configurable: true, writable: true, value: max }); } } const doubleDigit = (number) => { if (number < 0 || number > 99) throw new NumberRangeError(0, 99); return String(number).padStart(2, '0'); }; const addMonths = (to, nMonths) => { if (nMonths < 0) { return to.subtract({ months: -nMonths }); } return to.add({ months: nMonths }); }; const addDays = (to, nDays) => { if (nDays < 0) { return to.subtract({ days: -nDays }); } return to.add({ days: nDays }); }; const getFirstDayOPreviousMonth = (date) => { return addMonths(date, -1).with({ day: 1 }); }; const getFirstDayOfNextMonth = (date) => { const nextMonth = addMonths(date, 1); return nextMonth.with({ day: 1 }); }; function Chevron({ direction, onClick, buttonText, disabled = false, }) { const handleKeyDown = (keyboardEvent) => { if (isKeyEnterOrSpace(keyboardEvent)) onClick(); }; return (jsxRuntime.jsx("button", { type: "button", disabled: disabled, className: "sx__button sx__chevron-wrapper sx__ripple", onMouseUp: onClick, onKeyDown: handleKeyDown, tabIndex: 0, children: jsxRuntime.jsx("i", { className: `sx__chevron sx__chevron--${direction}`, children: buttonText }) })); } function MonthViewHeader({ setYearsView }) { const $app = hooks.useContext(AppContext); const dateStringToLocalizedMonthName = (selectedDate) => { return toLocalizedMonth(selectedDate, $app.config.locale.value); }; const getYearFrom = (datePickerDate) => { return datePickerDate.year; }; const [selectedDateMonthName, setSelectedDateMonthName] = hooks.useState(dateStringToLocalizedMonthName($app.datePickerState.datePickerDate.value)); const [datePickerYear, setDatePickerYear] = hooks.useState(getYearFrom($app.datePickerState.datePickerDate.value)); const setPreviousMonth = () => { $app.datePickerState.datePickerDate.value = getFirstDayOPreviousMonth($app.datePickerState.datePickerDate.value); }; const setNextMonth = () => { $app.datePickerState.datePickerDate.value = getFirstDayOfNextMonth($app.datePickerState.datePickerDate.value); }; hooks.useEffect(() => { setSelectedDateMonthName(dateStringToLocalizedMonthName($app.datePickerState.datePickerDate.value)); setDatePickerYear(getYearFrom($app.datePickerState.datePickerDate.value)); }, [$app.datePickerState.datePickerDate.value]); const handleOpenYearsView = (e) => { e.stopPropagation(); setYearsView(); }; return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("header", { className: "sx__date-picker__month-view-header", children: [jsxRuntime.jsx(Chevron, { direction: 'previous', onClick: () => setPreviousMonth(), buttonText: $app.translate('Previous month') }), jsxRuntime.jsx("button", { type: "button", className: "sx__button sx__date-picker__month-view-header__month-year", onClick: (event) => handleOpenYearsView(event), children: selectedDateMonthName + ' ' + datePickerYear }), jsxRuntime.jsx(Chevron, { direction: 'next', onClick: () => setNextMonth(), buttonText: $app.translate('Next month') })] }) })); } function DayNames() { const $app = hooks.useContext(AppContext); const aWeek = $app.timeUnitsImpl.getWeekFor($app.datePickerState.datePickerDate.value); const dayNames = getOneLetterOrShortDayNames(aWeek, $app.config.locale.value); return (jsxRuntime.jsx("div", { className: "sx__date-picker__day-names", children: dayNames.map((dayName) => (jsxRuntime.jsx("span", { "data-testid": "day-name", className: "sx__date-picker__day-name", children: dayName }))) })); } const isToday = (date, timezone) => { const today = Temporal.Now.zonedDateTimeISO(timezone); return (date.day === today.day && date.month === today.month && date.year === today.year); }; const isSameMonth = (date1, date2) => { return date1.month === date2.month && date1.year === date2.year; }; const isSameDay = (date1, date2) => { return (date1.day === date2.day && date1.month === date2.month && date1.year === date2.year); }; var WeekDay; (function (WeekDay) { WeekDay[WeekDay["MONDAY"] = 1] = "MONDAY"; WeekDay[WeekDay["TUESDAY"] = 2] = "TUESDAY"; WeekDay[WeekDay["WEDNESDAY"] = 3] = "WEDNESDAY"; WeekDay[WeekDay["THURSDAY"] = 4] = "THURSDAY"; WeekDay[WeekDay["FRIDAY"] = 5] = "FRIDAY"; WeekDay[WeekDay["SATURDAY"] = 6] = "SATURDAY"; WeekDay[WeekDay["SUNDAY"] = 7] = "SUNDAY"; })(WeekDay || (WeekDay = {})); const DEFAULT_LOCALE = 'en-US'; const DEFAULT_FIRST_DAY_OF_WEEK = WeekDay.MONDAY; const dateFn = (dateTime, locale) => { return dateTime.toLocaleString(locale, { day: 'numeric', month: 'long', year: 'numeric', }); }; const getLocalizedDate$1 = dateFn; var PluginName; (function (PluginName) { PluginName["DragAndDrop"] = "dragAndDrop"; PluginName["EventModal"] = "eventModal"; PluginName["ScrollController"] = "scrollController"; PluginName["EventRecurrence"] = "eventRecurrence"; PluginName["Resize"] = "resize"; PluginName["CalendarControls"] = "calendarControls"; PluginName["CurrentTime"] = "currentTime"; })(PluginName || (PluginName = {})); preact.createContext({}); function MonthViewWeek({ week }) { const $app = hooks.useContext(AppContext); const weekDays = week.map((day) => { const classes = ['sx__date-picker__day']; if (isToday(day, $app.config.timezone.value)) classes.push('sx__date-picker__day--today'); if (isSameDay(day, $app.datePickerState.selectedDate.value)) classes.push('sx__date-picker__day--selected'); if (!isSameMonth(day, $app.datePickerState.datePickerDate.value)) classes.push('is-leading-or-trailing'); return { day: day.toPlainDate(), classes, }; }); const isDateSelectable = (date) => { return (date.toString() >= $app.config.min.toString() && date.toString() <= $app.config.max.toString()); }; const selectDate = (date) => { $app.datePickerState.selectedDate.value = date; $app.datePickerState.close(); }; const hasFocus = (weekDay) => isSameDay(weekDay.day, $app.datePickerState.datePickerDate.value); const handleKeyDown = (event) => { if (event.key === 'Enter') { $app.datePickerState.selectedDate.value = $app.datePickerState.datePickerDate.value; $app.datePickerState.close(); return; } const keyMapDaysToAdd = new Map([ ['ArrowDown', 7], ['ArrowUp', -7], ['ArrowLeft', -1], ['ArrowRight', 1], ]); $app.datePickerState.datePickerDate.value = addDays($app.datePickerState.datePickerDate.value, keyMapDaysToAdd.get(event.key) || 0); }; return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("div", { "data-testid": DATE_PICKER_WEEK, className: "sx__date-picker__week", children: weekDays.map((weekDay) => (jsxRuntime.jsx("button", { type: "button", tabIndex: hasFocus(weekDay) ? 0 : -1, disabled: !isDateSelectable(weekDay.day), "aria-label": getLocalizedDate$1($app.datePickerState.datePickerDate.value, $app.config.locale.value), className: `sx__button ${weekDay.classes.join(' ')}`, "data-focus": hasFocus(weekDay) ? 'true' : undefined, onClick: () => selectDate(weekDay.day), onKeyDown: handleKeyDown, children: weekDay.day.day }))) }) })); } function MonthView({ seatYearsView }) { const elementId = randomStringId(); const $app = hooks.useContext(AppContext); const [month, setMonth] = hooks.useState([]); const renderMonth = () => { const newDatePickerDate = $app.datePickerState.datePickerDate.value; setMonth($app.timeUnitsImpl.getMonthWithTrailingAndLeadingDays(newDatePickerDate.year, newDatePickerDate.month)); }; hooks.useEffect(() => { renderMonth(); }, [$app.datePickerState.datePickerDate.value]); hooks.useEffect(() => { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { const mutatedElement = mutation.target; if (mutatedElement.dataset.focus === 'true') mutatedElement.focus(); }); }); const monthViewElement = document.getElementById(elementId); observer.observe(monthViewElement, { childList: true, subtree: true, attributes: true, }); return () => observer.disconnect(); }, []); return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("div", { id: elementId, "data-testid": MONTH_VIEW, className: "sx__date-picker__month-view", children: [jsxRuntime.jsx(MonthViewHeader, { setYearsView: seatYearsView }), jsxRuntime.jsx(DayNames, {}), month.map((week) => (jsxRuntime.jsx(MonthViewWeek, { week: week })))] }) })); } function YearsViewAccordion({ year, setYearAndMonth, isExpanded, expand, }) { const $app = hooks.useContext(AppContext); const yearWithDates = $app.timeUnitsImpl.getMonthsFor(year); const handleClickOnMonth = (event, month) => { event.stopPropagation(); setYearAndMonth(year, month.month); }; return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("li", { className: isExpanded ? 'sx__is-expanded' : '', children: [jsxRuntime.jsx("button", { type: "button", className: "sx__button sx__date-picker__years-accordion__expand-button sx__ripple--wide", onClick: () => expand(year), children: year }), isExpanded && (jsxRuntime.jsx("div", { className: "sx__date-picker__years-view-accordion__panel", children: yearWithDates.map((month) => (jsxRuntime.jsx("button", { type: "button", className: "sx__button sx__date-picker__years-view-accordion__month", onClick: (event) => handleClickOnMonth(event, month), children: toLocalizedMonth(month, $app.config.locale.value) }))) }))] }) })); } function YearsView({ setMonthView }) { const $app = hooks.useContext(AppContext); const minYear = $app.config.min.year; const maxYear = $app.config.max.year; const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) => minYear + i); const selectedYear = $app.datePickerState.selectedDate.value.year; const [expandedYear, setExpandedYear] = hooks.useState(selectedYear); const setNewDatePickerDate = (year, month) => { $app.datePickerState.datePickerDate.value = Temporal.PlainDate.from({ year, month, day: 1, }); setMonthView(); }; hooks.useEffect(() => { var _a; const initiallyExpandedYear = (_a = document .querySelector('.sx__date-picker__years-view')) === null || _a === void 0 ? void 0 : _a.querySelector('.sx__is-expanded'); if (!initiallyExpandedYear) return; initiallyExpandedYear.scrollIntoView({ block: 'center', }); }, []); return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("ul", { className: "sx__date-picker__years-view", "data-testid": YEARS_VIEW, children: years.map((year) => (jsxRuntime.jsx(YearsViewAccordion, { year: year, setYearAndMonth: (year, month) => setNewDatePickerDate(year, month), isExpanded: expandedYear === year, expand: (year) => setExpandedYear(year) }))) }) })); } const isScrollable = (el) => { if (el) { const hasScrollableContent = el.scrollHeight > el.clientHeight; const overflowYStyle = window.getComputedStyle(el).overflowY; const isOverflowHidden = overflowYStyle.indexOf('hidden') !== -1; return hasScrollableContent && !isOverflowHidden; } return true; }; const getScrollableParents = (el, acc = []) => { if (!el || el === document.body || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { acc.push(window); return acc; } if (isScrollable(el)) { acc.push(el); } return getScrollableParents((el.assignedSlot ? el.assignedSlot.parentNode : el.parentNode), acc); }; const POPUP_CLASS_NAME = 'sx__date-picker-popup'; function AppPopup({ wrapperEl }) { const $app = hooks.useContext(AppContext); const [datePickerView, setDatePickerView] = hooks.useState(DatePickerView.MONTH_DAYS); const classList = hooks.useMemo(() => { const returnValue = [ POPUP_CLASS_NAME, $app.datePickerState.isDark.value ? 'is-dark' : '', $app.config.teleportTo ? 'is-teleported' : '', ]; if ($app.config.placement && !$app.config.teleportTo && wrapperEl) { const placement = $app.config.placement instanceof Function ? $app.config.placement(wrapperEl) : $app.config.placement; returnValue.push(placement); } return returnValue; }, [ $app.datePickerState.isDark.value, $app.config.placement, $app.config.teleportTo, ]); const clickOutsideListener = (event) => { const target = event.target; if (!target.closest(`.${POPUP_CLASS_NAME}`)) $app.datePickerState.close(); }; const escapeKeyListener = (e) => { if (e.key === 'Escape') { if ($app.config.listeners.onEscapeKeyDown) $app.config.listeners.onEscapeKeyDown($app); else $app.datePickerState.close(); } }; hooks.useEffect(() => { document.addEventListener('click', clickOutsideListener); document.addEventListener('keydown', escapeKeyListener); return () => { document.removeEventListener('click', clickOutsideListener); document.removeEventListener('keydown', escapeKeyListener); }; }, []); const remSize = Number(getComputedStyle(document.documentElement).fontSize.split('px')[0]); const popupHeight = 362; const popupWidth = 332; const getFixedPositionStyles = () => { const inputWrapperEl = $app.datePickerState.inputWrapperElement.value; const inputRect = inputWrapperEl === null || inputWrapperEl === void 0 ? void 0 : inputWrapperEl.getBoundingClientRect(); if (inputWrapperEl === undefined || !(inputRect instanceof DOMRect)) return undefined; const resolvedPlacement = typeof $app.config.placement === 'function' ? wrapperEl ? $app.config.placement(wrapperEl) : 'bottom-end' : $app.config.placement; if (!resolvedPlacement) return undefined; return { top: resolvedPlacement.includes('bottom') ? inputRect.height + inputRect.y + 1 // 1px border : inputRect.y - remSize - popupHeight, // subtract remsize to leave room for label text left: resolvedPlacement.includes('start') ? inputRect.x : inputRect.x + inputRect.width - popupWidth, width: popupWidth, position: 'fixed', }; }; const [fixedPositionStyle, setFixedPositionStyle] = hooks.useState(getFixedPositionStyles()); hooks.useEffect(() => { const inputWrapper = $app.datePickerState.inputWrapperElement.value; if (inputWrapper === undefined) return; const scrollableParents = getScrollableParents(inputWrapper); const scrollListener = () => setFixedPositionStyle(getFixedPositionStyles()); scrollableParents.forEach((parent) => parent.addEventListener('scroll', scrollListener)); return () => scrollableParents.forEach((parent) => parent.removeEventListener('scroll', scrollListener)); }, []); return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("div", { style: $app.config.teleportTo ? fixedPositionStyle : undefined, "data-testid": "date-picker-popup", className: classList.join(' '), children: datePickerView === DatePickerView.MONTH_DAYS ? (jsxRuntime.jsx(MonthView, { seatYearsView: () => setDatePickerView(DatePickerView.YEARS) })) : (jsxRuntime.jsx(YearsView, { setMonthView: () => setDatePickerView(DatePickerView.MONTH_DAYS) })) }) })); } function AppWrapper({ $app }) { const initialClassList = ['sx__date-picker-wrapper']; const [classList, setClassList] = hooks.useState(initialClassList); const elementRef = compat.useRef(null); hooks.useEffect(() => { if (elementRef && elementRef.current instanceof HTMLDivElement) $app.elements = { DatePickerWrapper: elementRef.current }; }, []); hooks.useEffect(() => { var _a; const list = [...initialClassList]; if ($app.datePickerState.isDark.value) list.push('is-dark'); if ((_a = $app.config.style) === null || _a === void 0 ? void 0 : _a.fullWidth) list.push('has-full-width'); if ($app.datePickerState.isDisabled.value) list.push('is-disabled'); setClassList(list); }, [$app.datePickerState.isDark.value, $app.datePickerState.isDisabled.value]); let appPopupJSX = jsxRuntime.jsx(AppPopup, { wrapperEl: elementRef.current }); if ($app.config.teleportTo) appPopupJSX = compat.createPortal(appPopupJSX, $app.config.teleportTo); return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("div", { ref: elementRef, className: classList.join(' '), children: jsxRuntime.jsxs(AppContext.Provider, { value: $app, children: [jsxRuntime.jsx(AppInput, {}), $app.datePickerState.isOpen.value && appPopupJSX] }) }) })); } class DatePickerApp { constructor($app) { Object.defineProperty(this, "$app", { enumerable: true, configurable: true, writable: true, value: $app }); } render(el) { preact.render(preact.createElement(AppWrapper, { $app: this.$app, }), el); } get value() { return this.$app.datePickerState.selectedDate.value; } set value(value) { this.$app.datePickerState.selectedDate.value = Temporal.PlainDate.from(value); } get disabled() { return this.$app.datePickerState.isDisabled.value; } set disabled(value) { this.$app.datePickerState.isDisabled.value = value; } setTheme(theme) { this.$app.datePickerState.isDark.value = theme === 'dark'; } getTheme() { return this.$app.datePickerState.isDark.value ? 'dark' : 'light'; } } var Month; (function (Month) { Month[Month["JANUARY"] = 1] = "JANUARY"; Month[Month["FEBRUARY"] = 2] = "FEBRUARY"; Month[Month["MARCH"] = 3] = "MARCH"; Month[Month["APRIL"] = 4] = "APRIL"; Month[Month["MAY"] = 5] = "MAY"; Month[Month["JUNE"] = 6] = "JUNE"; Month[Month["JULY"] = 7] = "JULY"; Month[Month["AUGUST"] = 8] = "AUGUST"; Month[Month["SEPTEMBER"] = 9] = "SEPTEMBER"; Month[Month["OCTOBER"] = 10] = "OCTOBER"; Month[Month["NOVEMBER"] = 11] = "NOVEMBER"; Month[Month["DECEMBER"] = 12] = "DECEMBER"; })(Month || (Month = {})); class NoYearZeroError extends Error { constructor() { super('Year zero does not exist in the Gregorian calendar.'); } } class TimeUnitsImpl { constructor(config) { Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: config }); } get firstDayOfWeek() { return this.config.firstDayOfWeek.value; } set firstDayOfWeek(firstDayOfWeek) { this.config.firstDayOfWeek.value = firstDayOfWeek; } getMonth(year, month) { if (year === 0) throw new NoYearZeroError(); const firstDateOfMonth = Temporal.PlainDate.from({ year, month: month, day: 1, }); const lastDateOfMonth = firstDateOfMonth .toPlainYearMonth() .toPlainDate({ day: firstDateOfMonth.toPlainYearMonth().daysInMonth }); const dates = []; let currentDate = firstDateOfMonth; while (Temporal.PlainDate.compare(currentDate, lastDateOfMonth) <= 0) { dates.push(currentDate.toZonedDateTime(this.config.timezone.value)); currentDate = currentDate.add({ days: 1 }); } return dates; } getMonthWithTrailingAndLeadingDays(year, month) { if (year === 0) throw new NoYearZeroError(); const firstDateOfMonth = Temporal.PlainDate.from({ year, month: month, day: 1, }); const monthWithDates = [this.getWeekForTemporal(firstDateOfMonth)]; let isInMonth = true; let currentWeekStart = monthWithDates[0][0]; // first day of first week of month while (isInMonth) { const nextWeekStart = currentWeekStart.add({ days: 7 }); // Check if the next week contains any dates from the target month const nextWeekDates = this.getWeekForTemporal(nextWeekStart); const hasDatesInTargetMonth = nextWeekDates.some((date) => date.month === month); if (hasDatesInTargetMonth) { monthWithDates.push(nextWeekDates); currentWeekStart = nextWeekStart; } else { isInMonth = false; } } // Convert Temporal.PlainDate arrays to Temporal.ZonedDateTime arrays return monthWithDates.map((week) => week.map((plainDate) => plainDate.toZonedDateTime(this.config.timezone.value))); } getWeekFor(date) { const plainDate = date instanceof Temporal.PlainDate ? date : date.toPlainDate(); const week = [ this.getFirstDateOfWeekTemporal(plainDate).toZonedDateTime(this.config.timezone.value), ]; while (week.length < 7) { const lastDateOfWeek = week[week.length - 1]; const nextDateOfWeek = lastDateOfWeek.add({ days: 1 }); week.push(nextDateOfWeek); } return week; } getMonthsFor(year) { if (year === 0) throw new NoYearZeroError(); return Object.values(Month) .filter((month) => !isNaN(Number(month))) .map((month) => Temporal.PlainDate.from({ year, month: Number(month), day: 1 })); } getWeekForTemporal(date) { const week = [this.getFirstDateOfWeekTemporal(date)]; while (week.length < 7) { const lastDateOfWeek = week[week.length - 1]; const nextDateOfWeek = lastDateOfWeek.add({ days: 1 }); week.push(nextDateOfWeek); } return week; } getFirstDateOfWeekTemporal(date) { const dateIsNthDayOfWeek = date.dayOfWeek - this.firstDayOfWeek; if (dateIsNthDayOfWeek === 0) { return date; } else if (dateIsNthDayOfWeek > 0) { return date.subtract({ days: dateIsNthDayOfWeek }); } else { return date.subtract({ days: 7 + dateIsNthDayOfWeek }); } } } class TimeUnitsBuilder { constructor() { Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); } build() { return new TimeUnitsImpl(this.config); } withConfig(config) { this.config = config; return this; } } var DateFormatDelimiter; (function (DateFormatDelimiter) { DateFormatDelimiter["SLASH"] = "/"; DateFormatDelimiter["DASH"] = "-"; DateFormatDelimiter["PERIOD"] = "."; })(DateFormatDelimiter || (DateFormatDelimiter = {})); var DateFormatOrder; (function (DateFormatOrder) { DateFormatOrder["DMY"] = "DMY"; DateFormatOrder["MDY"] = "MDY"; DateFormatOrder["YMD"] = "YMD"; })(DateFormatOrder || (DateFormatOrder = {})); const formatRules = { slashMDY: { delimiter: DateFormatDelimiter.SLASH, order: DateFormatOrder.MDY, }, slashDMY: { delimiter: DateFormatDelimiter.SLASH, order: DateFormatOrder.DMY, }, slashYMD: { delimiter: DateFormatDelimiter.SLASH, order: DateFormatOrder.YMD, }, periodDMY: { delimiter: DateFormatDelimiter.PERIOD, order: DateFormatOrder.DMY, }, dashYMD: { delimiter: DateFormatDelimiter.DASH, order: DateFormatOrder.YMD, }, dashDMY: { delimiter: DateFormatDelimiter.DASH, order: DateFormatOrder.DMY, }, }; const dateFormatLocalizedRules = new Map([ ['ca-ES', formatRules.slashDMY], ['cs-CZ', formatRules.periodDMY], ['da-DK', formatRules.periodDMY], ['de-DE', formatRules.periodDMY], ['en-GB', formatRules.slashDMY], ['en-US', formatRules.slashMDY], ['es-ES', formatRules.slashDMY], ['et-EE', formatRules.periodDMY], ['fi-FI', formatRules.periodDMY], ['fr-FR', formatRules.slashDMY], ['fr-CH', formatRules.periodDMY], ['hr-HR', formatRules.periodDMY], ['id-ID', formatRules.slashDMY], ['it-IT', formatRules.slashDMY], ['ja-JP', formatRules.slashYMD], ['ko-KR', formatRules.slashYMD], ['ky-KG', formatRules.slashDMY], ['lt-LT', formatRules.dashYMD], ['mk-MK', formatRules.periodDMY], ['nl-NL', formatRules.dashDMY], ['pl-PL', formatRules.periodDMY], ['pt-BR', formatRules.slashDMY], ['ro-RO', formatRules.periodDMY], ['ru-RU', formatRules.periodDMY], ['sk-SK', formatRules.periodDMY], ['sl-SI', formatRules.periodDMY], ['sr-Latn-RS', formatRules.periodDMY], ['sr-RS', formatRules.periodDMY], ['sv-SE', formatRules.dashYMD], ['tr-TR', formatRules.periodDMY], ['uk-UA', formatRules.periodDMY], ['zh-CN', formatRules.slashYMD], ['zh-TW', formatRules.slashYMD], ]); class LocaleNotSupportedError extends Error { constructor(locale) { super(`Locale not supported: ${locale}`); } } class InvalidDateFormatError extends Error { constructor(dateFormat, locale) { super(`Invalid date format: ${dateFormat} for locale: ${locale}`); } } const _getMatchesOrThrow = (format, matcher, locale) => { const matches = format.match(matcher); if (!matches) throw new InvalidDateFormatError(format, locale); return matches; }; const toDateString = (format, locale) => { const internationalFormat = /^\d{4}-\d{2}-\d{2}$/; if (internationalFormat.test(format)) return format; // allow international format regardless of locale const localeDateFormatRule = dateFormatLocalizedRules.get(locale); if (!localeDateFormatRule) throw new LocaleNotSupportedError(locale); const { order, delimiter } = localeDateFormatRule; const pattern224Slashed = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/; const pattern224Dotted = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/; const pattern442Slashed = /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/; if (order === DateFormatOrder.DMY && delimiter === DateFormatDelimiter.SLASH) { const matches = _getMatchesOrThrow(format, pattern224Slashed, locale); const [, day, month, year] = matches; return `${year}-${doubleDigit(+month)}-${doubleDigit(+day)}`; } if (order === DateFormatOrder.MDY && delimiter === DateFormatDelimiter.SLASH) { const matches = _getMatchesOrThrow(format, pattern224Slashed, locale); const [, month, day, year] = matches; return `${year}-${doubleDigit(+month)}-${doubleDigit(+day)}`; } if (order === DateFormatOrder.YMD && delimiter === DateFormatDelimiter.SLASH) { const matches = _getMatchesOrThrow(format, pattern442Slashed, locale); const [, year, month, day] = matches; return `${year}-${doubleDigit(+month)}-${doubleDigit(+day)}`; } if (order === DateFormatOrder.DMY && delimiter === DateFormatDelimiter.PERIOD) { const matches = _getMatchesOrThrow(format, pattern224Dotted, locale); const [, day, month, year] = matches; return `${year}-${doubleDigit(+month)}-${doubleDigit(+day)}`; } throw new InvalidDateFormatError(format, locale); }; const getLocalizedDate = (date, locale) => { return toLocalizedDateString(date, locale); }; const createDatePickerState = (config, selectedDateParam) => { var _a; const initialSelectedDate = selectedDateParam instanceof Temporal.PlainDate ? selectedDateParam : Temporal.Now.plainDateISO(); const isOpen = signals.signal(false); const isDisabled = signals.signal(config.disabled || false); const datePickerView = signals.signal(DatePickerView.MONTH_DAYS); const selectedDate = signals.signal(initialSelectedDate); const datePickerDate = signals.signal(initialSelectedDate); const isDark = signals.signal(((_a = config.style) === null || _a === void 0 ? void 0 : _a.dark) || false); const inputDisplayedValue = signals.signal(toLocalizedDateString(initialSelectedDate, config.locale.value)); const lastValidDisplayedValue = signals.signal(inputDisplayedValue.value); const handleInput = (newInputValue) => { try { const newValue = toDateString(newInputValue, config.locale.value); if (newValue < config.min.toString() || newValue > config.max.toString()) { inputDisplayedValue.value = lastValidDisplayedValue.value; return; } const { year, month, date: day } = toIntegers(newValue); const newPlainDate = Temporal.PlainDate.from({ year, month: month + 1, day, }); selectedDate.value = newPlainDate; datePickerDate.value = newPlainDate; lastValidDisplayedValue.value = inputDisplayedValue.value; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_e) { // Nothing to do here. We don't want to log errors when users are typing invalid formats } }; signals.effect(() => { inputDisplayedValue.value = getLocalizedDate(selectedDate.value, config.locale.value); }); let wasInitialized = false; const handleOnChange = (selectedDate) => { if (!wasInitialized) return (wasInitialized = true); config.listeners.onChange(selectedDate); }; signals.effect(() => { var _a; if ((_a = config.listeners) === null || _a === void 0 ? void 0 : _a.onChange) handleOnChange(selectedDate.value); }); return { inputWrapperElement: signals.signal(undefined), isOpen, isDisabled, datePickerView, selectedDate, datePickerDate, inputDisplayedValue, handleInput, isDark, open: () => (isOpen.value = true), close: () => (isOpen.value = false), toggle: () => (isOpen.value = !isOpen.value), setView: (view) => (datePickerView.value = view), }; }; class DatePickerAppSingletonImpl { constructor(datePickerState, config, timeUnitsImpl, translate, elements = {}) { Object.defineProperty(this, "datePickerState", { enumerable: true, configurable: true, writable: true, value: datePickerState }); Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: config }); Object.defineProperty(this, "timeUnitsImpl", { enumerable: true, configurable: true, writable: true, value: timeUnitsImpl }); Object.defineProperty(this, "translate", { enumerable: true, configurable: true, writable: true, value: translate }); Object.defineProperty(this, "elements", { enumerable: true, configurable: true, writable: true, value: elements }); } } class DatePickerAppSingletonBuilder { constructor() { Object.defineProperty(this, "datePickerState", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "timeUnitsImpl", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "translate", { enumerable: true, configurable: true, writable: true, value: void 0 }); } build() { return new DatePickerAppSingletonImpl(this.datePickerState, this.config, this.timeUnitsImpl, this.translate); } withDatePickerState(datePickerState) { this.datePickerState = datePickerState; return this; } withConfig(config) { this.config = config; return this; } withTimeUnitsImpl(timeUnitsImpl) { this.timeUnitsImpl = timeUnitsImpl; return this; } withTranslate(translate) { this.translate = translate; return this; } } var Placement; (function (Placement) { Placement["TOP_START"] = "top-start"; Placement["TOP_END"] = "top-end"; Placement["BOTTOM_START"] = "bottom-start"; Placement["BOTTOM_END"] = "bottom-end"; })(Placement || (Placement = {})); class ConfigImpl { constructor(locale = DEFAULT_LOCALE, firstDayOfWeek = DEFAULT_FIRST_DAY_OF_WEEK, timezone = 'UTC', min = Temporal.PlainDate.from({ year: 1970, month: 1, day: 1, }), max = Temporal.PlainDate.from({ year: new Date().getFullYear() + 50, month: 11, day: 31, }), placement = Placement.BOTTOM_START, listeners = {}, style = {}, teleportTo, label, name, disabled, hasPlaceholder) { Object.defineProperty(this, "min", { enumerable: true, configurable: true, writable: true, value: min }); Object.defineProperty(this, "max", { enumerable: true, configurable: true, writable: true, value: max }); Object.defineProperty(this, "placement", { enumerable: true, configurable: true, writable: true, value: placement }); Object.defineProperty(this, "listeners", { enumerable: true, configurable: true, writable: true, value: listeners }); Object.defineProperty(this, "style", { enumerable: true, configurable: true, writable: true, value: style }); Object.defineProperty(this, "teleportTo", { enumerable: true, configurable: true, writable: true, value: teleportTo }); Object.defineProperty(this, "label", { enumerable: true, configurable: true, writable: true, value: label }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: name }); Object.defineProperty(this, "disabled", { enumerable: true, configurable: true, writable: true, value: disabled }); Object.defineProperty(this, "hasPlaceholder", { enumerable: true, configurable: true, writable: true, value: hasPlaceholder }); Object.defineProperty(this, "locale", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "firstDayOfWeek", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "timezone", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.locale = signals.signal(locale); this.firstDayOfWeek = signals.signal(firstDayOfWeek); this.timezone = signals.signal(timezone); } } class ConfigBuilder { constructor() { Object.defineProperty(this, "locale", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "firstDayOfWeek", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "timezone", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "min", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "max", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "placement", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "listeners", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "style", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "teleportTo", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "label", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "disabled", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "hasPlaceholder", { enumerable: true, configurable: true, writable: true, value: void 0 }); } build() { return new ConfigImpl(this.locale, this.firstDayOfWeek, this.timezone, this.min, this.max, this.placement, this.listeners, this.style, this.teleportTo, this.label, this.name, this.disabled, this.hasPlaceholder); } withLocale(locale) { this.locale = locale; return this; } withFirstDayOfWeek(firstDayOfWeek) { this.firstDayOfWeek = firstDayOfWeek; return this; } withTimezone(timezone) { this.timezone = timezone; return this; } withMin(min) { this.min = min; return this; } withMax(max) { this.max = max; return this; } withPlacement(placement) { this.placement = placement; return this; } withListeners(listeners) { this.listeners = listeners; return this; } withStyle(style) { this.style = style; return this; } withTeleportTo(teleportTo) { this.teleportTo = teleportTo; return this; } withLabel(label) { this.label = label; return this; } withName(name) { this.name = name; return this; } withDisabled(disabled) { this.disabled = disabled; return this; } withHasPlaceholder(hasPlaceholder) { this.hasPlaceholder = hasPlaceholder; return this; } } const datePickerArEG = { Date: 'التاريخ', 'MM/DD/YYYY': 'DD/MM/YYYY', // Keep format unchanged unless you want to localize it 'Next month': 'الشهر القادم', 'Previous month': 'الشهر السابق', 'Choose Date': 'اختر التاريخ', }; const timePickerArEG = { Time: 'الوقت', AM: 'ص', PM: 'م', Cancel: 'إلغاء', OK: 'موافق', 'Select time': 'اختر الوقت', }; const calendarArEG = { Today: 'اليوم', Month: 'الشهر', Week: 'الأسبوع', Day: 'اليوم', List: 'القائمة', 'Select View': 'اختر العرض', '+ {{n}} events': '+ {{n}} الأحداث', '+ 1 event': '+ 1 حدث', 'No events': 'لا توجد أحداث', 'Next period': 'الفترة التالية', 'Previous period': 'الفترة السابقة', to: 'إلى', // as in 2/1/2020 to 2/2/2020 'Full day- and multiple day events': 'أحداث ليوم كامل أو لعدة أيام', 'Link to {{n}} more events on {{date}}': 'رابط إلى {{n}} أحداث أخرى في {{date}}', 'Link to 1 more event on {{date}}': 'رابط إلى حدث آخر في {{date}}', CW: 'الأسبوع {{week}}', View: 'عرض', }; const arEG = { ...calendarArEG, ...datePickerArEG, ...timePickerArEG, }; const datePickerDeDE = { Date: 'Datum', 'MM/DD/YYYY': 'TT.MM.JJJJ', 'Next month': 'Nächster Monat', 'Previous month': 'Vorheriger Monat', 'Choose Date': 'Datum auswählen', }; const calendarDeDE = { Today: 'Heute', Month: 'Monat', Week: 'Woche', Day: 'Tag', List: 'Liste', 'Select View': 'Ansicht auswählen', View: 'Ansicht', '+ {{n}} events': '+ {{n}} Ereignisse', '+ 1 event': '+ 1 Ereignis', 'No events': 'Keine Ereignisse', 'Next period': 'Nächster Zeitraum', 'Previous period': 'Vorheriger Zeitraum', to: 'bis', // as in 2/1/2020 to 2/2/2020 'Full day- and multiple day events': 'Ganztägige und mehrtägige Termine', 'Link to {{n}} more events on {{date}}': 'Link zu {{n}} weiteren Terminen am {{date}}', 'Link to 1 more event on {{date}}': 'Link zu 1 weiterem Termin am {{date}}', CW: 'KW {{week}}', }; const timePickerDeDE = { Time: 'Uhrzeit', AM: 'AM', PM: 'PM', Cancel: 'Abbrechen', OK: 'OK', 'Select time': 'Uhrzeit auswählen', }; const deDE = { ...datePickerDeDE, ...calendarDeDE, ...timePickerDeDE, }; const datePickerEnUS = { Date: 'Date', 'MM/DD/YYYY': 'MM/DD/YYYY', 'Next month': 'Next month', 'Previous month': 'Previous month', 'Choose Date': 'Choose Date', }; const calendarEnUS = { Today: 'Today', Month: 'Month', Week: 'Week', Day: 'Day', List: 'List', 'Select View': 'Select View', View: 'View', '+ {{n}} events': '+ {{n}} events', '+ 1 event': '+ 1 event', 'No events': 'No events',