UNPKG

@mui/x-date-pickers

Version:

The community edition of the Date and Time Picker components (MUI X).

462 lines (460 loc) 16.5 kB
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; import _extends from "@babel/runtime/helpers/esm/extends"; const _excluded = ["parentProps", "day", "focusableDay", "selectedDays", "isDateDisabled", "currentMonthNumber", "isViewFocused"], _excluded2 = ["ownerState"]; import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import Typography from '@mui/material/Typography'; import useSlotProps from '@mui/utils/useSlotProps'; import { useRtl } from '@mui/system/RtlProvider'; import { styled, useThemeProps } from '@mui/material/styles'; import { unstable_composeClasses as composeClasses, unstable_useControlled as useControlled } from '@mui/utils'; import clsx from 'clsx'; import { PickersDay } from "../PickersDay/PickersDay.js"; import { usePickersTranslations } from "../hooks/usePickersTranslations.js"; import { useUtils, useNow } from "../internals/hooks/useUtils.js"; import { DAY_SIZE, DAY_MARGIN } from "../internals/constants/dimensions.js"; import { PickersSlideTransition } from "./PickersSlideTransition.js"; import { useIsDateDisabled } from "./useIsDateDisabled.js"; import { findClosestEnabledDate, getWeekdays } from "../internals/utils/date-utils.js"; import { getDayCalendarUtilityClass } from "./dayCalendarClasses.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const useUtilityClasses = ownerState => { const { classes } = ownerState; const slots = { root: ['root'], header: ['header'], weekDayLabel: ['weekDayLabel'], loadingContainer: ['loadingContainer'], slideTransition: ['slideTransition'], monthContainer: ['monthContainer'], weekContainer: ['weekContainer'], weekNumberLabel: ['weekNumberLabel'], weekNumber: ['weekNumber'] }; return composeClasses(slots, getDayCalendarUtilityClass, classes); }; const weeksContainerHeight = (DAY_SIZE + DAY_MARGIN * 2) * 6; const PickersCalendarDayRoot = styled('div', { name: 'MuiDayCalendar', slot: 'Root', overridesResolver: (_, styles) => styles.root })({}); const PickersCalendarDayHeader = styled('div', { name: 'MuiDayCalendar', slot: 'Header', overridesResolver: (_, styles) => styles.header })({ display: 'flex', justifyContent: 'center', alignItems: 'center' }); const PickersCalendarWeekDayLabel = styled(Typography, { name: 'MuiDayCalendar', slot: 'WeekDayLabel', overridesResolver: (_, styles) => styles.weekDayLabel })(({ theme }) => ({ width: 36, height: 40, margin: '0 2px', textAlign: 'center', display: 'flex', justifyContent: 'center', alignItems: 'center', color: (theme.vars || theme).palette.text.secondary })); const PickersCalendarWeekNumberLabel = styled(Typography, { name: 'MuiDayCalendar', slot: 'WeekNumberLabel', overridesResolver: (_, styles) => styles.weekNumberLabel })(({ theme }) => ({ width: 36, height: 40, margin: '0 2px', textAlign: 'center', display: 'flex', justifyContent: 'center', alignItems: 'center', color: theme.palette.text.disabled })); const PickersCalendarWeekNumber = styled(Typography, { name: 'MuiDayCalendar', slot: 'WeekNumber', overridesResolver: (_, styles) => styles.weekNumber })(({ theme }) => _extends({}, theme.typography.caption, { width: DAY_SIZE, height: DAY_SIZE, padding: 0, margin: `0 ${DAY_MARGIN}px`, color: theme.palette.text.disabled, fontSize: '0.75rem', alignItems: 'center', justifyContent: 'center', display: 'inline-flex' })); const PickersCalendarLoadingContainer = styled('div', { name: 'MuiDayCalendar', slot: 'LoadingContainer', overridesResolver: (_, styles) => styles.loadingContainer })({ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: weeksContainerHeight }); const PickersCalendarSlideTransition = styled(PickersSlideTransition, { name: 'MuiDayCalendar', slot: 'SlideTransition', overridesResolver: (_, styles) => styles.slideTransition })({ minHeight: weeksContainerHeight }); const PickersCalendarWeekContainer = styled('div', { name: 'MuiDayCalendar', slot: 'MonthContainer', overridesResolver: (_, styles) => styles.monthContainer })({ overflow: 'hidden' }); const PickersCalendarWeek = styled('div', { name: 'MuiDayCalendar', slot: 'WeekContainer', overridesResolver: (_, styles) => styles.weekContainer })({ margin: `${DAY_MARGIN}px 0`, display: 'flex', justifyContent: 'center' }); function WrappedDay(_ref) { let { parentProps, day, focusableDay, selectedDays, isDateDisabled, currentMonthNumber, isViewFocused } = _ref, other = _objectWithoutPropertiesLoose(_ref, _excluded); const { disabled, disableHighlightToday, isMonthSwitchingAnimating, showDaysOutsideCurrentMonth, slots, slotProps, timezone } = parentProps; const utils = useUtils(); const now = useNow(timezone); const isFocusableDay = focusableDay !== null && utils.isSameDay(day, focusableDay); const isSelected = selectedDays.some(selectedDay => utils.isSameDay(selectedDay, day)); const isToday = utils.isSameDay(day, now); const Day = slots?.day ?? PickersDay; // We don't want to pass to ownerState down, to avoid re-rendering all the day whenever a prop changes. const _useSlotProps = useSlotProps({ elementType: Day, externalSlotProps: slotProps?.day, additionalProps: _extends({ disableHighlightToday, showDaysOutsideCurrentMonth, role: 'gridcell', isAnimating: isMonthSwitchingAnimating, // it is used in date range dragging logic by accessing `dataset.timestamp` 'data-timestamp': utils.toJsDate(day).valueOf() }, other), ownerState: _extends({}, parentProps, { day, selected: isSelected }) }), dayProps = _objectWithoutPropertiesLoose(_useSlotProps, _excluded2); const isDisabled = React.useMemo(() => disabled || isDateDisabled(day), [disabled, isDateDisabled, day]); const outsideCurrentMonth = React.useMemo(() => utils.getMonth(day) !== currentMonthNumber, [utils, day, currentMonthNumber]); const isFirstVisibleCell = React.useMemo(() => { const startOfMonth = utils.startOfMonth(utils.setMonth(day, currentMonthNumber)); if (!showDaysOutsideCurrentMonth) { return utils.isSameDay(day, startOfMonth); } return utils.isSameDay(day, utils.startOfWeek(startOfMonth)); }, [currentMonthNumber, day, showDaysOutsideCurrentMonth, utils]); const isLastVisibleCell = React.useMemo(() => { const endOfMonth = utils.endOfMonth(utils.setMonth(day, currentMonthNumber)); if (!showDaysOutsideCurrentMonth) { return utils.isSameDay(day, endOfMonth); } return utils.isSameDay(day, utils.endOfWeek(endOfMonth)); }, [currentMonthNumber, day, showDaysOutsideCurrentMonth, utils]); return /*#__PURE__*/_jsx(Day, _extends({}, dayProps, { day: day, disabled: isDisabled, autoFocus: isViewFocused && isFocusableDay, today: isToday, outsideCurrentMonth: outsideCurrentMonth, isFirstVisibleCell: isFirstVisibleCell, isLastVisibleCell: isLastVisibleCell, selected: isSelected, tabIndex: isFocusableDay ? 0 : -1, "aria-selected": isSelected, "aria-current": isToday ? 'date' : undefined })); } /** * @ignore - do not document. */ export function DayCalendar(inProps) { const props = useThemeProps({ props: inProps, name: 'MuiDayCalendar' }); const utils = useUtils(); const { onFocusedDayChange, className, currentMonth, selectedDays, focusedDay, loading, onSelectedDaysChange, onMonthSwitchingAnimationEnd, readOnly, reduceAnimations, renderLoading = () => /*#__PURE__*/_jsx("span", { children: "..." }), slideDirection, TransitionProps, disablePast, disableFuture, minDate, maxDate, shouldDisableDate, shouldDisableMonth, shouldDisableYear, dayOfWeekFormatter = date => utils.format(date, 'weekdayShort').charAt(0).toUpperCase(), hasFocus, onFocusedViewChange, gridLabelId, displayWeekNumber, fixedWeekNumber, autoFocus, timezone } = props; const now = useNow(timezone); const classes = useUtilityClasses(props); const isRtl = useRtl(); const isDateDisabled = useIsDateDisabled({ shouldDisableDate, shouldDisableMonth, shouldDisableYear, minDate, maxDate, disablePast, disableFuture, timezone }); const translations = usePickersTranslations(); const [internalHasFocus, setInternalHasFocus] = useControlled({ name: 'DayCalendar', state: 'hasFocus', controlled: hasFocus, default: autoFocus ?? false }); const [internalFocusedDay, setInternalFocusedDay] = React.useState(() => focusedDay || now); const handleDaySelect = useEventCallback(day => { if (readOnly) { return; } onSelectedDaysChange(day); }); const focusDay = day => { if (!isDateDisabled(day)) { onFocusedDayChange(day); setInternalFocusedDay(day); onFocusedViewChange?.(true); setInternalHasFocus(true); } }; const handleKeyDown = useEventCallback((event, day) => { switch (event.key) { case 'ArrowUp': focusDay(utils.addDays(day, -7)); event.preventDefault(); break; case 'ArrowDown': focusDay(utils.addDays(day, 7)); event.preventDefault(); break; case 'ArrowLeft': { const newFocusedDayDefault = utils.addDays(day, isRtl ? 1 : -1); const nextAvailableMonth = utils.addMonths(day, isRtl ? 1 : -1); const closestDayToFocus = findClosestEnabledDate({ utils, date: newFocusedDayDefault, minDate: isRtl ? newFocusedDayDefault : utils.startOfMonth(nextAvailableMonth), maxDate: isRtl ? utils.endOfMonth(nextAvailableMonth) : newFocusedDayDefault, isDateDisabled, timezone }); focusDay(closestDayToFocus || newFocusedDayDefault); event.preventDefault(); break; } case 'ArrowRight': { const newFocusedDayDefault = utils.addDays(day, isRtl ? -1 : 1); const nextAvailableMonth = utils.addMonths(day, isRtl ? -1 : 1); const closestDayToFocus = findClosestEnabledDate({ utils, date: newFocusedDayDefault, minDate: isRtl ? utils.startOfMonth(nextAvailableMonth) : newFocusedDayDefault, maxDate: isRtl ? newFocusedDayDefault : utils.endOfMonth(nextAvailableMonth), isDateDisabled, timezone }); focusDay(closestDayToFocus || newFocusedDayDefault); event.preventDefault(); break; } case 'Home': focusDay(utils.startOfWeek(day)); event.preventDefault(); break; case 'End': focusDay(utils.endOfWeek(day)); event.preventDefault(); break; case 'PageUp': focusDay(utils.addMonths(day, 1)); event.preventDefault(); break; case 'PageDown': focusDay(utils.addMonths(day, -1)); event.preventDefault(); break; default: break; } }); const handleFocus = useEventCallback((event, day) => focusDay(day)); const handleBlur = useEventCallback((event, day) => { if (internalHasFocus && utils.isSameDay(internalFocusedDay, day)) { onFocusedViewChange?.(false); } }); const currentMonthNumber = utils.getMonth(currentMonth); const currentYearNumber = utils.getYear(currentMonth); const validSelectedDays = React.useMemo(() => selectedDays.filter(day => !!day).map(day => utils.startOfDay(day)), [utils, selectedDays]); // need a new ref whenever the `key` of the transition changes: https://reactcommunity.org/react-transition-group/transition/#Transition-prop-nodeRef. const transitionKey = `${currentYearNumber}-${currentMonthNumber}`; // eslint-disable-next-line react-hooks/exhaustive-deps const slideNodeRef = React.useMemo(() => /*#__PURE__*/React.createRef(), [transitionKey]); const focusableDay = React.useMemo(() => { const startOfMonth = utils.startOfMonth(currentMonth); const endOfMonth = utils.endOfMonth(currentMonth); if (isDateDisabled(internalFocusedDay) || utils.isAfterDay(internalFocusedDay, endOfMonth) || utils.isBeforeDay(internalFocusedDay, startOfMonth)) { return findClosestEnabledDate({ utils, date: internalFocusedDay, minDate: startOfMonth, maxDate: endOfMonth, disablePast, disableFuture, isDateDisabled, timezone }); } return internalFocusedDay; }, [currentMonth, disableFuture, disablePast, internalFocusedDay, isDateDisabled, utils, timezone]); const weeksToDisplay = React.useMemo(() => { const toDisplay = utils.getWeekArray(currentMonth); let nextMonth = utils.addMonths(currentMonth, 1); while (fixedWeekNumber && toDisplay.length < fixedWeekNumber) { const additionalWeeks = utils.getWeekArray(nextMonth); const hasCommonWeek = utils.isSameDay(toDisplay[toDisplay.length - 1][0], additionalWeeks[0][0]); additionalWeeks.slice(hasCommonWeek ? 1 : 0).forEach(week => { if (toDisplay.length < fixedWeekNumber) { toDisplay.push(week); } }); nextMonth = utils.addMonths(nextMonth, 1); } return toDisplay; }, [currentMonth, fixedWeekNumber, utils]); return /*#__PURE__*/_jsxs(PickersCalendarDayRoot, { role: "grid", "aria-labelledby": gridLabelId, className: classes.root, children: [/*#__PURE__*/_jsxs(PickersCalendarDayHeader, { role: "row", className: classes.header, children: [displayWeekNumber && /*#__PURE__*/_jsx(PickersCalendarWeekNumberLabel, { variant: "caption", role: "columnheader", "aria-label": translations.calendarWeekNumberHeaderLabel, className: classes.weekNumberLabel, children: translations.calendarWeekNumberHeaderText }), getWeekdays(utils, now).map((weekday, i) => /*#__PURE__*/_jsx(PickersCalendarWeekDayLabel, { variant: "caption", role: "columnheader", "aria-label": utils.format(weekday, 'weekday'), className: classes.weekDayLabel, children: dayOfWeekFormatter(weekday) }, i.toString()))] }), loading ? /*#__PURE__*/_jsx(PickersCalendarLoadingContainer, { className: classes.loadingContainer, children: renderLoading() }) : /*#__PURE__*/_jsx(PickersCalendarSlideTransition, _extends({ transKey: transitionKey, onExited: onMonthSwitchingAnimationEnd, reduceAnimations: reduceAnimations, slideDirection: slideDirection, className: clsx(className, classes.slideTransition) }, TransitionProps, { nodeRef: slideNodeRef, children: /*#__PURE__*/_jsx(PickersCalendarWeekContainer, { ref: slideNodeRef, role: "rowgroup", className: classes.monthContainer, children: weeksToDisplay.map((week, index) => /*#__PURE__*/_jsxs(PickersCalendarWeek, { role: "row", className: classes.weekContainer // fix issue of announcing row 1 as row 2 // caused by week day labels row , "aria-rowindex": index + 1, children: [displayWeekNumber && /*#__PURE__*/_jsx(PickersCalendarWeekNumber, { className: classes.weekNumber, role: "rowheader", "aria-label": translations.calendarWeekNumberAriaLabelText(utils.getWeekNumber(week[0])), children: translations.calendarWeekNumberText(utils.getWeekNumber(week[0])) }), week.map((day, dayIndex) => /*#__PURE__*/_jsx(WrappedDay, { parentProps: props, day: day, selectedDays: validSelectedDays, focusableDay: focusableDay, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, onDaySelect: handleDaySelect, isDateDisabled: isDateDisabled, currentMonthNumber: currentMonthNumber, isViewFocused: internalHasFocus // fix issue of announcing column 1 as column 2 when `displayWeekNumber` is enabled , "aria-colindex": dayIndex + 1 }, day.toString()))] }, `week-${week[0]}`)) }) }))] }); }