UNPKG

@mui/x-date-pickers

Version:

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

401 lines (391 loc) 15.3 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.useFieldState = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled")); var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout")); var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); var _RtlProvider = require("@mui/system/RtlProvider"); var _usePickerTranslations = require("../../../hooks/usePickerTranslations"); var _useUtils = require("../useUtils"); var _useField = require("./useField.utils"); var _buildSectionsFromFormat = require("./buildSectionsFromFormat"); var _validation = require("../../../validation"); var _useControlledValue = require("../useControlledValue"); var _getDefaultReferenceDate = require("../../utils/getDefaultReferenceDate"); const QUERY_LIFE_DURATION_MS = 5000; const useFieldState = parameters => { const utils = (0, _useUtils.useUtils)(); const translations = (0, _usePickerTranslations.usePickerTranslations)(); const adapter = (0, _useUtils.useLocalizationContext)(); const isRtl = (0, _RtlProvider.useRtl)(); const { manager: { validator, valueType, internal_valueManager: valueManager, internal_fieldValueManager: fieldValueManager }, internalPropsWithDefaults, internalPropsWithDefaults: { value: valueProp, defaultValue, referenceDate: referenceDateProp, onChange, format, formatDensity = 'dense', selectedSections: selectedSectionsProp, onSelectedSectionsChange, shouldRespectLeadingZeros = false, timezone: timezoneProp, enableAccessibleFieldDOMStructure = true }, forwardedProps: { error: errorProp } } = parameters; const { value, handleValueChange, timezone } = (0, _useControlledValue.useControlledValue)({ name: 'a field component', timezone: timezoneProp, value: valueProp, defaultValue, referenceDate: referenceDateProp, onChange, valueManager }); const valueRef = React.useRef(value); React.useEffect(() => { valueRef.current = value; }, [value]); const { hasValidationError } = (0, _validation.useValidation)({ props: internalPropsWithDefaults, validator, timezone, value, onError: internalPropsWithDefaults.onError }); const error = React.useMemo(() => { // only override when `error` is undefined. // in case of multi input fields, the `error` value is provided externally and will always be defined. if (errorProp !== undefined) { return errorProp; } return hasValidationError; }, [hasValidationError, errorProp]); const localizedDigits = React.useMemo(() => (0, _useField.getLocalizedDigits)(utils), [utils]); const sectionsValueBoundaries = React.useMemo(() => (0, _useField.getSectionsBoundaries)(utils, localizedDigits, timezone), [utils, localizedDigits, timezone]); const getSectionsFromValue = React.useCallback(valueToAnalyze => fieldValueManager.getSectionsFromValue(valueToAnalyze, date => (0, _buildSectionsFromFormat.buildSectionsFromFormat)({ utils, localeText: translations, localizedDigits, format, date, formatDensity, shouldRespectLeadingZeros, enableAccessibleFieldDOMStructure, isRtl })), [fieldValueManager, format, translations, localizedDigits, isRtl, shouldRespectLeadingZeros, utils, formatDensity, enableAccessibleFieldDOMStructure]); const [state, setState] = React.useState(() => { const sections = getSectionsFromValue(value); (0, _useField.validateSections)(sections, valueType); const stateWithoutReferenceDate = { sections, lastExternalValue: value, lastSectionsDependencies: { format, isRtl, locale: utils.locale }, tempValueStrAndroid: null, characterQuery: null }; const granularity = (0, _getDefaultReferenceDate.getSectionTypeGranularity)(sections); const referenceValue = valueManager.getInitialReferenceValue({ referenceDate: referenceDateProp, value, utils, props: internalPropsWithDefaults, granularity, timezone }); return (0, _extends2.default)({}, stateWithoutReferenceDate, { referenceValue }); }); const [selectedSections, innerSetSelectedSections] = (0, _useControlled.default)({ controlled: selectedSectionsProp, default: null, name: 'useField', state: 'selectedSections' }); const setSelectedSections = newSelectedSections => { innerSetSelectedSections(newSelectedSections); onSelectedSectionsChange?.(newSelectedSections); }; const parsedSelectedSections = React.useMemo(() => (0, _useField.parseSelectedSections)(selectedSections, state.sections), [selectedSections, state.sections]); const activeSectionIndex = parsedSelectedSections === 'all' ? 0 : parsedSelectedSections; const sectionOrder = React.useMemo(() => (0, _useField.getSectionOrder)(state.sections, isRtl && !enableAccessibleFieldDOMStructure), [state.sections, isRtl, enableAccessibleFieldDOMStructure]); const areAllSectionsEmpty = React.useMemo(() => state.sections.every(section => section.value === ''), [state.sections]); const publishValue = newValue => { const context = { validationError: validator({ adapter, value: newValue, timezone, props: internalPropsWithDefaults }) }; handleValueChange(newValue, context); }; const setSectionValue = (sectionIndex, newSectionValue) => { const newSections = [...state.sections]; newSections[sectionIndex] = (0, _extends2.default)({}, newSections[sectionIndex], { value: newSectionValue, modified: true }); return newSections; }; const sectionToUpdateOnNextInvalidDateRef = React.useRef(null); const updateSectionValueOnNextInvalidDateTimeout = (0, _useTimeout.default)(); const setSectionUpdateToApplyOnNextInvalidDate = newSectionValue => { if (activeSectionIndex == null) { return; } sectionToUpdateOnNextInvalidDateRef.current = { sectionIndex: activeSectionIndex, value: newSectionValue }; updateSectionValueOnNextInvalidDateTimeout.start(0, () => { sectionToUpdateOnNextInvalidDateRef.current = null; }); }; const clearValue = () => { if (valueManager.areValuesEqual(utils, value, valueManager.emptyValue)) { setState(prevState => (0, _extends2.default)({}, prevState, { sections: prevState.sections.map(section => (0, _extends2.default)({}, section, { value: '' })), tempValueStrAndroid: null, characterQuery: null })); } else { setState(prevState => (0, _extends2.default)({}, prevState, { characterQuery: null })); publishValue(valueManager.emptyValue); } }; const clearActiveSection = () => { if (activeSectionIndex == null) { return; } const activeSection = state.sections[activeSectionIndex]; if (activeSection.value === '') { return; } setSectionUpdateToApplyOnNextInvalidDate(''); if (fieldValueManager.getDateFromSection(value, activeSection) === null) { setState(prevState => (0, _extends2.default)({}, prevState, { sections: setSectionValue(activeSectionIndex, ''), tempValueStrAndroid: null, characterQuery: null })); } else { setState(prevState => (0, _extends2.default)({}, prevState, { characterQuery: null })); publishValue(fieldValueManager.updateDateInValue(value, activeSection, null)); } }; const updateValueFromValueStr = valueStr => { const parseDateStr = (dateStr, referenceDate) => { const date = utils.parse(dateStr, format); if (!utils.isValid(date)) { return null; } const sections = (0, _buildSectionsFromFormat.buildSectionsFromFormat)({ utils, localeText: translations, localizedDigits, format, date, formatDensity, shouldRespectLeadingZeros, enableAccessibleFieldDOMStructure, isRtl }); return (0, _useField.mergeDateIntoReferenceDate)(utils, date, sections, referenceDate, false); }; const newValue = fieldValueManager.parseValueStr(valueStr, state.referenceValue, parseDateStr); publishValue(newValue); }; const cleanActiveDateSectionsIfValueNullTimeout = (0, _useTimeout.default)(); const updateSectionValue = ({ section, newSectionValue, shouldGoToNextSection }) => { updateSectionValueOnNextInvalidDateTimeout.clear(); cleanActiveDateSectionsIfValueNullTimeout.clear(); const activeDate = fieldValueManager.getDateFromSection(value, section); /** * Decide which section should be focused */ if (shouldGoToNextSection && activeSectionIndex < state.sections.length - 1) { setSelectedSections(activeSectionIndex + 1); } /** * Try to build a valid date from the new section value */ const newSections = setSectionValue(activeSectionIndex, newSectionValue); const newActiveDateSections = fieldValueManager.getDateSectionsFromValue(newSections, section); const newActiveDate = (0, _useField.getDateFromDateSections)(utils, newActiveDateSections, localizedDigits); /** * If the new date is valid, * Then we merge the value of the modified sections into the reference date. * This makes sure that we don't lose some information of the initial date (like the time on a date field). */ if (utils.isValid(newActiveDate)) { const mergedDate = (0, _useField.mergeDateIntoReferenceDate)(utils, newActiveDate, newActiveDateSections, fieldValueManager.getDateFromSection(state.referenceValue, section), true); if (activeDate == null) { cleanActiveDateSectionsIfValueNullTimeout.start(0, () => { if (valueRef.current === value) { setState(prevState => (0, _extends2.default)({}, prevState, { sections: fieldValueManager.clearDateSections(state.sections, section), tempValueStrAndroid: null })); } }); } return publishValue(fieldValueManager.updateDateInValue(value, section, mergedDate)); } /** * If all the sections are filled but the date is invalid, * Then we publish an invalid date. */ if (newActiveDateSections.every(sectionBis => sectionBis.value !== '')) { setSectionUpdateToApplyOnNextInvalidDate(newSectionValue); return publishValue(fieldValueManager.updateDateInValue(value, section, newActiveDate)); } /** * If the previous date is not null, * Then we publish the date as `null`. */ if (activeDate != null) { setSectionUpdateToApplyOnNextInvalidDate(newSectionValue); return publishValue(fieldValueManager.updateDateInValue(value, section, null)); } /** * If the previous date is already null, * Then we don't publish the date and we update the sections. */ return setState(prevState => (0, _extends2.default)({}, prevState, { sections: newSections, tempValueStrAndroid: null })); }; const setTempAndroidValueStr = tempValueStrAndroid => setState(prevState => (0, _extends2.default)({}, prevState, { tempValueStrAndroid })); const setCharacterQuery = (0, _useEventCallback.default)(newCharacterQuery => { setState(prevState => (0, _extends2.default)({}, prevState, { characterQuery: newCharacterQuery })); }); // If `prop.value` changes, we update the state to reflect the new value if (value !== state.lastExternalValue) { let sections; if (sectionToUpdateOnNextInvalidDateRef.current != null && !utils.isValid(fieldValueManager.getDateFromSection(value, state.sections[sectionToUpdateOnNextInvalidDateRef.current.sectionIndex]))) { sections = setSectionValue(sectionToUpdateOnNextInvalidDateRef.current.sectionIndex, sectionToUpdateOnNextInvalidDateRef.current.value); } else { sections = getSectionsFromValue(value); } setState(prevState => (0, _extends2.default)({}, prevState, { lastExternalValue: value, sections, sectionsDependencies: { format, isRtl, locale: utils.locale }, referenceValue: fieldValueManager.updateReferenceValue(utils, value, prevState.referenceValue), tempValueStrAndroid: null })); } if (isRtl !== state.lastSectionsDependencies.isRtl || format !== state.lastSectionsDependencies.format || utils.locale !== state.lastSectionsDependencies.locale) { const sections = getSectionsFromValue(value); (0, _useField.validateSections)(sections, valueType); setState(prevState => (0, _extends2.default)({}, prevState, { lastSectionsDependencies: { format, isRtl, locale: utils.locale }, sections, tempValueStrAndroid: null, characterQuery: null })); } if (state.characterQuery != null && !error && activeSectionIndex == null) { setCharacterQuery(null); } if (state.characterQuery != null && state.sections[state.characterQuery.sectionIndex]?.type !== state.characterQuery.sectionType) { setCharacterQuery(null); } React.useEffect(() => { if (sectionToUpdateOnNextInvalidDateRef.current != null) { sectionToUpdateOnNextInvalidDateRef.current = null; } }); const cleanCharacterQueryTimeout = (0, _useTimeout.default)(); React.useEffect(() => { if (state.characterQuery != null) { cleanCharacterQueryTimeout.start(QUERY_LIFE_DURATION_MS, () => setCharacterQuery(null)); } return () => {}; }, [state.characterQuery, setCharacterQuery, cleanCharacterQueryTimeout]); // If `tempValueStrAndroid` is still defined for some section when running `useEffect`, // Then `onChange` has only been called once, which means the user pressed `Backspace` to reset the section. // This causes a small flickering on Android, // But we can't use `useEnhancedEffect` which is always called before the second `onChange` call and then would cause false positives. React.useEffect(() => { if (state.tempValueStrAndroid != null && activeSectionIndex != null) { clearActiveSection(); } }, [state.sections]); // eslint-disable-line react-hooks/exhaustive-deps return { // States and derived states activeSectionIndex, areAllSectionsEmpty, error, localizedDigits, parsedSelectedSections, sectionOrder, sectionsValueBoundaries, state, timezone, value, // Methods to update the states clearValue, clearActiveSection, setCharacterQuery, setSelectedSections, setTempAndroidValueStr, updateSectionValue, updateValueFromValueStr, // Utilities methods getSectionsFromValue }; }; exports.useFieldState = useFieldState;