@mui/x-date-pickers
Version:
The community edition of the Date and Time Picker components (MUI X).
357 lines (356 loc) • 10.6 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import clsx from 'clsx';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import { styled, useThemeProps } from '@mui/material/styles';
import { unstable_useEnhancedEffect as useEnhancedEffect, unstable_composeClasses as composeClasses } from '@mui/utils';
import { ClockPointer } from "./ClockPointer.js";
import { usePickersTranslations } from "../hooks/usePickersTranslations.js";
import { useUtils } from "../internals/hooks/useUtils.js";
import { CLOCK_HOUR_WIDTH, getHours, getMinutes } from "./shared.js";
import { getClockUtilityClass } from "./clockClasses.js";
import { formatMeridiem } from "../internals/utils/date-utils.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
classes,
meridiemMode
} = ownerState;
const slots = {
root: ['root'],
clock: ['clock'],
wrapper: ['wrapper'],
squareMask: ['squareMask'],
pin: ['pin'],
amButton: ['amButton', meridiemMode === 'am' && 'selected'],
pmButton: ['pmButton', meridiemMode === 'pm' && 'selected'],
meridiemText: ['meridiemText']
};
return composeClasses(slots, getClockUtilityClass, classes);
};
const ClockRoot = styled('div', {
name: 'MuiClock',
slot: 'Root',
overridesResolver: (_, styles) => styles.root
})(({
theme
}) => ({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin: theme.spacing(2)
}));
const ClockClock = styled('div', {
name: 'MuiClock',
slot: 'Clock',
overridesResolver: (_, styles) => styles.clock
})({
backgroundColor: 'rgba(0,0,0,.07)',
borderRadius: '50%',
height: 220,
width: 220,
flexShrink: 0,
position: 'relative',
pointerEvents: 'none'
});
const ClockWrapper = styled('div', {
name: 'MuiClock',
slot: 'Wrapper',
overridesResolver: (_, styles) => styles.wrapper
})({
'&:focus': {
outline: 'none'
}
});
const ClockSquareMask = styled('div', {
name: 'MuiClock',
slot: 'SquareMask',
overridesResolver: (_, styles) => styles.squareMask
})({
width: '100%',
height: '100%',
position: 'absolute',
pointerEvents: 'auto',
outline: 0,
// Disable scroll capabilities.
touchAction: 'none',
userSelect: 'none',
variants: [{
props: {
disabled: false
},
style: {
'@media (pointer: fine)': {
cursor: 'pointer',
borderRadius: '50%'
},
'&:active': {
cursor: 'move'
}
}
}]
});
const ClockPin = styled('div', {
name: 'MuiClock',
slot: 'Pin',
overridesResolver: (_, styles) => styles.pin
})(({
theme
}) => ({
width: 6,
height: 6,
borderRadius: '50%',
backgroundColor: (theme.vars || theme).palette.primary.main,
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}));
const meridiemButtonCommonStyles = (theme, meridiemMode) => ({
zIndex: 1,
bottom: 8,
paddingLeft: 4,
paddingRight: 4,
width: CLOCK_HOUR_WIDTH,
variants: [{
props: {
meridiemMode
},
style: {
backgroundColor: (theme.vars || theme).palette.primary.main,
color: (theme.vars || theme).palette.primary.contrastText,
'&:hover': {
backgroundColor: (theme.vars || theme).palette.primary.light
}
}
}]
});
const ClockAmButton = styled(IconButton, {
name: 'MuiClock',
slot: 'AmButton',
overridesResolver: (_, styles) => styles.amButton
})(({
theme
}) => _extends({}, meridiemButtonCommonStyles(theme, 'am'), {
// keeping it here to make TS happy
position: 'absolute',
left: 8
}));
const ClockPmButton = styled(IconButton, {
name: 'MuiClock',
slot: 'PmButton',
overridesResolver: (_, styles) => styles.pmButton
})(({
theme
}) => _extends({}, meridiemButtonCommonStyles(theme, 'pm'), {
// keeping it here to make TS happy
position: 'absolute',
right: 8
}));
const ClockMeridiemText = styled(Typography, {
name: 'MuiClock',
slot: 'meridiemText',
overridesResolver: (_, styles) => styles.meridiemText
})({
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
});
/**
* @ignore - internal component.
*/
export function Clock(inProps) {
const props = useThemeProps({
props: inProps,
name: 'MuiClock'
});
const {
ampm,
ampmInClock,
autoFocus,
children,
value,
handleMeridiemChange,
isTimeDisabled,
meridiemMode,
minutesStep = 1,
onChange,
selectedId,
type,
viewValue,
viewRange: [minViewValue, maxViewValue],
disabled = false,
readOnly,
className
} = props;
const ownerState = props;
const utils = useUtils();
const translations = usePickersTranslations();
const isMoving = React.useRef(false);
const classes = useUtilityClasses(ownerState);
const isSelectedTimeDisabled = isTimeDisabled(viewValue, type);
const isPointerInner = !ampm && type === 'hours' && (viewValue < 1 || viewValue > 12);
const handleValueChange = (newValue, isFinish) => {
if (disabled || readOnly) {
return;
}
if (isTimeDisabled(newValue, type)) {
return;
}
onChange(newValue, isFinish);
};
const setTime = (event, isFinish) => {
let {
offsetX,
offsetY
} = event;
if (offsetX === undefined) {
const rect = event.target.getBoundingClientRect();
offsetX = event.changedTouches[0].clientX - rect.left;
offsetY = event.changedTouches[0].clientY - rect.top;
}
const newSelectedValue = type === 'seconds' || type === 'minutes' ? getMinutes(offsetX, offsetY, minutesStep) : getHours(offsetX, offsetY, Boolean(ampm));
handleValueChange(newSelectedValue, isFinish);
};
const handleTouchSelection = event => {
isMoving.current = true;
setTime(event, 'shallow');
};
const handleTouchEnd = event => {
if (isMoving.current) {
setTime(event, 'finish');
isMoving.current = false;
}
event.preventDefault();
};
const handleMouseMove = event => {
// event.buttons & PRIMARY_MOUSE_BUTTON
if (event.buttons > 0) {
setTime(event.nativeEvent, 'shallow');
}
};
const handleMouseUp = event => {
if (isMoving.current) {
isMoving.current = false;
}
setTime(event.nativeEvent, 'finish');
};
const hasSelected = React.useMemo(() => {
if (type === 'hours') {
return true;
}
return viewValue % 5 === 0;
}, [type, viewValue]);
const keyboardControlStep = type === 'minutes' ? minutesStep : 1;
const listboxRef = React.useRef(null);
// Since this is rendered when a Popper is opened we can't use passive effects.
// Focusing in passive effects in Popper causes scroll jump.
useEnhancedEffect(() => {
if (autoFocus) {
// The ref not being resolved would be a bug in MUI.
listboxRef.current.focus();
}
}, [autoFocus]);
const clampValue = newValue => Math.max(minViewValue, Math.min(maxViewValue, newValue));
const circleValue = newValue => (newValue + (maxViewValue + 1)) % (maxViewValue + 1);
const handleKeyDown = event => {
// TODO: Why this early exit?
if (isMoving.current) {
return;
}
switch (event.key) {
case 'Home':
// reset both hours and minutes
handleValueChange(minViewValue, 'partial');
event.preventDefault();
break;
case 'End':
handleValueChange(maxViewValue, 'partial');
event.preventDefault();
break;
case 'ArrowUp':
handleValueChange(circleValue(viewValue + keyboardControlStep), 'partial');
event.preventDefault();
break;
case 'ArrowDown':
handleValueChange(circleValue(viewValue - keyboardControlStep), 'partial');
event.preventDefault();
break;
case 'PageUp':
handleValueChange(clampValue(viewValue + 5), 'partial');
event.preventDefault();
break;
case 'PageDown':
handleValueChange(clampValue(viewValue - 5), 'partial');
event.preventDefault();
break;
case 'Enter':
case ' ':
handleValueChange(viewValue, 'finish');
event.preventDefault();
break;
default:
// do nothing
}
};
return /*#__PURE__*/_jsxs(ClockRoot, {
className: clsx(classes.root, className),
children: [/*#__PURE__*/_jsxs(ClockClock, {
className: classes.clock,
children: [/*#__PURE__*/_jsx(ClockSquareMask, {
onTouchMove: handleTouchSelection,
onTouchStart: handleTouchSelection,
onTouchEnd: handleTouchEnd,
onMouseUp: handleMouseUp,
onMouseMove: handleMouseMove,
ownerState: {
disabled
},
className: classes.squareMask
}), !isSelectedTimeDisabled && /*#__PURE__*/_jsxs(React.Fragment, {
children: [/*#__PURE__*/_jsx(ClockPin, {
className: classes.pin
}), value != null && /*#__PURE__*/_jsx(ClockPointer, {
type: type,
viewValue: viewValue,
isInner: isPointerInner,
hasSelected: hasSelected
})]
}), /*#__PURE__*/_jsx(ClockWrapper, {
"aria-activedescendant": selectedId,
"aria-label": translations.clockLabelText(type, value, utils, value == null ? null : utils.format(value, 'fullTime')),
ref: listboxRef,
role: "listbox",
onKeyDown: handleKeyDown,
tabIndex: 0,
className: classes.wrapper,
children: children
})]
}), ampm && ampmInClock && /*#__PURE__*/_jsxs(React.Fragment, {
children: [/*#__PURE__*/_jsx(ClockAmButton, {
onClick: readOnly ? undefined : () => handleMeridiemChange('am'),
disabled: disabled || meridiemMode === null,
ownerState: ownerState,
className: classes.amButton,
title: formatMeridiem(utils, 'am'),
children: /*#__PURE__*/_jsx(ClockMeridiemText, {
variant: "caption",
className: classes.meridiemText,
children: formatMeridiem(utils, 'am')
})
}), /*#__PURE__*/_jsx(ClockPmButton, {
disabled: disabled || meridiemMode === null,
onClick: readOnly ? undefined : () => handleMeridiemChange('pm'),
ownerState: ownerState,
className: classes.pmButton,
title: formatMeridiem(utils, 'pm'),
children: /*#__PURE__*/_jsx(ClockMeridiemText, {
variant: "caption",
className: classes.meridiemText,
children: formatMeridiem(utils, 'pm')
})
})]
})]
});
}