@schedule-x/calendar
Version:
Schedule-X calendar component
1,186 lines (1,130 loc) • 296 kB
JavaScript
'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$1 = 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$1);
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 DateFormats = {
DATE_STRING: /^\d{4}-\d{2}-\d{2}$/,
DATE_TIME_STRING: /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/,
};
class InvalidDateTimeError extends Error {
constructor(dateTimeSpecification) {
super(`Invalid date time specification: ${dateTimeSpecification}`);
}
}
const toJSDate = (dateTimeSpecification) => {
if (!DateFormats.DATE_TIME_STRING.test(dateTimeSpecification) &&
!DateFormats.DATE_STRING.test(dateTimeSpecification))
throw new InvalidDateTimeError(dateTimeSpecification);
return new Date(Number(dateTimeSpecification.slice(0, 4)), Number(dateTimeSpecification.slice(5, 7)) - 1, Number(dateTimeSpecification.slice(8, 10)), Number(dateTimeSpecification.slice(11, 13)), // for date strings this will be 0
Number(dateTimeSpecification.slice(14, 16)) // for date strings this will be 0
);
};
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 toDateString$1 = (date) => {
return `${date.year}-${doubleDigit(date.month)}-${doubleDigit(date.day)}`;
};
const toTimeString = (date) => {
return `${doubleDigit(date.hour)}:${doubleDigit(date.minute)}`;
};
const toDateTimeString = (date) => {
return `${toDateString$1(date)} ${toTimeString(date)}`;
};
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$1);
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$1);
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);
};
/**
* Origin of SVG: https://www.svgrepo.com/svg/506771/time
* License: PD License
* Author Salah Elimam
* Author website: https://www.figma.com/@salahelimam
* */
function TimeIcon({ strokeColor }) {
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("svg", { className: "sx__event-icon", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsxRuntime.jsx("g", { id: "SVGRepo_bgCarrier", "stroke-width": "0" }), jsxRuntime.jsx("g", { id: "SVGRepo_tracerCarrier", "stroke-linecap": "round", "stroke-linejoin": "round" }), jsxRuntime.jsxs("g", { id: "SVGRepo_iconCarrier", children: [jsxRuntime.jsx("path", { d: "M12 8V12L15 15", stroke: strokeColor, "stroke-width": "2", "stroke-linecap": "round" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9", stroke: strokeColor, "stroke-width": "2" })] })] }) }));
}
/**
* Origin of SVG: https://www.svgrepo.com/svg/506772/user
* License: PD License
* Author Salah Elimam
* Author website: https://www.figma.com/@salahelimam
* */
function UserIcon({ strokeColor }) {
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("svg", { className: "sx__event-icon", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsxRuntime.jsx("g", { id: "SVGRepo_bgCarrier", "stroke-width": "0" }), jsxRuntime.jsx("g", { id: "SVGRepo_tracerCarrier", "stroke-linecap": "round", "stroke-linejoin": "round" }), jsxRuntime.jsxs("g", { id: "SVGRepo_iconCarrier", children: [jsxRuntime.jsx("path", { d: "M15 7C15 8.65685 13.6569 10 12 10C10.3431 10 9 8.65685 9 7C9 5.34315 10.3431 4 12 4C13.6569 4 15 5.34315 15 7Z", stroke: strokeColor, "stroke-width": "2" }), jsxRuntime.jsx("path", { d: "M5 19.5C5 15.9101 7.91015 13 11.5 13H12.5C16.0899 13 19 15.9101 19 19.5V20C19 20.5523 18.5523 21 18 21H6C5.44772 21 5 20.5523 5 20V19.5Z", stroke: strokeColor, "stroke-width": "2" })] })] }) }));
}
/**
* Origin of SVG: https://www.svgrepo.com/svg/489502/location-pin
* License: PD License
* Author: Dariush Habibpour
* Author website: https://redl.ink/dariush/links?ref=svgrepo.com
* */
function LocationPinIcon({ strokeColor }) {
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs("svg", { className: "sx__event-icon", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsxRuntime.jsx("g", { id: "SVGRepo_bgCarrier", "stroke-width": "0" }), jsxRuntime.jsx("g", { id: "SVGRepo_tracerCarrier", "stroke-linecap": "round", "stroke-linejoin": "round" }), jsxRuntime.jsxs("g", { id: "SVGRepo_iconCarrier", children: [jsxRuntime.jsxs("g", { "clip-path": "url(#clip0_429_11046)", children: [jsxRuntime.jsx("rect", { x: "12", y: "11", width: "0.01", height: "0.01", stroke: strokeColor, "stroke-width": "2", "stroke-linejoin": "round" }), jsxRuntime.jsx("path", { d: "M12 22L17.5 16.5C20.5376 13.4624 20.5376 8.53757 17.5 5.5C14.4624 2.46244 9.53757 2.46244 6.5 5.5C3.46244 8.53757 3.46244 13.4624 6.5 16.5L12 22Z", stroke: strokeColor, "stroke-width": "2", "stroke-linejoin": "round" })] }), jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("clipPath", { id: "clip0_429_11046", children: jsxRuntime.jsx("rect", { width: "24", height: "24", fill: "white" }) }) })] })] }) }));
}
class InvalidTimeStringError extends Error {
constructor(timeString) {
super(`Invalid time string: ${timeString}`);
}
}
// regex for strings between 00:00 and 23:59
const timeStringRegex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]/;
const minuteTimePointMultiplier = 1.6666666666666667; // 100 / 60
const timePointsFromString = (timeString) => {
if (!timeStringRegex.test(timeString) && timeString !== '24:00')
throw new InvalidTimeStringError(timeString);
const [hoursInt, minutesInt] = timeString
.split(':')
.map((time) => parseInt(time, 10));
let minutePoints = (minutesInt * minuteTimePointMultiplier).toString();
if (minutePoints.split('.')[0].length < 2)
minutePoints = `0${minutePoints}`;
return Number(hoursInt + minutePoints);
};
const timeStringFromTimePoints = (timePoints) => {
const hours = Math.floor(timePoints / 100);
const minutes = Math.round((timePoints % 100) / minuteTimePointMultiplier);
return `${doubleDigit(hours)}:${doubleDigit(minutes)}`;
};
const addTimePointsToDateTime = (dateTime, pointsToAdd) => {
const minutesToAdd = Math.round(pointsToAdd / minuteTimePointMultiplier);
const newDateTime = dateTime.add({ minutes: minutesToAdd });
return newDateTime;
};
const dateFromDateTime = (dateTime) => {
return dateTime.slice(0, 10);
};
const timeFromDateTime = (dateTime) => {
return dateTime.slice(11);
};
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 DEFAULT_EVENT_COLOR_NAME = 'primary';
class CalendarEventImpl {
constructor(_config, id, _start, _end, title, people, location, description, calendarId, _options = undefined, _customContent = {}, _foreignProperties = {}, resourceId) {
Object.defineProperty(this, "_config", {
enumerable: true,
configurable: true,
writable: true,
value: _config
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: id
});
Object.defineProperty(this, "_start", {
enumerable: true,
configurable: true,
writable: true,
value: _start
});
Object.defineProperty(this, "_end", {
enumerable: true,
configurable: true,
writable: true,
value: _end
});
Object.defineProperty(this, "title", {
enumerable: true,
configurable: true,
writable: true,
value: title
});
Object.defineProperty(this, "people", {
enumerable: true,
configurable: true,
writable: true,
value: people
});
Object.defineProperty(this, "location", {
enumerable: true,
configurable: true,
writable: true,
value: location
});
Object.defineProperty(this, "description", {
enumerable: true,
configurable: true,
writable: true,
value: description
});
Object.defineProperty(this, "calendarId", {
enumerable: true,
configurable: true,
writable: true,
value: calendarId
});
Object.defineProperty(this, "_options", {
enumerable: true,
configurable: true,
writable: true,
value: _options
});
Object.defineProperty(this, "_customContent", {
enumerable: true,
configurable: true,
writable: true,
value: _customContent
});
Object.defineProperty(this, "_foreignProperties", {
enumerable: true,
configurable: true,
writable: true,
value: _foreignProperties
});
Object.defineProperty(this, "resourceId", {
enumerable: true,
configurable: true,
writable: true,
value: resourceId
});
Object.defineProperty(this, "_previousConcurrentEvents", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_totalConcurrentEvents", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_maxConcurrentEvents", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_nDaysInGrid", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_createdAt", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_originalTimezone", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_eventFragments", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
this._originalTimezone =
this._start instanceof Temporal.ZonedDateTime
? this._start.timeZoneId
: undefined;
}
get start() {
if (this._start instanceof Temporal.PlainDate) {
return this._start;
}
return this._start.withTimeZone(this._config.timezone.value);
}
set start(value) {
this._start =
value instanceof Temporal.ZonedDateTime
? value.withTimeZone(this._originalTimezone)
: value;
}
get end() {
if (this._end instanceof Temporal.PlainDate) {
return this._end;
}
return this._end.withTimeZone(this._config.timezone.value);
}
set end(value) {
this._end =
value instanceof Temporal.ZonedDateTime
? value.withTimeZone(this._originalTimezone)
: value;
}
get _isSingleDayTimed() {
if (this.start instanceof Temporal.PlainDate ||
this.end instanceof Temporal.PlainDate)
return false;
const localStartDate = dateFromDateTime(this.start.toString());
const localEndDate = dateFromDateTime(this.end.toString());
return localStartDate === localEndDate;
}
get _isSingleDayFullDay() {
const startDate = dateFromDateTime(this.start.toString());
const endDate = dateFromDateTime(this.end.toString());
return (startDate === endDate &&
this.start instanceof Temporal.PlainDate &&
this.end instanceof Temporal.PlainDate);
}
get _isMultiDayTimed() {
if (this.start instanceof Temporal.PlainDate ||
this.end instanceof Temporal.PlainDate)
return false;
const startDate = dateFromDateTime(this.start.toString());
const endDate = dateFromDateTime(this.end.toString());
return startDate !== endDate;
}
get _isMultiDayFullDay() {
const startDate = dateFromDateTime(this.start.toString());
const endDate = dateFromDateTime(this.end.toString());
return (this.start instanceof Temporal.PlainDate &&
this.end instanceof Temporal.PlainDate &&
startDate !== endDate);
}
get _isSingleHybridDayTimed() {
if (!this._config.isHybridDay)
return false;
if (this.start instanceof Temporal.PlainDate ||
this.end instanceof Temporal.PlainDate)
return false;
const startDate = dateFromDateTime(this.start.toString());
const endDate = dateFromDateTime(this.end.toString());
const endDateMinusOneDay = toDateString$1(Temporal.PlainDate.from(endDate).subtract({ days: 1 }));
if (startDate !== endDate && startDate !== endDateMinusOneDay)
return false;
const dayBoundaries = this._config.dayBoundaries.value;
const eventStartTimePoints = timePointsFromString(timeFromDateTime(this.start.toString()));
const eventEndTimePoints = timePointsFromString(timeFromDateTime(this.end.toString()));
const eventIsFullyInFirstDayOfBoundary = eventEndTimePoints > eventStartTimePoints && startDate === endDate;
return ((eventStartTimePoints >= dayBoundaries.start &&
(eventEndTimePoints <= dayBoundaries.end ||
eventIsFullyInFirstDayOfBoundary)) ||
(eventStartTimePoints < dayBoundaries.end &&
eventEndTimePoints <= dayBoundaries.end));
}
get _color() {
if (this.calendarId &&
this._config.calendars.value &&
this.calendarId in this._config.calendars.value) {
return this._config.calendars.value[this.calendarId].colorName;
}
return DEFAULT_EVENT_COLOR_NAME;
}
_getForeignProperties() {
return this._foreignProperties;
}
_getExternalEvent() {
return {
id: this.id,
start: this._start,
end: this._end,
title: this.title,
people: this.people,
location: this.location,
description: this.description,
calendarId: this.calendarId,
resourceId: this.resourceId,
_options: this._options,
...this._getForeignProperties(),
};
}
}
class CalendarEventBuilder {
constructor(_config, id, start, end) {
Object.defineProperty(this, "_config", {
enumerable: true,
configurable: true,
writable: true,
value: _config
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: id
});
Object.defineProperty(this, "start", {
enumerable: true,
configurable: true,
writable: true,
value: start
});
Object.defineProperty(this, "end", {
enumerable: true,
configurable: true,
writable: true,
value: end
});
Object.defineProperty(this, "people", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "location", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "description", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "title", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "calendarId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "resourceId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_foreignProperties", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "_options", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "_customContent", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
}
build() {
return new CalendarEventImpl(this._config, this.id, this.start, this.end, this.title, this.people, this.location, this.description, this.calendarId, this._options, this._customContent, this._foreignProperties, this.resourceId);
}
withTitle(title) {
this.title = title;
return this;
}
withPeople(people) {
this.people = people;
return this;
}
withLocation(location) {
this.location = location;
return this;
}
withDescription(description) {
this.description = description;
return this;
}
withForeignProperties(foreignProperties) {
this._foreignProperties = foreignProperties;
return this;
}
withCalendarId(calendarId) {
this.calendarId = calendarId;
return this;
}
withOptions(options) {
this._options = options;
return this;
}
withCustomContent(customContent) {
this._customContent = customContent;
return this;
}
withResourceId(resourceId) {
this.resourceId = resourceId;
return this;
}
}
const deepCloneEvent = (calendarEvent, $app) => {
const calendarEventInternal = new CalendarEventBuilder($app.config, calendarEvent.id, calendarEvent._start, calendarEvent._end)
.withTitle(calendarEvent.title)
.withPeople(calendarEvent.people)
.withCalendarId(calendarEvent.calendarId)
.withForeignProperties(JSON.parse(JSON.stringify(calendarEvent._getForeignProperties())))
.withLocation(calendarEvent.location)
.withDescription(calendarEvent.description)
.withOptions(calendarEvent._options)
.withCustomContent(calendarEvent._customContent)
.build();
calendarEventInternal._nDaysInGrid = calendarEvent._nDaysInGrid;
return calendarEventInternal;
};
const getTimeGridEventCopyElementId = (id) => {
return 'time-grid-event-copy-' + id;
};
const isUIEventTouchEvent = (event) => {
return 'touches' in event && typeof event.touches === 'object';
};
const getEventCoordinates = (uiEvent) => {
const actualEvent = isUIEventTouchEvent(uiEvent)
? uiEvent.touches[0]
: uiEvent;
return {
clientX: actualEvent.clientX,
clientY: actualEvent.clientY,
};
};
const concatenatePeople = (people) => {
return people.reduce((acc, person, index) => {
if (index === 0)
return person;
if (index === people.length - 1)
return `${acc} & ${person}`;
return `${acc}, ${person}`;
}, '');
};
const dateFn = (dateTime, locale) => {
return dateTime.toLocaleString(locale, {
day: 'numeric',
month: 'long',
year: 'numeric',
});
};
const getLocalizedDate$1 = dateFn;
const timeFn = (dateTime, locale) => {
const dateTimeString = dateTime.toString();
const { year, month, date, hours, minutes } = toIntegers(dateTimeString);
return new Date(year, month, date, hours, minutes).toLocaleTimeString(locale, {
hour: 'numeric',
minute: 'numeric',
});
};
const getTimeStamp = (calendarEvent, // to facilitate testing. In reality, we will always have a full CalendarEventInternal
locale, delimiter = '\u2013') => {
var _a, _b, _c, _d;
const eventTime = { start: calendarEvent.start, end: calendarEvent.end };
if (calendarEvent._isSingleDayFullDay) {
return dateFn(eventTime.start, locale);
}
if (calendarEvent._isMultiDayFullDay) {
return `${dateFn(eventTime.start, locale)} ${delimiter} ${dateFn(eventTime.end, locale)}`;
}
if (calendarEvent._isSingleDayTimed &&
((_a = eventTime.start) === null || _a === void 0 ? void 0 : _a.toString()) !== ((_b = eventTime.end) === null || _b === void 0 ? void 0 : _b.toString())) {
return `${dateFn(eventTime.start, locale)} <span aria-hidden="true">⋅</span> ${timeFn(eventTime.start, locale)} ${delimiter} ${timeFn(eventTime.end, locale)}`;
}
if (calendarEvent._isSingleDayTimed &&
((_c = calendarEvent.start) === null || _c === void 0 ? void 0 : _c.toString()) === ((_d = calendarEvent.end) === null || _d === void 0 ? void 0 : _d.toString())) {
return `${dateFn(eventTime.start, locale)}, ${timeFn(eventTime.start, locale)}`;
}
return `${dateFn(eventTime.start, locale)}, ${timeFn(eventTime.start, locale)} ${delimiter} ${dateFn(eventTime.end, locale)}, ${timeFn(eventTime.end, locale)}`;
};
/**
* Push a task to the end of the current call stack
* */
const nextTick = (cb) => {
setTimeout(() => {
cb();
});
};
const focusModal = ($app) => {
const calendarWrapper = $app.elements.calendarWrapper;
if (!(calendarWrapper instanceof HTMLElement))
return;
const eventModal = calendarWrapper.querySelector('.sx__event-modal');
if (!(eventModal instanceof HTMLElement))
return;
setTimeout(() => {
eventModal.focus();
}, 100);
};
const invokeOnEventClickCallback = ($app, calendarEvent, e) => {
if ($app.config.callbacks.onEventClick) {
$app.config.callbacks.onEventClick(calendarEvent._getExternalEvent(), e);
}
};
const invokeOnEventDoubleClickCallback = ($app, calendarEvent, e) => {
if ($app.config.callbacks.onDoubleClickEvent) {
$app.config.callbacks.onDoubleClickEvent(calendarEvent._getExternalEvent(), e);
}
};
const timePointToPercentage = (timePointsInDay, dayBoundaries, timePoint) => {
if (timePoint < dayBoundaries.start) {
const firstDayTimePoints = 2400 - dayBoundaries.start;
return ((timePoint + firstDayTimePoints) / timePointsInDay) * 100;
}
return ((timePoint - dayBoundaries.start) / timePointsInDay) * 100;
};
const getYCoordinateInTimeGrid = (dateTime, dayBoundaries, pointsPerDay) => {
return timePointToPercentage(pointsPerDay, dayBoundaries, timePointsFromString(timeFromDateTime(dateTime.toString())));
};
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 = {}));
const AppContext = preact.createContext({});
class PreactView {
constructor(config) {
Object.defineProperty(this, "randomId", {
enumerable: true,
configurable: true,
writable: true,
value: randomStringId()
});
Object.defineProperty(this, "name", {
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, "Component", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "setDateRange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "hasSmallScreenCompat", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "hasWideScreenCompat", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "backwardForwardFn", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "backwardForwardUnits", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.name = config.name;
this.label = config.label;
this.Component = config.Component;
this.setDateRange = config.setDateRange;
this.hasSmallScreenCompat = config.hasSmallScreenCompat;
this.hasWideScreenCompat = config.hasWideScreenCompat;
this.backwardForwardFn = config.backwardForwardFn;
this.backwardForwardUnits = config.backwardForwardUnits;
}
render(onElement, $app) {
preact.render(preact.createElement(this.Component, { $app, id: this.randomId }), onElement);
}
destroy() {
const el = document.getElementById(this.randomId);
if (el) {
el.remove();
}
}
}
const createPreactView = (config) => {
return new PreactView(config);
};
function MonthViewWeek({ week }) {
const $app = hooks.useContext(AppContext$1);
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$1);
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$1);
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$1);
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$1);
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$1.Provider, { value: $app, children: [jsxRuntime.jsx(AppInput, {}), $app.datePickerState.isOpen.value && appPopupJSX] }) }) }));
}
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,