279 lines
15 KiB
JavaScript
279 lines
15 KiB
JavaScript
import { __assign } from "tslib";
|
|
import * as React from 'react';
|
|
import { getRTL, classNamesFunction } from '@fluentui/utilities';
|
|
import { FocusZone } from '../../FocusZone';
|
|
import { getDateRangeArray, getDayGrid, getBoundedDateRange, isRestrictedDate, DAYS_IN_WEEK, compareDates, DateRangeType, } from '@fluentui/date-time-utilities';
|
|
import { usePrevious, useId } from '@fluentui/react-hooks';
|
|
import { CalendarMonthHeaderRow } from './CalendarMonthHeaderRow';
|
|
import { CalendarGridRow } from './CalendarGridRow';
|
|
var getClassNames = classNamesFunction();
|
|
function useDayRefs() {
|
|
var daysRef = React.useRef({});
|
|
var getSetRefCallback = function (dayKey) { return function (element) {
|
|
if (element === null) {
|
|
delete daysRef.current[dayKey];
|
|
}
|
|
else {
|
|
daysRef.current[dayKey] = element;
|
|
}
|
|
}; };
|
|
return [daysRef, getSetRefCallback];
|
|
}
|
|
function useWeeks(props, onSelectDate, getSetRefCallback) {
|
|
/**
|
|
* Initial parsing of the given props to generate IDayInfo two dimensional array, which contains a representation
|
|
* of every day in the grid. Convenient for helping with conversions between day refs and Date objects in callbacks.
|
|
*/
|
|
var weeks = React.useMemo(function () {
|
|
var _a;
|
|
var weeksGrid = getDayGrid(props);
|
|
var firstVisibleDay = weeksGrid[1][0].originalDate;
|
|
var lastVisibleDay = weeksGrid[weeksGrid.length - 1][6].originalDate;
|
|
var markedDays = ((_a = props.getMarkedDays) === null || _a === void 0 ? void 0 : _a.call(props, firstVisibleDay, lastVisibleDay)) || [];
|
|
/**
|
|
* Weeks is a 2D array. Weeks[0] contains the last week of the prior range,
|
|
* Weeks[weeks.length - 1] contains first week of next range. These are for transition states.
|
|
*
|
|
* Weeks[1... weeks.length - 2] contains the actual visible data
|
|
*/
|
|
var returnValue = [];
|
|
for (var weekIndex = 0; weekIndex < weeksGrid.length; weekIndex++) {
|
|
var week = [];
|
|
var _loop_1 = function (dayIndex) {
|
|
var day = weeksGrid[weekIndex][dayIndex];
|
|
var dayInfo = __assign(__assign({ onSelected: function () { return onSelectDate(day.originalDate); }, setRef: getSetRefCallback(day.key) }, day), { isMarked: day.isMarked || (markedDays === null || markedDays === void 0 ? void 0 : markedDays.some(function (markedDay) { return compareDates(day.originalDate, markedDay); })) });
|
|
week.push(dayInfo);
|
|
};
|
|
for (var dayIndex = 0; dayIndex < DAYS_IN_WEEK; dayIndex++) {
|
|
_loop_1(dayIndex);
|
|
}
|
|
returnValue.push(week);
|
|
}
|
|
return returnValue;
|
|
// TODO: this is missing deps on getSetRefCallback and onSelectDate (and depending on the entire
|
|
// props object may not be a good idea due to likely frequent mutation). It would be easy to
|
|
// fix getSetRefCallback to not mutate every render, but onSelectDate is passed down from
|
|
// Calendar and trying to fix it requires a huge cascade of changes.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [props]);
|
|
return weeks;
|
|
}
|
|
/**
|
|
* Hook to determine whether to animate the CalendarDayGrid forwards or backwards
|
|
* @returns true if the grid should animate backwards; false otherwise
|
|
*/
|
|
function useAnimateBackwards(weeks) {
|
|
var previousNavigatedDate = usePrevious(weeks[0][0].originalDate);
|
|
if (!previousNavigatedDate || previousNavigatedDate.getTime() === weeks[0][0].originalDate.getTime()) {
|
|
return undefined;
|
|
}
|
|
else if (previousNavigatedDate <= weeks[0][0].originalDate) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
function useWeekCornerStyles(props) {
|
|
/**
|
|
*
|
|
* Section for setting the rounded corner styles on individual day cells. Individual day cells need different
|
|
* corners to be rounded depending on which date range type and where the cell is located in the current grid.
|
|
* If we just round all of the corners, there isn't a good overlap and we get gaps between contiguous day boxes
|
|
* in Edge browser.
|
|
*
|
|
*/
|
|
var getWeekCornerStyles = function (classNames, initialWeeks) {
|
|
var weekCornersStyled = {};
|
|
/* need to handle setting all of the corners on arbitrarily shaped blobs
|
|
__
|
|
__|A |
|
|
|B |C |__
|
|
|D |E |F |
|
|
|
|
in this case, A needs top left rounded, top right rounded
|
|
B needs top left rounded
|
|
C doesn't need any rounding
|
|
D needs bottom left rounded
|
|
E doesn't need any rounding
|
|
F needs top right rounding
|
|
*/
|
|
// cut off the animation transition weeks
|
|
var weeks = initialWeeks.slice(1, initialWeeks.length - 1);
|
|
// if there's an item above, lose both top corners. Item below, lose both bottom corners, etc.
|
|
weeks.forEach(function (week, weekIndex) {
|
|
week.forEach(function (day, dayIndex) {
|
|
var above = weeks[weekIndex - 1] &&
|
|
weeks[weekIndex - 1][dayIndex] &&
|
|
isInSameHoverRange(weeks[weekIndex - 1][dayIndex].originalDate, day.originalDate, weeks[weekIndex - 1][dayIndex].isSelected, day.isSelected);
|
|
var below = weeks[weekIndex + 1] &&
|
|
weeks[weekIndex + 1][dayIndex] &&
|
|
isInSameHoverRange(weeks[weekIndex + 1][dayIndex].originalDate, day.originalDate, weeks[weekIndex + 1][dayIndex].isSelected, day.isSelected);
|
|
var left = weeks[weekIndex][dayIndex - 1] &&
|
|
isInSameHoverRange(weeks[weekIndex][dayIndex - 1].originalDate, day.originalDate, weeks[weekIndex][dayIndex - 1].isSelected, day.isSelected);
|
|
var right = weeks[weekIndex][dayIndex + 1] &&
|
|
isInSameHoverRange(weeks[weekIndex][dayIndex + 1].originalDate, day.originalDate, weeks[weekIndex][dayIndex + 1].isSelected, day.isSelected);
|
|
var style = [];
|
|
style.push(calculateRoundedStyles(classNames, above, below, left, right));
|
|
style.push(calculateBorderStyles(classNames, above, below, left, right));
|
|
weekCornersStyled[weekIndex + '_' + dayIndex] = style.join(' ');
|
|
});
|
|
});
|
|
return weekCornersStyled;
|
|
};
|
|
var calculateRoundedStyles = function (classNames, above, below, left, right) {
|
|
var style = [];
|
|
var roundedTopLeft = !above && !left;
|
|
var roundedTopRight = !above && !right;
|
|
var roundedBottomLeft = !below && !left;
|
|
var roundedBottomRight = !below && !right;
|
|
if (roundedTopLeft) {
|
|
style.push(getRTL() ? classNames.topRightCornerDate : classNames.topLeftCornerDate);
|
|
}
|
|
if (roundedTopRight) {
|
|
style.push(getRTL() ? classNames.topLeftCornerDate : classNames.topRightCornerDate);
|
|
}
|
|
if (roundedBottomLeft) {
|
|
style.push(getRTL() ? classNames.bottomRightCornerDate : classNames.bottomLeftCornerDate);
|
|
}
|
|
if (roundedBottomRight) {
|
|
style.push(getRTL() ? classNames.bottomLeftCornerDate : classNames.bottomRightCornerDate);
|
|
}
|
|
return style.join(' ');
|
|
};
|
|
var calculateBorderStyles = function (classNames, above, below, left, right) {
|
|
var style = [];
|
|
if (!above) {
|
|
style.push(classNames.datesAbove);
|
|
}
|
|
if (!below) {
|
|
style.push(classNames.datesBelow);
|
|
}
|
|
if (!left) {
|
|
style.push(getRTL() ? classNames.datesRight : classNames.datesLeft);
|
|
}
|
|
if (!right) {
|
|
style.push(getRTL() ? classNames.datesLeft : classNames.datesRight);
|
|
}
|
|
return style.join(' ');
|
|
};
|
|
var isInSameHoverRange = function (date1, date2, date1Selected, date2Selected) {
|
|
var dateRangeType = props.dateRangeType, firstDayOfWeek = props.firstDayOfWeek, workWeekDays = props.workWeekDays;
|
|
// The hover state looks weird with non-contiguous days in work week view. In work week, show week hover state
|
|
var dateRangeHoverType = dateRangeType === DateRangeType.WorkWeek ? DateRangeType.Week : dateRangeType;
|
|
// we do not pass daysToSelectInDayView because we handle setting those styles dyanamically in onMouseOver
|
|
var dateRange = getDateRangeArray(date1, dateRangeHoverType, firstDayOfWeek, workWeekDays);
|
|
if (date1Selected !== date2Selected) {
|
|
// if one is selected and the other is not, they can't be in the same range
|
|
return false;
|
|
}
|
|
else if (date1Selected && date2Selected) {
|
|
// if they're both selected at the same time they must be in the same range
|
|
return true;
|
|
}
|
|
// otherwise, both must be unselected, so check the dateRange
|
|
return dateRange.filter(function (date) { return date.getTime() === date2.getTime(); }).length > 0;
|
|
};
|
|
return [getWeekCornerStyles, calculateRoundedStyles];
|
|
}
|
|
export var CalendarDayGridBase = function (props) {
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var navigatedDayRef = React.useRef(null);
|
|
var activeDescendantId = useId();
|
|
var onSelectDate = function (selectedDate) {
|
|
var _a, _b;
|
|
var firstDayOfWeek = props.firstDayOfWeek, minDate = props.minDate, maxDate = props.maxDate, workWeekDays = props.workWeekDays, daysToSelectInDayView = props.daysToSelectInDayView, restrictedDates = props.restrictedDates;
|
|
var restrictedDatesOptions = { minDate: minDate, maxDate: maxDate, restrictedDates: restrictedDates };
|
|
var dateRange = getDateRangeArray(selectedDate, dateRangeType, firstDayOfWeek, workWeekDays, daysToSelectInDayView);
|
|
dateRange = getBoundedDateRange(dateRange, minDate, maxDate);
|
|
dateRange = dateRange.filter(function (d) {
|
|
return !isRestrictedDate(d, restrictedDatesOptions);
|
|
});
|
|
(_a = props.onSelectDate) === null || _a === void 0 ? void 0 : _a.call(props, selectedDate, dateRange);
|
|
(_b = props.onNavigateDate) === null || _b === void 0 ? void 0 : _b.call(props, selectedDate, true);
|
|
};
|
|
var _a = useDayRefs(), daysRef = _a[0], getSetRefCallback = _a[1];
|
|
var weeks = useWeeks(props, onSelectDate, getSetRefCallback);
|
|
var animateBackwards = useAnimateBackwards(weeks);
|
|
var _b = useWeekCornerStyles(props), getWeekCornerStyles = _b[0], calculateRoundedStyles = _b[1];
|
|
React.useImperativeHandle(props.componentRef, function () { return ({
|
|
focus: function () {
|
|
var _a, _b;
|
|
(_b = (_a = navigatedDayRef.current) === null || _a === void 0 ? void 0 : _a.focus) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
},
|
|
}); }, []);
|
|
/**
|
|
*
|
|
* Section for setting hover/pressed styles. Because we want arbitrary blobs of days to be selectable, to support
|
|
* highlighting every day in the month for month view, css :hover style isn't enough, so we need mouse callbacks
|
|
* to set classnames on all relevant child refs to apply the styling
|
|
*
|
|
*/
|
|
var getDayInfosInRangeOfDay = function (dayToCompare) {
|
|
// The hover state looks weird with non-contiguous days in work week view. In work week, show week hover state
|
|
var dateRangeHoverType = getDateRangeTypeToUse(props.dateRangeType, props.workWeekDays);
|
|
// gets all the dates for the given date range type that are in the same date range as the given day
|
|
var dateRange = getDateRangeArray(dayToCompare.originalDate, dateRangeHoverType, props.firstDayOfWeek, props.workWeekDays, props.daysToSelectInDayView).map(function (date) { return date.getTime(); });
|
|
// gets all the day refs for the given dates
|
|
var dayInfosInRange = weeks.reduce(function (accumulatedValue, currentWeek) {
|
|
return accumulatedValue.concat(currentWeek.filter(function (weekDay) { return dateRange.indexOf(weekDay.originalDate.getTime()) !== -1; }));
|
|
}, []);
|
|
return dayInfosInRange;
|
|
};
|
|
var getRefsFromDayInfos = function (dayInfosInRange) {
|
|
var dayRefs = [];
|
|
dayRefs = dayInfosInRange.map(function (dayInfo) { return daysRef.current[dayInfo.key]; });
|
|
return dayRefs;
|
|
};
|
|
var styles = props.styles, theme = props.theme, className = props.className, dateRangeType = props.dateRangeType, showWeekNumbers = props.showWeekNumbers, labelledBy = props.labelledBy, lightenDaysOutsideNavigatedMonth = props.lightenDaysOutsideNavigatedMonth, animationDirection = props.animationDirection;
|
|
var classNames = getClassNames(styles, {
|
|
theme: theme,
|
|
className: className,
|
|
dateRangeType: dateRangeType,
|
|
showWeekNumbers: showWeekNumbers,
|
|
lightenDaysOutsideNavigatedMonth: lightenDaysOutsideNavigatedMonth === undefined ? true : lightenDaysOutsideNavigatedMonth,
|
|
animationDirection: animationDirection,
|
|
animateBackwards: animateBackwards,
|
|
});
|
|
// When the month is highlighted get the corner dates so that styles can be added to them
|
|
var weekCorners = getWeekCornerStyles(classNames, weeks);
|
|
var partialWeekProps = {
|
|
weeks: weeks,
|
|
navigatedDayRef: navigatedDayRef,
|
|
calculateRoundedStyles: calculateRoundedStyles,
|
|
activeDescendantId: activeDescendantId,
|
|
classNames: classNames,
|
|
weekCorners: weekCorners,
|
|
getDayInfosInRangeOfDay: getDayInfosInRangeOfDay,
|
|
getRefsFromDayInfos: getRefsFromDayInfos,
|
|
};
|
|
return (React.createElement(FocusZone, { className: classNames.wrapper, preventDefaultWhenHandled: true },
|
|
React.createElement("table", { className: classNames.table, "aria-multiselectable": "false", "aria-labelledby": labelledBy, "aria-activedescendant": activeDescendantId, role: "grid" },
|
|
React.createElement("tbody", null,
|
|
React.createElement(CalendarMonthHeaderRow, __assign({}, props, { classNames: classNames, weeks: weeks })),
|
|
React.createElement(CalendarGridRow, __assign({}, props, partialWeekProps, { week: weeks[0], weekIndex: -1, rowClassName: classNames.firstTransitionWeek, ariaRole: "presentation", ariaHidden: true })),
|
|
weeks.slice(1, weeks.length - 1).map(function (week, weekIndex) { return (React.createElement(CalendarGridRow, __assign({}, props, partialWeekProps, { key: weekIndex, week: week, weekIndex: weekIndex, rowClassName: classNames.weekRow }))); }),
|
|
React.createElement(CalendarGridRow, __assign({}, props, partialWeekProps, { week: weeks[weeks.length - 1], weekIndex: -2, rowClassName: classNames.lastTransitionWeek, ariaRole: "presentation", ariaHidden: true }))))));
|
|
};
|
|
CalendarDayGridBase.displayName = 'CalendarDayGridBase';
|
|
/**
|
|
* When given work week, if the days are non-contiguous, the hover states look really weird. So for non-contiguous
|
|
* work weeks, we'll just show week view instead.
|
|
*/
|
|
function getDateRangeTypeToUse(dateRangeType, workWeekDays) {
|
|
if (workWeekDays && dateRangeType === DateRangeType.WorkWeek) {
|
|
var sortedWWDays = workWeekDays.slice().sort();
|
|
var isContiguous = true;
|
|
for (var i = 1; i < sortedWWDays.length; i++) {
|
|
if (sortedWWDays[i] !== sortedWWDays[i - 1] + 1) {
|
|
isContiguous = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!isContiguous || workWeekDays.length === 0) {
|
|
return DateRangeType.Week;
|
|
}
|
|
}
|
|
return dateRangeType;
|
|
}
|
|
//# sourceMappingURL=CalendarDayGrid.base.js.map
|