UNPKG

@mui/x-date-pickers

Version:

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

401 lines (395 loc) 14.1 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.Clock = Clock; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _clsx = _interopRequireDefault(require("clsx")); var _IconButton = _interopRequireDefault(require("@mui/material/IconButton")); var _Typography = _interopRequireDefault(require("@mui/material/Typography")); var _styles = require("@mui/material/styles"); var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect")); var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument")); var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses")); var _ClockPointer = require("./ClockPointer"); var _hooks = require("../hooks"); var _shared = require("./shared"); var _clockClasses = require("./clockClasses"); var _dateUtils = require("../internals/utils/date-utils"); var _usePickerPrivateContext = require("../internals/hooks/usePickerPrivateContext"); var _jsxRuntime = require("react/jsx-runtime"); const useUtilityClasses = (classes, ownerState) => { const slots = { root: ['root'], clock: ['clock'], wrapper: ['wrapper'], squareMask: ['squareMask'], pin: ['pin'], amButton: ['amButton', ownerState.clockMeridiemMode === 'am' && 'selected'], pmButton: ['pmButton', ownerState.clockMeridiemMode === 'pm' && 'selected'], meridiemText: ['meridiemText'] }; return (0, _composeClasses.default)(slots, _clockClasses.getClockUtilityClass, classes); }; const ClockRoot = (0, _styles.styled)('div', { name: 'MuiClock', slot: 'Root' })(({ theme }) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', margin: theme.spacing(2) })); const ClockClock = (0, _styles.styled)('div', { name: 'MuiClock', slot: 'Clock' })({ backgroundColor: 'rgba(0,0,0,.07)', borderRadius: '50%', height: 220, width: 220, flexShrink: 0, position: 'relative', pointerEvents: 'none' }); const ClockWrapper = (0, _styles.styled)('div', { name: 'MuiClock', slot: 'Wrapper' })({ '&:focus': { outline: 'none' } }); const ClockSquareMask = (0, _styles.styled)('div', { name: 'MuiClock', slot: 'SquareMask' })({ width: '100%', height: '100%', position: 'absolute', pointerEvents: 'auto', outline: 0, // Disable scroll capabilities. touchAction: 'none', userSelect: 'none', variants: [{ props: { isClockDisabled: false }, style: { '@media (pointer: fine)': { cursor: 'pointer', borderRadius: '50%' }, '&:active': { cursor: 'move' } } }] }); const ClockPin = (0, _styles.styled)('div', { name: 'MuiClock', slot: '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, clockMeridiemMode) => ({ zIndex: 1, bottom: 8, paddingLeft: 4, paddingRight: 4, width: _shared.CLOCK_HOUR_WIDTH, variants: [{ props: { clockMeridiemMode }, 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 = (0, _styles.styled)(_IconButton.default, { name: 'MuiClock', slot: 'AmButton' })(({ theme }) => (0, _extends2.default)({}, meridiemButtonCommonStyles(theme, 'am'), { // keeping it here to make TS happy position: 'absolute', left: 8 })); const ClockPmButton = (0, _styles.styled)(_IconButton.default, { name: 'MuiClock', slot: 'PmButton' })(({ theme }) => (0, _extends2.default)({}, meridiemButtonCommonStyles(theme, 'pm'), { // keeping it here to make TS happy position: 'absolute', right: 8 })); const ClockMeridiemText = (0, _styles.styled)(_Typography.default, { name: 'MuiClock', slot: 'MeridiemText' })({ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }); /** * @ignore - internal component. */ function Clock(inProps) { const props = (0, _styles.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, classes: classesProp } = props; const adapter = (0, _hooks.usePickerAdapter)(); const translations = (0, _hooks.usePickerTranslations)(); const { ownerState: pickerOwnerState } = (0, _usePickerPrivateContext.usePickerPrivateContext)(); const ownerState = (0, _extends2.default)({}, pickerOwnerState, { isClockDisabled: disabled, clockMeridiemMode: meridiemMode }); const isMoving = React.useRef(false); const activePointerIdRef = React.useRef(null); const squareMaskRef = React.useRef(null); const removeDragListenersRef = React.useRef(undefined); const classes = useUtilityClasses(classesProp, 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) => { const mask = squareMaskRef.current; // The document-level listeners can briefly outlive the mask during unmount: // React detaches refs before the cleanup effect removes the listeners, so a // pointer event firing in that window would otherwise dereference a null ref. if (!mask) { return; } // Resolve the pointer coordinates relative to the clock mask rather than from // `offsetX`/`offsetY`: the `pointermove`/`pointerup` listeners live on the // document (see `handlePointerDown`), so the event target can be any element // once the pointer leaves the clock. const rect = mask.getBoundingClientRect(); const offsetX = event.clientX - rect.left; const offsetY = event.clientY - rect.top; const newSelectedValue = type === 'seconds' || type === 'minutes' ? (0, _shared.getMinutes)(offsetX, offsetY, minutesStep) : (0, _shared.getHours)(offsetX, offsetY, Boolean(ampm)); handleValueChange(newSelectedValue, isFinish); }; // Pointer events are the single source of truth for mouse, touch and pen. The // move/up/cancel listeners are attached to the document so a drag keeps // tracking and commits even when the pointer is released outside the clock. const stopTracking = () => { isMoving.current = false; activePointerIdRef.current = null; // Idempotent: the cleanup clears its own ref, so calling `stopTracking` twice // (e.g. a `pointercancel` racing a `pointerup`) is a safe no-op. removeDragListenersRef.current?.(); }; const handleDocumentPointerMove = (0, _useEventCallback.default)(event => { if (event.pointerId !== activePointerIdRef.current) { return; } event.preventDefault(); setTime(event, 'shallow'); }); const handleDocumentPointerUp = (0, _useEventCallback.default)(event => { if (event.pointerId !== activePointerIdRef.current) { return; } stopTracking(); setTime(event, 'finish'); }); const handleDocumentPointerCancel = (0, _useEventCallback.default)(event => { if (event.pointerId !== activePointerIdRef.current) { return; } // The gesture was interrupted by the user agent: drop it without committing. stopTracking(); }); const handlePointerDown = event => { // Ignore secondary buttons (middle = 1, right = 2), secondary multi-touch // pointers and interactions that can't change the value. `> 0` rather than // `!== 0` keeps the gesture permissive when `event.button` is left unset by a // synthetic event (some test environments), matching `useDragRange`. if (event.button > 0 || event.isPrimary === false || disabled || readOnly) { return; } // A fresh primary pointerdown ends any previous gesture whose pointerup was // lost, fully resetting its state before starting the new one. stopTracking(); isMoving.current = true; activePointerIdRef.current = event.pointerId; const doc = (0, _ownerDocument.default)(squareMaskRef.current); doc.addEventListener('pointermove', handleDocumentPointerMove); doc.addEventListener('pointerup', handleDocumentPointerUp); doc.addEventListener('pointercancel', handleDocumentPointerCancel); removeDragListenersRef.current = () => { doc.removeEventListener('pointermove', handleDocumentPointerMove); doc.removeEventListener('pointerup', handleDocumentPointerUp); doc.removeEventListener('pointercancel', handleDocumentPointerCancel); removeDragListenersRef.current = undefined; }; setTime(event.nativeEvent, 'shallow'); }; const isPointerBetweenTwoClockValues = type === 'hours' ? false : viewValue % 5 !== 0; 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. (0, _useEnhancedEffect.default)(() => { if (autoFocus) { // The ref not being resolved would be a bug in MUI. listboxRef.current.focus(); } }, [autoFocus]); // Clean up the document-level pointer listeners if the clock unmounts mid-drag. React.useEffect(() => () => removeDragListenersRef.current?.(), []); 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__*/(0, _jsxRuntime.jsxs)(ClockRoot, { className: (0, _clsx.default)(classes.root, className), children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(ClockClock, { className: classes.clock, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ClockSquareMask, { ref: squareMaskRef, onPointerDown: handlePointerDown, ownerState: ownerState, className: classes.squareMask }), !isSelectedTimeDisabled && /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ClockPin, { className: classes.pin }), value != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ClockPointer.ClockPointer, { type: type, viewValue: viewValue, isInner: isPointerInner, isBetweenTwoClockValues: isPointerBetweenTwoClockValues })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(ClockWrapper, { "aria-activedescendant": selectedId, "aria-label": translations.clockLabelText(type, value == null ? null : adapter.format(value, ampm ? 'fullTime12h' : 'fullTime24h')), ref: listboxRef, role: "listbox", onKeyDown: handleKeyDown, tabIndex: 0, className: classes.wrapper, children: children })] }), ampm && ampmInClock && /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ClockAmButton, { onClick: readOnly ? undefined : () => handleMeridiemChange('am'), disabled: disabled || meridiemMode === null, ownerState: ownerState, className: classes.amButton, title: (0, _dateUtils.formatMeridiem)(adapter, 'am'), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ClockMeridiemText, { variant: "caption", className: classes.meridiemText, children: (0, _dateUtils.formatMeridiem)(adapter, 'am') }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(ClockPmButton, { disabled: disabled || meridiemMode === null, onClick: readOnly ? undefined : () => handleMeridiemChange('pm'), ownerState: ownerState, className: classes.pmButton, title: (0, _dateUtils.formatMeridiem)(adapter, 'pm'), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ClockMeridiemText, { variant: "caption", className: classes.meridiemText, children: (0, _dateUtils.formatMeridiem)(adapter, 'pm') }) })] })] }); }