@syncfusion/react-calendars
Version:
A complete package of date or time components with built-in features such as date formatting, inline editing, multiple (range) selection, range restriction, month and year selection, strict mode, and globalization.
1,102 lines (1,101 loc) • 49.7 kB
JavaScript
import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef, useState, useImperativeHandle, forwardRef, isValidElement, useCallback } from 'react';
import { L10n, formatDate, parseDate, useProviderContext } from '@syncfusion/react-base';
import { getValue, getDefaultDateObject, isNullOrUndefined, cldrData, preRender } from '@syncfusion/react-base';
import * as React from 'react';
import { Button, Color, Variant } from '@syncfusion/react-buttons';
import { addMonths, addYears, getWeekNumber } from '../../src/utils/calendar-util';
import { ChevronLeftIcon, ChevronRightIcon, CloseIcon } from '@syncfusion/react-icons';
import { CalendarCell } from './calendar-cell';
/**
* Specifies the view options for the calendar.
*
* @enum {string}
*/
export var CalendarView;
(function (CalendarView) {
/**
* Displays the calendar by month.
*
*/
CalendarView["Month"] = "Month";
/**
* Displays the calendar by year.
*
*/
CalendarView["Year"] = "Year";
/**
* Displays the calendar by decade.
*
*/
CalendarView["Decade"] = "Decade";
})(CalendarView || (CalendarView = {}));
/**
* Specifies the format of the day to be displayed in the header.
*
* @enum {string}
*/
export var WeekDaysFormats;
(function (WeekDaysFormats) {
/**
* Short format, typically a single letter.
*
*/
WeekDaysFormats["Short"] = "Short";
/**
* Narrow format, usually a minimal abbreviation.
*
*/
WeekDaysFormats["Narrow"] = "Narrow";
/**
* Abbreviated format, a shortened form of the day name.
*
*/
WeekDaysFormats["Abbreviated"] = "Abbreviated";
/**
* Wide format, the full name of the day.
*
*/
WeekDaysFormats["Wide"] = "Wide";
})(WeekDaysFormats || (WeekDaysFormats = {}));
/**
* Specifies the rules used to determine which week is considered
* the first week of a calendar year.
*
*/
export var WeekRule;
(function (WeekRule) {
/**
* The first week begins on January first, regardless of
* which day of the week it falls on.
*
*/
WeekRule["FirstDay"] = "FirstDay";
/**
* The first week begins on the first occurrence of the
* designated weekday on or after January first.
*
*/
WeekRule["FirstFullWeek"] = "FirstFullWeek";
/**
* The first week must contain at least four days of the new year,
* following the ISO standard definition.
*
*/
WeekRule["FirstFourDayWeek"] = "FirstFourDayWeek";
})(WeekRule || (WeekRule = {}));
export const Calendar = forwardRef((props, ref) => {
const { className = '', minDate = new Date(1900, 0, 1), maxDate = new Date(2099, 11, 31), depth = CalendarView.Month, start = CalendarView.Month, firstDayOfWeek: firstDayOfWeekProp = 0, value, defaultValue, readOnly = false, weekNumber = false, weekRule = WeekRule.FirstDay, showTodayButton = true, weekDaysFormat = WeekDaysFormats.Short, disabled = false, multiSelect = false, onViewChange, cellTemplate = null, onChange, fullScreenMode = false, ...otherProps } = props;
const isControlled = value !== undefined;
const { locale } = useProviderContext();
const localeStrings = {
today: 'Today'
};
const [currentView, setCurrentView] = useState(start);
const [isImproperDateRange, setIsImproperDateRange] = useState(false);
const [isTodayDisabled, setIsTodayDisabled] = useState(false);
const [isPreviousDisabled, setIsPreviousDisabled] = useState(false);
const [isNextDisabled, setIsNextDisabled] = useState(false);
const [focusedActiveDecendent, setFocusedActiveDecendent] = useState('');
const [onSelection, setOnSelection] = useState(false);
const [headerTitle, setheaderTitle] = useState('');
const firstDayOfWeek = firstDayOfWeekProp !== null ? (firstDayOfWeekProp > 6 ? 0 : firstDayOfWeekProp) : 0;
const calendarElement = useRef(null);
const headerTitleElement = useRef(null);
const focusedElementRef = useRef(null);
const selectedElementRef = useRef(null);
const l10nInstance = L10n('calendar', localeStrings, locale || 'en-US');
const [isHeaderFocused, setIsHeaderFocused] = useState(false);
let startDecadeHdYr = '';
let endDecadeHdYr = '';
const { dir } = useProviderContext();
const [internalValue, setInternalValue] = useState(() => {
if (isControlled) {
return null;
}
if (defaultValue !== undefined) {
return defaultValue;
}
if (multiSelect) {
return [];
}
return null;
});
const currentValue = isControlled ? value : internalValue;
const normalizedDates = React.useMemo(() => {
if (multiSelect && Array.isArray(currentValue)) {
return currentValue.filter((d) => (!minDate || d >= minDate) &&
(!maxDate || d <= maxDate));
}
if (!multiSelect && currentValue && !Array.isArray(currentValue)) {
if (minDate && currentValue < minDate) {
return [minDate];
}
if (maxDate && currentValue > maxDate) {
return [maxDate];
}
if ((!minDate || currentValue >= minDate) &&
(!maxDate || currentValue <= maxDate)) {
return [currentValue];
}
}
return [];
}, [currentValue, minDate, maxDate, multiSelect]);
const [currentDate, setCurrentDate] = useState(() => {
if (normalizedDates.length > 0) {
if (multiSelect) {
return normalizedDates[normalizedDates.length - 1];
}
else {
return normalizedDates[0];
}
}
if (!isControlled && defaultValue) {
if (Array.isArray(defaultValue) && defaultValue.length > 0) {
return defaultValue[defaultValue.length - 1];
}
else if (!Array.isArray(defaultValue)) {
return defaultValue;
}
}
const today = new Date();
return today < minDate ? new Date(minDate) : today > maxDate ? new Date(maxDate) : today;
});
useEffect(() => {
setCurrentView(start);
}, [start]);
const updateValue = useCallback((newValue, event) => {
if (!isControlled) {
setInternalValue(newValue);
}
if (onChange) {
onChange({ value: newValue, event });
}
}, [isControlled, onChange]);
const publicAPI = {
value: currentValue,
multiSelect,
minDate,
maxDate,
firstDayOfWeek,
start,
depth,
weekNumber,
disabled,
weekRule,
showTodayButton,
weekDaysFormat,
cellTemplate
};
useEffect(() => {
preRender('calendar');
}, []);
useEffect(() => {
if (currentView === CalendarView.Month) {
titleUpdate(currentDate, 'days');
}
else if (currentView === CalendarView.Year) {
titleUpdate(currentDate, 'months');
}
else {
setheaderTitle(`${startDecadeHdYr} - ${endDecadeHdYr}`);
}
setIsImproperDateRange(minDate > maxDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
setIsTodayDisabled(today < minDate || today > maxDate);
iconHandler();
}, [currentDate, currentView, minDate, maxDate, onSelection]);
useEffect(() => {
if (focusedElementRef.current) {
setFocusedActiveDecendent(focusedElementRef.current.id);
}
}, [focusedElementRef.current]);
const iconHandler = () => {
const current = new Date(currentDate);
current.setDate(1);
switch (currentView) {
case CalendarView.Month:
setIsPreviousDisabled(compareMonth(currentDate, minDate) < 1);
setIsNextDisabled(compareMonth(currentDate, maxDate) > -1);
break;
case CalendarView.Year:
setIsPreviousDisabled(compare(currentDate, minDate, 0) < 1);
setIsNextDisabled(compare(currentDate, maxDate, 0) > -1);
break;
case CalendarView.Decade:
setIsPreviousDisabled(compare(currentDate, minDate, 10) < 1);
setIsNextDisabled(compare(currentDate, maxDate, 10) > -1);
}
};
const previous = () => {
switch (currentView) {
case CalendarView.Month:
setCurrentDate(addMonths(currentDate, -1));
break;
case CalendarView.Year:
setCurrentDate(addYears(currentDate, -1));
break;
case CalendarView.Decade:
setCurrentDate(addYears(currentDate, -10));
break;
}
};
const next = () => {
switch (currentView) {
case CalendarView.Month:
setCurrentDate(addMonths(currentDate, 1));
break;
case CalendarView.Year:
setCurrentDate(addYears(currentDate, 1));
break;
case CalendarView.Decade:
setCurrentDate(addYears(currentDate, 10));
break;
}
};
const compareMonth = (start, end) => {
let result;
if (start.getFullYear() > end.getFullYear()) {
result = 1;
}
else if (start.getFullYear() < end.getFullYear()) {
result = -1;
}
else {
result = start.getMonth() === end.getMonth() ? 0 : start.getMonth() > end.getMonth() ? 1 : -1;
}
return result;
};
const compare = (startDate, endDate, modifier) => {
let start = endDate.getFullYear();
let end = start;
let result = 0;
if (modifier) {
start = start - (start % modifier);
end = start - (start % modifier) + modifier - 1;
}
if (startDate.getFullYear() > end) {
result = 1;
}
else if (startDate.getFullYear() < start) {
result = -1;
}
return result;
};
const addContentFocus = () => {
if (selectedElementRef.current) {
selectedElementRef.current.classList.add('sf-focused-cell');
}
else if (focusedElementRef.current) {
focusedElementRef.current.classList.add('sf-focused-cell');
}
};
const removeContentFocus = () => {
if (selectedElementRef.current) {
selectedElementRef.current.classList.remove('sf-focused-cell');
}
else if (focusedElementRef.current) {
focusedElementRef.current.classList.remove('sf-focused-cell');
}
};
const navigatedTo = (e, view, date) => {
e.preventDefault();
if (date && +date >= +minDate && +date <= +maxDate) {
setCurrentDate(date);
}
if (date && +date <= +minDate) {
setCurrentDate(new Date(minDate));
}
if (date && +date >= +maxDate) {
setCurrentDate(new Date(maxDate));
}
if (onViewChange) {
onViewChange({
event: e,
view: view,
date: currentDate || date
});
}
setCurrentView(view);
};
const minMaxDate = (localDate) => {
const currentDate = new Date(new Date(+localDate).setHours(0, 0, 0, 0));
const min = new Date(new Date(+minDate).setHours(0, 0, 0, 0));
const max = new Date(new Date(+maxDate).setHours(0, 0, 0, 0));
if (+currentDate === +min || +currentDate === +max) {
if (+localDate < +minDate) {
return new Date(+minDate);
}
if (+localDate > +maxDate) {
return new Date(+maxDate);
}
}
return localDate;
};
const renderMonths = (date) => {
const monthPerRow = weekNumber ? 8 : 7;
const tdEles = renderDays(date);
return (_jsxs(_Fragment, { children: [createContentHeader(), renderTemplate(tdEles, monthPerRow, 'sf-month')] }));
};
const todayButtonClick = (e) => {
if (showTodayButton) {
e.preventDefault();
const tempValue = new Date(new Date().setHours(0, 0, 0, 0));
const dateValue = new Date(+tempValue.getTime());
if (isControlled) {
if (onChange) {
onChange({ value: multiSelect ? [tempValue] : tempValue, ...e });
}
}
else {
if (multiSelect) {
const currentValues = Array.isArray(currentValue) ? currentValue : [];
const newValues = [...currentValues, tempValue];
updateValue(newValues, e);
}
else {
updateValue(tempValue, e);
}
}
if (depth !== CalendarView.Month) {
navigatedTo(e, depth, new Date(dateValue));
}
else {
if (getViewNumber(start) >= getViewNumber(depth)) {
navigatedTo(e, depth, new Date(dateValue));
}
else {
navigatedTo(e, CalendarView.Month, new Date(dateValue));
}
}
}
};
const renderDays = (currentDate, isTodayDate) => {
const tdEles = [];
const totalDaysInGrid = 42;
const todayDate = isTodayDate ? new Date(+currentDate) : new Date();
let localDate = new Date(currentDate);
const currentMonth = localDate.getMonth();
let minMaxDates;
localDate = new Date(localDate.getFullYear(), localDate.getMonth(), 0, localDate.getHours(), localDate.getMinutes(), localDate.getSeconds(), localDate.getMilliseconds());
while (localDate.getDay() !== firstDayOfWeek) {
setStartDate(localDate, -1 * 86400000);
}
for (let day = 0; day < totalDaysInGrid; ++day) {
const isWeekNumber = day % 7 === 0 && weekNumber;
minMaxDates = new Date(+localDate);
localDate = minMaxDate(localDate);
const dateFormatOptions = { locale: locale, type: 'dateTime', skeleton: 'full' };
const date = parseDate(formatDate(localDate, dateFormatOptions), dateFormatOptions);
const isOtherMonth = date.getMonth() !== currentMonth;
const isToday = date && todayDate &&
date.getFullYear() === todayDate.getFullYear() &&
date.getMonth() === todayDate.getMonth() &&
date.getDate() === todayDate.getDate();
const isSelected = multiSelect
? normalizedDates.some((selectedDate) => selectedDate && date.toDateString() === selectedDate.toDateString())
: normalizedDates.length > 0 && date.toDateString() === normalizedDates[0].toDateString();
const isDisabled = (minDate > date) || (maxDate < date);
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
let isFocused = false;
if (currentDate.getDate() === localDate.getDate() && !isOtherMonth && !isDisabled) {
isFocused = true;
}
else {
if (currentDate >= maxDate && parseInt(`${date.valueOf()}`, 10) === +maxDate && !isOtherMonth && !isDisabled) {
isFocused = true;
}
if (currentDate <= minDate && parseInt(`${date.valueOf()}`, 10) === +minDate && !isOtherMonth && !isDisabled) {
isFocused = true;
}
}
const baseClasses = `${isOtherMonth ? 'sf-other-month' : ''} ${isWeekend ? 'sf-weekend' : ''} ${isDisabled ? 'sf-disabled sf-overlay' : ''}`;
const disabledClass = disabled ? 'sf-disabled sf-overlay' : '';
const className = `sf-cell ${baseClasses} ${disabledClass} ${isToday ? 'sf-today' : ''} ${isToday && isFocused && !disabled ? 'sf-focused-date' : ''} ${isFocused && !onSelection && !disabled ? 'sf-focused-date' : ''} ${isSelected ? 'sf-selected' : ''}`.trim();
if (isWeekNumber) {
tdEles.push(_jsx("td", { className: 'sf-cell sf-week-number', style: { cursor: 'default' }, children: _jsx("span", { children: (() => {
const numberOfDays = weekRule === 'FirstDay' ? 6 : (weekRule === 'FirstFourDayWeek' ? 3 : 0);
const finalDate = new Date(localDate.getFullYear(), localDate.getMonth(), (localDate.getDate() + numberOfDays));
return getWeekNumber(finalDate);
})() }) }, `week-${day}`));
}
const cellProps = {
id: `${localDate.valueOf()}`,
className: className,
isDisabled: isDisabled || (disabled ? true : false),
isOutOfRange: isOtherMonth,
isToday: isToday,
isSelected: isSelected,
isFocused: isFocused && !onSelection,
isWeekend: isWeekend,
date: new Date(date),
onClick: (e) => {
if (disabled) {
return;
}
clickHandler(e, date);
},
title: formatDate(date, { locale: locale, type: 'date', skeleton: 'full' })
};
if (typeof cellTemplate === 'function') {
try {
const wrappedCellProps = {
...cellProps,
ref: (el) => {
if (isFocused && el) {
focusedElementRef.current = el;
}
if (isSelected && el) {
selectedElementRef.current = el;
}
}
};
const CustomCell = cellTemplate;
tdEles.push(CustomCell(wrappedCellProps));
}
catch (error) {
tdEles.push(_jsx(CalendarCell, { ...cellProps, ref: (el) => {
if (isFocused && el) {
focusedElementRef.current = el;
}
if (isSelected && el) {
selectedElementRef.current = el;
}
}, children: renderDayCells(localDate, isDisabled, isOtherMonth) }));
}
}
else {
tdEles.push(_jsx(CalendarCell, { ...cellProps, ref: (el) => {
if (isFocused && el) {
focusedElementRef.current = el;
}
if (isSelected && el) {
selectedElementRef.current = el;
}
}, children: renderDayCells(localDate, isDisabled, isOtherMonth) }));
}
localDate = new Date(+minMaxDates);
localDate.setDate(localDate.getDate() + 1);
}
return tdEles;
};
const setStartDate = (date, time) => {
const tzOffset = date.getTimezoneOffset();
const d = new Date(date.getTime() + time);
const tzOffsetDiff = d.getTimezoneOffset() - tzOffset;
const minutesMilliSeconds = 60000;
date.setTime(d.getTime() + tzOffsetDiff * minutesMilliSeconds);
};
const renderDayCells = (date, isDisabled, isOtherMonth) => {
const title = formatDate(date, { locale: locale, type: 'date', skeleton: 'full' });
const dayText = formatDate(date, { locale: locale, format: 'd', type: 'date', skeleton: 'yMd' });
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
if (isValidElement(cellTemplate)) {
return cellTemplate;
}
else if (typeof cellTemplate === 'function') {
const isCalendarCellComponent = (cellTemplate).length === 1;
if (isCalendarCellComponent) {
return null;
}
else {
const customContent = cellTemplate(date, isWeekend, currentView, currentDate);
if (customContent) {
return customContent;
}
}
}
return (_jsx("span", { className: "sf-day", title: title, "aria-disabled": isDisabled || isOtherMonth, children: dayText }));
};
const createContentHeader = () => {
const effectiveFirstDayOfWeek = firstDayOfWeek;
const shortNames = !isNullOrUndefined(weekDaysFormat) ? getCultureValues().length > 0
? shiftArray(getCultureValues(), effectiveFirstDayOfWeek)
: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] : [];
return (_jsx("thead", { className: 'sf-week-header', children: _jsxs("tr", { children: [weekNumber && _jsx("th", { className: 'sf-week-number', "aria-hidden": 'true' }), shortNames.slice(0, 7).map((day, index) => (_jsx("th", { className: '', children: toCapitalize(day) }, index)))] }) }));
};
const getCultureValues = () => {
const culShortNames = [];
let cldrObj;
const dayFormat = 'days.stand-alone.' + weekDaysFormat?.toLowerCase();
if ((locale === 'en' || locale === 'en-US') && !isNullOrUndefined(dayFormat)) {
cldrObj = getValue(dayFormat, getDefaultDateObject());
}
else {
cldrObj = getCultureObjects(cldrData, (locale || 'en-US'));
}
if (!isNullOrUndefined(cldrObj)) {
for (const obj of Object.keys(cldrObj)) {
culShortNames.push(getValue(obj, cldrObj));
}
}
return culShortNames;
};
const getCultureObjects = (ld, c) => {
const gregorianFormat = weekDaysFormat
? `.dates.calendars.gregorian.days.format.${weekDaysFormat.toLowerCase()}`
: '';
const mainVal = 'main.';
return getValue(`${mainVal}${c}${gregorianFormat}`, ld);
};
const shiftArray = (array, places) => {
return array.slice(places).concat(array.slice(0, places));
};
const renderTemplate = (elements, count, classNm) => {
const rows = [];
for (let i = 0; i < elements.length; i += count) {
const trElements = elements.slice(i, i + count);
const hasOtherMonth = trElements.some((element) => isValidElement(element) &&
'className' in element.props &&
typeof element.props.className === 'string' &&
element.props.className.includes('sf-other-month'));
const isOtherMonthRow = hasOtherMonth && trElements.every((element) => isValidElement(element) &&
'className' in element.props &&
typeof element.props.className === 'string' &&
(element.props.className.includes('sf-other-month') ||
element.props.className.includes('sf-week-number')));
rows.push(_jsx("tr", { className: isOtherMonthRow && i === 0 ? 'sf-month-hide' : '', children: trElements }, i));
}
return (_jsx("tbody", { className: classNm, children: rows }));
};
const renderYears = () => {
const monthPerRow = 3;
const tdEles = [];
const curDate = new Date(currentDate);
const curYear = curDate.getFullYear();
const curMonth = curDate.getMonth();
const min = new Date(minDate);
const max = new Date(maxDate);
const minYear = min.getFullYear();
const minMonth = min.getMonth();
const maxYear = max.getFullYear();
const maxMonth = max.getMonth();
const selectedDate = multiSelect
? (Array.isArray(currentValue) && currentValue.length > 0 ? currentValue[currentValue.length - 1] : null)
: (currentValue && !Array.isArray(currentValue) ? currentValue : null);
const selectedMonth = selectedDate?.getMonth();
const selectedYear = selectedDate?.getFullYear();
for (let month = 0; month < 12; ++month) {
const monthDate = new Date(curYear, month, 1);
const isFocusedDate = month === curMonth;
const isSelected = selectedMonth === month && selectedYear === curYear;
const isDisabled = (minDate && (curYear < minYear || (curYear === minYear && month < minMonth))) ||
(maxDate && (curYear > maxYear || (curYear === maxYear && month > maxMonth)));
const className = 'sf-cell' +
(isSelected ? ' sf-selected' : isFocusedDate ? ' sf-focused-date' : '') +
(isDisabled ? ' sf-disabled' : '');
tdEles.push(_jsx("td", { className: className, id: `${monthDate.valueOf()}`, onClick: (e) => handleCellClick(e, monthDate, currentView), ref: (el) => {
if (isFocusedDate && el) {
focusedElementRef.current = el;
}
if (isSelected && el) {
selectedElementRef.current = el;
}
}, "aria-selected": isSelected || false, children: renderYearCell(monthDate) }, month));
}
return _jsx(_Fragment, { children: renderTemplate(tdEles, monthPerRow, 'sf-zoomin') });
};
const renderYearCell = (localDate) => {
const title = formatDate(localDate, { locale: locale, type: 'date', format: 'MMM y' });
const content = toCapitalize(formatDate(localDate, { locale: locale,
format: undefined,
type: 'dateTime',
skeleton: 'MMM' }));
return (_jsx("span", { className: 'sf-day', title: title, children: content }));
};
const renderDecades = () => {
const yearPerRow = 3;
const yearCell = 12;
const tdEles = [];
const curDate = new Date(currentDate);
const curYear = curDate.getFullYear();
const localDate = new Date(curDate);
localDate.setMonth(0);
const baseDecadeStartYear = curYear - (curYear % 10);
const startYr = new Date(localDate.setFullYear(baseDecadeStartYear));
const endYr = new Date(localDate.setFullYear(baseDecadeStartYear + 9));
const startFullYr = startYr.getFullYear();
const endFullYr = endYr.getFullYear();
startDecadeHdYr = formatDate(startYr, { locale: locale,
format: undefined, type: 'dateTime', skeleton: 'y' });
endDecadeHdYr = formatDate(endYr, { locale: locale,
format: undefined, type: 'dateTime', skeleton: 'y' });
const minYear = new Date(minDate).getFullYear();
const maxYear = new Date(maxDate).getFullYear();
const selectedDate = multiSelect
? (Array.isArray(currentValue) && currentValue.length > 0 ? currentValue[currentValue.length - 1] : null)
: (currentValue && !Array.isArray(currentValue) ? currentValue : null);
const startYear = baseDecadeStartYear - 1;
const selectedYear = selectedDate?.getFullYear() || null;
for (let i = 0; i < yearCell; ++i) {
const year = startYear + i;
localDate.setFullYear(year);
const fullYear = localDate.getFullYear();
const isOutOfRange = fullYear < minYear || fullYear > maxYear;
const isOtherDecade = fullYear < startFullYr || fullYear > endFullYr;
let className = 'sf-cell';
let isSelected = false;
let isFocused = false;
if (isOutOfRange) {
className += ' sf-disabled';
}
if (isOtherDecade) {
className += ' sf-other-year';
}
if (selectedYear === fullYear) {
className += ' sf-selected';
isSelected = true;
}
else if (fullYear === curYear) {
className += ' sf-focused-date';
isFocused = true;
}
const decadeDate = new Date(localDate);
tdEles.push(_jsx("td", { className: className, id: `${decadeDate.valueOf()}`, onClick: (e) => handleCellClick(e, decadeDate, currentView), ref: (el) => {
if (isFocused && el) {
focusedElementRef.current = el;
}
if (isSelected && el) {
selectedElementRef.current = el;
}
}, "aria-selected": isSelected || false, children: renderDecadeCell(decadeDate) }, year));
}
return _jsx(_Fragment, { children: renderTemplate(tdEles, yearPerRow, 'sf-zoomin') });
};
const renderDecadeCell = (localDate) => {
const content = formatDate(localDate, { locale: locale,
format: undefined, type: 'dateTime', skeleton: 'y' });
return (_jsx("span", { className: 'sf-day', children: content }));
};
const handleCellClick = useCallback((e, date, view) => {
setCurrentDate(date);
if (view === CalendarView.Year) {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const firstValidDay = new Date(firstDay);
while (firstValidDay <= lastDay) {
if (firstValidDay >= minDate && firstValidDay <= maxDate) {
break;
}
firstValidDay.setDate(firstValidDay.getDate() + 1);
}
if (firstValidDay > lastDay) {
return;
}
if (depth === CalendarView.Year) {
updateValue(firstValidDay, e);
}
else {
navigatedTo(e, CalendarView.Month, firstValidDay);
}
return;
}
if (depth === CalendarView.Decade) {
updateValue(date, e);
}
else {
navigatedTo(e, CalendarView.Year, date);
}
}, [minDate, maxDate, depth, updateValue, navigatedTo]);
const titleUpdate = (date, view) => {
const dayFormatOptions = formatDate(date, { locale: locale,
type: 'dateTime', skeleton: 'yMMMM', calendar: 'gregorian' });
const monthFormatOptions = formatDate(date, { locale: locale,
format: undefined, type: 'dateTime', skeleton: 'y', calendar: 'gregorian' });
switch (view) {
case 'days':
if (headerTitleElement.current) {
setheaderTitle(toCapitalize(dayFormatOptions));
}
break;
case 'months':
if (headerTitleElement.current) {
setheaderTitle(monthFormatOptions);
}
break;
}
};
const clickHandler = (e, value) => {
if (readOnly || disabled || isImproperDateRange) {
return;
}
const isOtherMonth = currentView === CalendarView.Month &&
value.getMonth() !== currentDate.getMonth();
if (isOtherMonth) {
const newDate = new Date(value);
setCurrentDate(newDate);
return;
}
const storeView = currentView;
selectDate(e, getIdValue(e, null));
if (multiSelect && currentDate !== value && (storeView === CalendarView.Year || storeView === CalendarView.Month)) {
if (focusedElementRef.current) {
focusedElementRef.current.classList.remove('sf-focused-date');
}
}
if (calendarElement.current) {
calendarElement.current.focus();
}
};
const selectDate = (e, date) => {
if (isControlled) {
if (onChange) {
let newValue;
if (multiSelect) {
const currentValues = Array.isArray(currentValue) ? currentValue : [];
const dateExists = currentValues.some((v) => v.toDateString() === date.toDateString());
if (dateExists) {
newValue = currentValues.filter((v) => v.toDateString() !== date.toDateString());
}
else {
newValue = [...currentValues, date];
}
}
else {
newValue = date;
}
onChange({ value: newValue, ...e });
}
}
else {
if (multiSelect) {
const currentValues = Array.isArray(currentValue) ? currentValue : [];
const dateExists = currentValues.some((v) => v.toDateString() === date.toDateString());
let newValues;
if (dateExists) {
newValues = currentValues.filter((v) => v.toDateString() !== date.toDateString());
}
else {
newValues = [...currentValues, date];
}
updateValue(newValues, e);
}
else {
updateValue(date, e);
}
}
setOnSelection(true);
setCurrentDate(date);
};
const getIdValue = (e, element) => {
let eve;
if (e) {
eve = e.currentTarget;
}
else {
eve = element;
}
const dateFormatOptions = { locale: locale, type: 'dateTime', skeleton: 'full', calendar: 'gregorian' };
const dateString = formatDate(new Date(parseInt('' + eve.getAttribute('id'), 10)), dateFormatOptions);
const date = parseDate(dateString, dateFormatOptions);
const value = date.valueOf() - date.valueOf() % 1000;
return new Date(value);
};
const handleHeaderTitleClick = (e, view) => {
switch (view) {
case CalendarView.Month:
navigatedTo(e, CalendarView.Year);
break;
case CalendarView.Year:
navigatedTo(e, CalendarView.Decade);
break;
default:
break;
}
};
const toCapitalize = (text) => {
return text.charAt(0).toUpperCase() + text.slice(1);
};
const getViewNumber = (view) => {
switch (view) {
case CalendarView.Month:
return 0;
case CalendarView.Year:
return 1;
case CalendarView.Decade:
return 2;
default:
return 0;
}
};
const keyActionHandle = (e) => {
if (calendarElement.current === null || e.key === 'Escape' || readOnly || disabled || isImproperDateRange || isHeaderFocused) {
return;
}
e.stopPropagation();
setOnSelection(false);
const selectedDate = multiSelect ? selectedElementRef.current : focusedElementRef.current;
const view = getViewNumber(currentView);
const depthValue = getViewNumber(depth);
const levelRestrict = (view === depthValue && getViewNumber(start) >= depthValue);
const element = focusedElementRef.current || selectedDate;
switch (e.key) {
case 'ArrowLeft':
keyboardNavigate(-1, view);
e.preventDefault();
break;
case 'ArrowRight':
keyboardNavigate(1, view);
e.preventDefault();
break;
case 'ArrowUp':
keyboardNavigate(view === 0 ? -7 : -4, view);
e.preventDefault();
break;
case 'ArrowDown':
keyboardNavigate(view === 0 ? 7 : 4, view);
e.preventDefault();
break;
case 'Enter':
if (element) {
if (levelRestrict) {
const d = new Date(parseInt(element.id, 10));
selectDate(e, d);
}
else if (!e.currentTarget.className.includes('sf-disabled')) {
handleCellClick(e, currentDate, currentView);
}
}
break;
case 'Home':
setCurrentDate(firstDay(currentDate));
e.preventDefault();
break;
case 'End':
setCurrentDate(lastDay(currentDate, view));
e.preventDefault();
break;
case 'PageUp':
if (e.shiftKey) {
setCurrentDate(addYears(currentDate, -1));
}
else {
setCurrentDate(addMonths(currentDate, -1));
}
e.preventDefault();
break;
case 'PageDown':
if (e.shiftKey) {
setCurrentDate(addYears(currentDate, 1));
}
else {
setCurrentDate(addMonths(currentDate, 1));
}
e.preventDefault();
break;
}
if (e.ctrlKey) {
switch (e.key) {
case 'ArrowUp':
if (view < 2 && view >= getViewNumber(depth)) {
const nextView = view === 0 ? CalendarView.Year : CalendarView.Decade;
navigatedTo(e, nextView);
}
e.preventDefault();
break;
case 'ArrowDown':
if (view > 0 && view > depthValue) {
const nextView = view === 2 ? CalendarView.Year : CalendarView.Month;
navigatedTo(e, nextView, currentDate);
}
e.preventDefault();
break;
case 'Home':
navigatedTo(e, CalendarView.Month, new Date(currentDate.getFullYear(), 0, 1));
e.preventDefault();
break;
case 'End':
navigatedTo(e, CalendarView.Month, new Date(currentDate.getFullYear(), 11, 31));
e.preventDefault();
break;
}
}
};
const firstDay = (date) => {
const view = getViewNumber(currentView);
const visibleDates = getVisibleDates(date, view);
for (const d of visibleDates) {
if (!isDateDisabled(d)) {
return d;
}
}
return date;
};
const lastDay = (date, view) => {
const lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0);
if (view !== 2) {
const timeOffset = Math.abs(lastDate.getTimezoneOffset() - firstDay(date).getTimezoneOffset());
if (timeOffset) {
lastDate.setHours(firstDay(date).getHours() + (timeOffset / 60));
}
return findLastDay(lastDate, view);
}
else {
return findLastDay(firstDay(lastDate), view);
}
};
const findLastDay = (date, view) => {
const visibleDates = getVisibleDates(date, view).reverse();
for (const d of visibleDates) {
if (!isDateDisabled(d)) {
return d;
}
}
return date;
};
const isDateDisabled = (date) => {
return (minDate && date < minDate) || (maxDate && date > maxDate);
};
const getVisibleDates = (baseDate, view) => {
const result = [];
if (view === 0) {
const start = new Date(baseDate.getFullYear(), baseDate.getMonth(), 1);
const end = new Date(baseDate.getFullYear(), baseDate.getMonth() + 1, 0);
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
result.push(new Date(d));
}
}
else if (view === 1) {
for (let m = 0; m < 12; m++) {
result.push(new Date(baseDate.getFullYear(), m, 1));
}
}
else if (view === 2) {
const decadeStart = baseDate.getFullYear() - (baseDate.getFullYear() % 10) - 1;
for (let y = decadeStart; y < decadeStart + 12; y++) {
result.push(new Date(y, 0, 1));
}
}
return result;
};
const keyboardNavigate = (num, view) => {
const date = new Date(currentDate);
switch (view) {
case 2:
if (isMonthYearRange(date)) {
setCurrentDate(addYears(currentDate, num));
}
else {
setCurrentDate(currentDate);
}
break;
case 1:
if (isMonthYearRange(date)) {
setCurrentDate(addMonths(currentDate, num));
}
else {
setCurrentDate(currentDate);
}
break;
case 0:
date.setDate(date.getDate() + num);
if (+date >= +minDate && +date <= +maxDate) {
setCurrentDate(date);
}
else {
setCurrentDate(currentDate);
}
break;
}
};
const isMonthYearRange = (date) => {
return date.getMonth() >= minDate.getMonth()
&& date.getFullYear() >= minDate.getFullYear()
&& date.getMonth() <= maxDate.getMonth()
&& date.getFullYear() <= maxDate.getFullYear();
};
const renderModelHeader = () => {
if (!fullScreenMode) {
return null;
}
return (_jsxs("div", { className: "sf-model-header", children: [_jsx("div", { className: "sf-popup-close", role: "button", "aria-label": "close", children: _jsx(CloseIcon, { width: 14, height: 14 }) }), showTodayButton && (_jsx(Button, { tabIndex: disabled || isImproperDateRange ? -1 : 0, className: `sf-today sf-primary ${isTodayDisabled || disabled || isImproperDateRange ? 'sf-disabled' : ''}`, variant: Variant.Standard, disabled: isTodayDisabled || disabled || isImproperDateRange, onClick: (e) => todayButtonClick(e), "aria-label": 'Today', children: l10nInstance.getConstant('today') })), _jsxs("div", { className: "sf-day-wrapper", children: [_jsx("span", { className: "sf-model-day", children: formatDate(currentValue && !Array.isArray(currentValue) ? currentValue : new Date(), { locale: locale || 'en-US', format: 'E', type: 'date' }) }), _jsx("span", { className: "sf-model-month", children: formatDate(currentValue && !Array.isArray(currentValue) ? currentValue : new Date(), { locale: locale || 'en-US', format: 'MMM d', type: 'date' }) })] })] }));
};
useImperativeHandle(ref, () => ({
...publicAPI,
element: calendarElement.current
}), [publicAPI]);
const classNames = [
'sf-calendar',
className,
'sf-control',
dir === 'rtl' ? 'sf-rtl' : '',
(isImproperDateRange || disabled) ? 'sf-overlay' : '',
weekDaysFormat === WeekDaysFormats.Wide ? 'sf-calendar-day-header-lg' : '',
weekNumber ? 'sf-week-number' : '',
readOnly ? 'sf-readonly' : ''
].filter(Boolean).join(' ');
return (_jsxs("div", { ref: calendarElement, className: classNames, tabIndex: 0, "data-role": 'calendar', onKeyDown: keyActionHandle, onClick: () => {
if (calendarElement.current) {
calendarElement.current.focus();
}
}, ...otherProps, children: [fullScreenMode && renderModelHeader(), _jsxs("div", { className: `sf-header ${currentView === CalendarView.Decade ? 'sf-decade' : currentView === CalendarView.Year ? 'sf-year' : 'sf-month'}`, children: [_jsx("div", { ref: headerTitleElement, className: "sf-day sf-title", onClick: (e) => {
if (!disabled && !isImproperDateRange) {
handleHeaderTitleClick(e, currentView);
}
}, onKeyDown: (e) => {
if (e.key === 'Enter') {
e.preventDefault();
if (!disabled && !isImproperDateRange) {
handleHeaderTitleClick(e, currentView);
}
}
}, "aria-atomic": "true", "aria-live": "assertive", "aria-label": "title", tabIndex: disabled || isImproperDateRange ? -1 : 0, onFocus: () => setIsHeaderFocused(true), onMouseUp: (e) => {
e.currentTarget.blur();
}, onBlur: () => {
setIsHeaderFocused(false);
if (focusedElementRef.current) {
focusedElementRef.current.focus();
}
}, children: (() => {
if (typeof cellTemplate === 'function') {
const customTitle = cellTemplate(currentView, true);
if (customTitle) {
return customTitle;
}
}
return headerTitle;
})() }), _jsxs("div", { className: 'sf-icon-container', children: [_jsx(Button, { className: `sf-prev sf-round ${isPreviousDisabled || disabled || isImproperDateRange ? 'sf-overlay' : ''}`, disabled: isPreviousDisabled || disabled || isImproperDateRange, variant: Variant.Standard, color: Color.Secondary, onClick: () => {
setOnSelection(false);
previous();
}, onKeyDown: (e) => {
if (e.key === 'Enter') {
e.preventDefault();
setOnSelection(false);
previous();
}
}, "aria-label": 'previous month', title: currentView === CalendarView.Month ? 'Previous Month' :
currentView === CalendarView.Year ? 'Previous Year' : 'Previous Decade', "aria-disabled": isPreviousDisabled, icon: _jsx(ChevronLeftIcon, { viewBox: '0 0 26 24' }), onMouseUp: (e) => {
e.currentTarget.blur();
}, onFocus: () => setIsHeaderFocused(true), onBlur: () => {
setIsHeaderFocused(false);
} }), _jsx(Button, { className: `sf-next sf-round ${isNextDisabled || disabled || isImproperDateRange ? 'sf-overlay' : ''}`, disabled: isNextDisabled || disabled || isImproperDateRange, variant: Variant.Standard, color: Color.Secondary, onClick: () => {
setOnSelection(false);
next();
}, "aria-label": 'next month', title: currentView === CalendarView.Month ? 'Next Month' :
currentView === CalendarView.Year ? 'Next Year' : 'Next Decade', "aria-disabled": isNextDisabled, icon: _jsx(ChevronRightIcon, { viewBox: '0 0 20 26' }), onMouseUp: (e) => {
e.currentTarget.blur();
}, onFocus: () => setIsHeaderFocused(true), onBlur: () => {
setIsHeaderFocused(false);
}, onKeyDown: (e) => {
if (e.key === 'Enter') {
e.preventDefault();
setOnSelection(false);
next();
}
} })] })] }), _jsx("div", { className: `sf-content ${currentView === CalendarView.Decade ? 'sf-decade' : currentView === CalendarView.Year ? 'sf-year' : 'sf-month'}`, children: _jsx("table", { className: 'sf-calendar-content-table', onFocus: addContentFocus, onBlur: removeContentFocus, tabIndex: disabled || isImproperDateRange ? -1 : 0, "aria-activedescendant": focusedActiveDecendent, "aria-labelledby": 'element', children: (() => {
switch (getViewNumber(currentView)) {
case 0:
return renderMonths(currentDate);
case 1:
return renderYears();
case 2:
return renderDecades();
default:
return null;
}
})() || null }) }), showTodayButton && (_jsx("div", { className: 'sf-footer-container', children: _jsx(Button, { tabIndex: disabled || isImproperDateRange ? -1 : 0, className: `sf-today sf-primary sf-css ${isTodayDisabled || disabled || isImproperDateRange ? 'sf-disabled' : ''}`, variant: Variant.Standard, disabled: isTodayDisabled || disabled || isImproperDateRange, onClick: (e) => todayButtonClick(e), "aria-label": 'Today', children: l10nInstance.getConstant('today') }) }))] }));
});
export default React.memo(Calendar);