UNPKG

@mui/x-date-pickers

Version:

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

370 lines (363 loc) 12.9 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.usePickerValue = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); var _useOpenState = require("../useOpenState"); var _useUtils = require("../useUtils"); var _validation = require("../../../validation"); var _useValueWithTimezone = require("../useValueWithTimezone"); /** * Decide if the new value should be published * The published value will be passed to `onChange` if defined. */ const shouldPublishValue = params => { const { action, hasChanged, dateState, isControlled } = params; const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount; // The field is responsible for only calling `onChange` when needed. if (action.name === 'setValueFromField') { return true; } if (action.name === 'setValueFromAction') { // If the component is not controlled, and the value has not been modified since the mount, // Then we want to publish the default value whenever the user pressed the "Accept", "Today" or "Clear" button. if (isCurrentValueTheDefaultValue && ['accept', 'today', 'clear'].includes(action.pickerAction)) { return true; } return hasChanged(dateState.lastPublishedValue); } if (action.name === 'setValueFromView' && action.selectionState !== 'shallow') { // On the first view, // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onChange` if (isCurrentValueTheDefaultValue) { return true; } return hasChanged(dateState.lastPublishedValue); } if (action.name === 'setValueFromShortcut') { // On the first view, // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onChange` if (isCurrentValueTheDefaultValue) { return true; } return hasChanged(dateState.lastPublishedValue); } return false; }; /** * Decide if the new value should be committed. * The committed value will be passed to `onAccept` if defined. * It will also be used as a reset target when calling the `cancel` picker action (when clicking on the "Cancel" button). */ const shouldCommitValue = params => { const { action, hasChanged, dateState, isControlled, closeOnSelect } = params; const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount; if (action.name === 'setValueFromAction') { // If the component is not controlled, and the value has not been modified since the mount, // Then we want to commit the default value whenever the user pressed the "Accept", "Today" or "Clear" button. if (isCurrentValueTheDefaultValue && ['accept', 'today', 'clear'].includes(action.pickerAction)) { return true; } return hasChanged(dateState.lastCommittedValue); } if (action.name === 'setValueFromView' && action.selectionState === 'finish' && closeOnSelect) { // On picker where the 1st view is also the last view, // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onAccept` if (isCurrentValueTheDefaultValue) { return true; } return hasChanged(dateState.lastCommittedValue); } if (action.name === 'setValueFromShortcut') { return action.changeImportance === 'accept' && hasChanged(dateState.lastCommittedValue); } return false; }; /** * Decide if the picker should be closed after the value is updated. */ const shouldClosePicker = params => { const { action, closeOnSelect } = params; if (action.name === 'setValueFromAction') { return true; } if (action.name === 'setValueFromView') { return action.selectionState === 'finish' && closeOnSelect; } if (action.name === 'setValueFromShortcut') { return action.changeImportance === 'accept'; } return false; }; /** * Manage the value lifecycle of all the pickers. */ const usePickerValue = ({ props, valueManager, valueType, wrapperVariant, validator }) => { const { onAccept, onChange, value: inValueWithoutRenderTimezone, defaultValue: inDefaultValue, closeOnSelect = wrapperVariant === 'desktop', timezone: timezoneProp, referenceDate } = props; const { current: defaultValue } = React.useRef(inDefaultValue); const { current: isControlled } = React.useRef(inValueWithoutRenderTimezone !== undefined); const [previousTimezoneProp, setPreviousTimezoneProp] = React.useState(timezoneProp); /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ if (process.env.NODE_ENV !== 'production') { React.useEffect(() => { if (isControlled !== (inValueWithoutRenderTimezone !== undefined)) { console.error([`MUI X: A component is changing the ${isControlled ? '' : 'un'}controlled value of a picker to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled value` + 'for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n')); } }, [inValueWithoutRenderTimezone]); React.useEffect(() => { if (!isControlled && defaultValue !== inDefaultValue) { console.error([`MUI X: A component is changing the defaultValue of an uncontrolled picker after being initialized. ` + `To suppress this warning opt to use a controlled value.`].join('\n')); } }, [JSON.stringify(defaultValue)]); } /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ const utils = (0, _useUtils.useUtils)(); const adapter = (0, _useUtils.useLocalizationContext)(); const { isOpen, setIsOpen } = (0, _useOpenState.useOpenState)(props); const { timezone, value: inValueWithTimezoneToRender, handleValueChange } = (0, _useValueWithTimezone.useValueWithTimezone)({ timezone: timezoneProp, value: inValueWithoutRenderTimezone, defaultValue, referenceDate, onChange, valueManager }); const [dateState, setDateState] = React.useState(() => { let initialValue; if (inValueWithTimezoneToRender !== undefined) { initialValue = inValueWithTimezoneToRender; } else if (defaultValue !== undefined) { initialValue = defaultValue; } else { initialValue = valueManager.emptyValue; } return { draft: initialValue, lastPublishedValue: initialValue, lastCommittedValue: initialValue, lastControlledValue: inValueWithoutRenderTimezone, hasBeenModifiedSinceMount: false }; }); const timezoneFromDraftValue = valueManager.getTimezone(utils, dateState.draft); if (previousTimezoneProp !== timezoneProp) { setPreviousTimezoneProp(timezoneProp); if (timezoneProp && timezoneFromDraftValue && timezoneProp !== timezoneFromDraftValue) { setDateState(prev => (0, _extends2.default)({}, prev, { draft: valueManager.setTimezone(utils, timezoneProp, prev.draft) })); } } const { getValidationErrorForNewValue } = (0, _validation.useValidation)({ props, validator, timezone, value: dateState.draft, onError: props.onError }); const updateDate = (0, _useEventCallback.default)(action => { const updaterParams = { action, dateState, hasChanged: comparison => !valueManager.areValuesEqual(utils, action.value, comparison), isControlled, closeOnSelect }; const shouldPublish = shouldPublishValue(updaterParams); const shouldCommit = shouldCommitValue(updaterParams); const shouldClose = shouldClosePicker(updaterParams); setDateState(prev => (0, _extends2.default)({}, prev, { draft: action.value, lastPublishedValue: shouldPublish ? action.value : prev.lastPublishedValue, lastCommittedValue: shouldCommit ? action.value : prev.lastCommittedValue, hasBeenModifiedSinceMount: true })); let cachedContext = null; const getContext = () => { if (!cachedContext) { const validationError = action.name === 'setValueFromField' ? action.context.validationError : getValidationErrorForNewValue(action.value); cachedContext = { validationError }; if (action.name === 'setValueFromShortcut') { cachedContext.shortcut = action.shortcut; } } return cachedContext; }; if (shouldPublish) { handleValueChange(action.value, getContext()); } if (shouldCommit && onAccept) { onAccept(action.value, getContext()); } if (shouldClose) { setIsOpen(false); } }); if (dateState.lastControlledValue !== inValueWithoutRenderTimezone) { const isUpdateComingFromPicker = valueManager.areValuesEqual(utils, dateState.draft, inValueWithTimezoneToRender); setDateState(prev => (0, _extends2.default)({}, prev, { lastControlledValue: inValueWithoutRenderTimezone }, isUpdateComingFromPicker ? {} : { lastCommittedValue: inValueWithTimezoneToRender, lastPublishedValue: inValueWithTimezoneToRender, draft: inValueWithTimezoneToRender, hasBeenModifiedSinceMount: true })); } const handleClear = (0, _useEventCallback.default)(() => { updateDate({ value: valueManager.emptyValue, name: 'setValueFromAction', pickerAction: 'clear' }); }); const handleAccept = (0, _useEventCallback.default)(() => { updateDate({ value: dateState.lastPublishedValue, name: 'setValueFromAction', pickerAction: 'accept' }); }); const handleDismiss = (0, _useEventCallback.default)(() => { updateDate({ value: dateState.lastPublishedValue, name: 'setValueFromAction', pickerAction: 'dismiss' }); }); const handleCancel = (0, _useEventCallback.default)(() => { updateDate({ value: dateState.lastCommittedValue, name: 'setValueFromAction', pickerAction: 'cancel' }); }); const handleSetToday = (0, _useEventCallback.default)(() => { updateDate({ value: valueManager.getTodayValue(utils, timezone, valueType), name: 'setValueFromAction', pickerAction: 'today' }); }); const handleOpen = (0, _useEventCallback.default)(event => { event.preventDefault(); setIsOpen(true); }); const handleClose = (0, _useEventCallback.default)(event => { event?.preventDefault(); setIsOpen(false); }); const handleChange = (0, _useEventCallback.default)((newValue, selectionState = 'partial') => updateDate({ name: 'setValueFromView', value: newValue, selectionState })); const handleSelectShortcut = (0, _useEventCallback.default)((newValue, changeImportance, shortcut) => updateDate({ name: 'setValueFromShortcut', value: newValue, changeImportance, shortcut })); const handleChangeFromField = (0, _useEventCallback.default)((newValue, context) => updateDate({ name: 'setValueFromField', value: newValue, context })); const actions = { onClear: handleClear, onAccept: handleAccept, onDismiss: handleDismiss, onCancel: handleCancel, onSetToday: handleSetToday, onOpen: handleOpen, onClose: handleClose }; const fieldResponse = { value: dateState.draft, onChange: handleChangeFromField }; const viewValue = React.useMemo(() => valueManager.cleanValue(utils, dateState.draft), [utils, valueManager, dateState.draft]); const viewResponse = { value: viewValue, onChange: handleChange, onClose: handleClose, open: isOpen }; const isValid = testedValue => { const error = validator({ adapter, value: testedValue, timezone, props }); return !valueManager.hasError(error); }; const layoutResponse = (0, _extends2.default)({}, actions, { value: viewValue, onChange: handleChange, onSelectShortcut: handleSelectShortcut, isValid }); const contextValue = React.useMemo(() => ({ onOpen: handleOpen, onClose: handleClose, open: isOpen }), [isOpen, handleClose, handleOpen]); return { open: isOpen, fieldProps: fieldResponse, viewProps: viewResponse, layoutProps: layoutResponse, actions, contextValue }; }; exports.usePickerValue = usePickerValue;