280 lines
11 KiB
JavaScript
280 lines
11 KiB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
import * as React from 'react';
|
|
import useControlled from '@mui/utils/useControlled';
|
|
import { useTheme } from '@mui/material/styles';
|
|
import { useUtils, useLocaleText, useLocalizationContext } from '../useUtils';
|
|
import { addPositionPropertiesToSections, splitFormatIntoSections, mergeDateIntoReferenceDate, getSectionsBoundaries, validateSections, getDateFromDateSections } from './useField.utils';
|
|
import { useValueWithTimezone } from '../useValueWithTimezone';
|
|
import { getSectionTypeGranularity } from '../../utils/getDefaultReferenceDate';
|
|
export const useFieldState = params => {
|
|
const utils = useUtils();
|
|
const localeText = useLocaleText();
|
|
const adapter = useLocalizationContext();
|
|
const theme = useTheme();
|
|
const isRTL = theme.direction === 'rtl';
|
|
const {
|
|
valueManager,
|
|
fieldValueManager,
|
|
valueType,
|
|
validator,
|
|
internalProps,
|
|
internalProps: {
|
|
value: valueProp,
|
|
defaultValue,
|
|
referenceDate: referenceDateProp,
|
|
onChange,
|
|
format,
|
|
formatDensity = 'dense',
|
|
selectedSections: selectedSectionsProp,
|
|
onSelectedSectionsChange,
|
|
shouldRespectLeadingZeros = false,
|
|
timezone: timezoneProp
|
|
}
|
|
} = params;
|
|
const {
|
|
timezone,
|
|
value: valueFromTheOutside,
|
|
handleValueChange
|
|
} = useValueWithTimezone({
|
|
timezone: timezoneProp,
|
|
value: valueProp,
|
|
defaultValue,
|
|
onChange,
|
|
valueManager
|
|
});
|
|
const sectionsValueBoundaries = React.useMemo(() => getSectionsBoundaries(utils, timezone), [utils, timezone]);
|
|
const getSectionsFromValue = React.useCallback((value, fallbackSections = null) => fieldValueManager.getSectionsFromValue(utils, value, fallbackSections, isRTL, date => splitFormatIntoSections(utils, timezone, localeText, format, date, formatDensity, shouldRespectLeadingZeros, isRTL)), [fieldValueManager, format, localeText, isRTL, shouldRespectLeadingZeros, utils, formatDensity, timezone]);
|
|
const placeholder = React.useMemo(() => fieldValueManager.getValueStrFromSections(getSectionsFromValue(valueManager.emptyValue), isRTL), [fieldValueManager, getSectionsFromValue, valueManager.emptyValue, isRTL]);
|
|
const [state, setState] = React.useState(() => {
|
|
const sections = getSectionsFromValue(valueFromTheOutside);
|
|
validateSections(sections, valueType);
|
|
const stateWithoutReferenceDate = {
|
|
sections,
|
|
value: valueFromTheOutside,
|
|
referenceValue: valueManager.emptyValue,
|
|
tempValueStrAndroid: null
|
|
};
|
|
const granularity = getSectionTypeGranularity(sections);
|
|
const referenceValue = valueManager.getInitialReferenceValue({
|
|
referenceDate: referenceDateProp,
|
|
value: valueFromTheOutside,
|
|
utils,
|
|
props: internalProps,
|
|
granularity,
|
|
timezone
|
|
});
|
|
return _extends({}, stateWithoutReferenceDate, {
|
|
referenceValue
|
|
});
|
|
});
|
|
const [selectedSections, innerSetSelectedSections] = useControlled({
|
|
controlled: selectedSectionsProp,
|
|
default: null,
|
|
name: 'useField',
|
|
state: 'selectedSectionIndexes'
|
|
});
|
|
const setSelectedSections = newSelectedSections => {
|
|
innerSetSelectedSections(newSelectedSections);
|
|
onSelectedSectionsChange == null || onSelectedSectionsChange(newSelectedSections);
|
|
setState(prevState => _extends({}, prevState, {
|
|
selectedSectionQuery: null
|
|
}));
|
|
};
|
|
const selectedSectionIndexes = React.useMemo(() => {
|
|
if (selectedSections == null) {
|
|
return null;
|
|
}
|
|
if (selectedSections === 'all') {
|
|
return {
|
|
startIndex: 0,
|
|
endIndex: state.sections.length - 1,
|
|
shouldSelectBoundarySelectors: true
|
|
};
|
|
}
|
|
if (typeof selectedSections === 'number') {
|
|
return {
|
|
startIndex: selectedSections,
|
|
endIndex: selectedSections
|
|
};
|
|
}
|
|
if (typeof selectedSections === 'string') {
|
|
const selectedSectionIndex = state.sections.findIndex(section => section.type === selectedSections);
|
|
return {
|
|
startIndex: selectedSectionIndex,
|
|
endIndex: selectedSectionIndex
|
|
};
|
|
}
|
|
return selectedSections;
|
|
}, [selectedSections, state.sections]);
|
|
const publishValue = ({
|
|
value,
|
|
referenceValue,
|
|
sections
|
|
}) => {
|
|
setState(prevState => _extends({}, prevState, {
|
|
sections,
|
|
value,
|
|
referenceValue,
|
|
tempValueStrAndroid: null
|
|
}));
|
|
if (valueManager.areValuesEqual(utils, state.value, value)) {
|
|
return;
|
|
}
|
|
const context = {
|
|
validationError: validator({
|
|
adapter,
|
|
value,
|
|
props: _extends({}, internalProps, {
|
|
value,
|
|
timezone
|
|
})
|
|
})
|
|
};
|
|
handleValueChange(value, context);
|
|
};
|
|
const setSectionValue = (sectionIndex, newSectionValue) => {
|
|
const newSections = [...state.sections];
|
|
newSections[sectionIndex] = _extends({}, newSections[sectionIndex], {
|
|
value: newSectionValue,
|
|
modified: true
|
|
});
|
|
return addPositionPropertiesToSections(newSections, isRTL);
|
|
};
|
|
const clearValue = () => {
|
|
publishValue({
|
|
value: valueManager.emptyValue,
|
|
referenceValue: state.referenceValue,
|
|
sections: getSectionsFromValue(valueManager.emptyValue)
|
|
});
|
|
};
|
|
const clearActiveSection = () => {
|
|
if (selectedSectionIndexes == null) {
|
|
return;
|
|
}
|
|
const activeSection = state.sections[selectedSectionIndexes.startIndex];
|
|
const activeDateManager = fieldValueManager.getActiveDateManager(utils, state, activeSection);
|
|
const nonEmptySectionCountBefore = activeDateManager.getSections(state.sections).filter(section => section.value !== '').length;
|
|
const hasNoOtherNonEmptySections = nonEmptySectionCountBefore === (activeSection.value === '' ? 0 : 1);
|
|
const newSections = setSectionValue(selectedSectionIndexes.startIndex, '');
|
|
const newActiveDate = hasNoOtherNonEmptySections ? null : utils.date(new Date(''));
|
|
const newValues = activeDateManager.getNewValuesFromNewActiveDate(newActiveDate);
|
|
if ((newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.date != null && !utils.isValid(activeDateManager.date))) {
|
|
publishValue(_extends({}, newValues, {
|
|
sections: newSections
|
|
}));
|
|
} else {
|
|
setState(prevState => _extends({}, prevState, newValues, {
|
|
sections: newSections,
|
|
tempValueStrAndroid: null
|
|
}));
|
|
}
|
|
};
|
|
const updateValueFromValueStr = valueStr => {
|
|
const parseDateStr = (dateStr, referenceDate) => {
|
|
const date = utils.parse(dateStr, format);
|
|
if (date == null || !utils.isValid(date)) {
|
|
return null;
|
|
}
|
|
const sections = splitFormatIntoSections(utils, timezone, localeText, format, date, formatDensity, shouldRespectLeadingZeros, isRTL);
|
|
return mergeDateIntoReferenceDate(utils, timezone, date, sections, referenceDate, false);
|
|
};
|
|
const newValue = fieldValueManager.parseValueStr(valueStr, state.referenceValue, parseDateStr);
|
|
const newReferenceValue = fieldValueManager.updateReferenceValue(utils, newValue, state.referenceValue);
|
|
publishValue({
|
|
value: newValue,
|
|
referenceValue: newReferenceValue,
|
|
sections: getSectionsFromValue(newValue, state.sections)
|
|
});
|
|
};
|
|
const updateSectionValue = ({
|
|
activeSection,
|
|
newSectionValue,
|
|
shouldGoToNextSection
|
|
}) => {
|
|
/**
|
|
* 1. Decide which section should be focused
|
|
*/
|
|
if (shouldGoToNextSection && selectedSectionIndexes && selectedSectionIndexes.startIndex < state.sections.length - 1) {
|
|
setSelectedSections(selectedSectionIndexes.startIndex + 1);
|
|
} else if (selectedSectionIndexes && selectedSectionIndexes.startIndex !== selectedSectionIndexes.endIndex) {
|
|
setSelectedSections(selectedSectionIndexes.startIndex);
|
|
}
|
|
|
|
/**
|
|
* 2. Try to build a valid date from the new section value
|
|
*/
|
|
const activeDateManager = fieldValueManager.getActiveDateManager(utils, state, activeSection);
|
|
const newSections = setSectionValue(selectedSectionIndexes.startIndex, newSectionValue);
|
|
const newActiveDateSections = activeDateManager.getSections(newSections);
|
|
const newActiveDate = getDateFromDateSections(utils, newActiveDateSections);
|
|
let values;
|
|
let shouldPublish;
|
|
|
|
/**
|
|
* 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 (newActiveDate != null && utils.isValid(newActiveDate)) {
|
|
const mergedDate = mergeDateIntoReferenceDate(utils, timezone, newActiveDate, newActiveDateSections, activeDateManager.referenceDate, true);
|
|
values = activeDateManager.getNewValuesFromNewActiveDate(mergedDate);
|
|
shouldPublish = true;
|
|
} else {
|
|
values = activeDateManager.getNewValuesFromNewActiveDate(newActiveDate);
|
|
shouldPublish = (newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.date != null && !utils.isValid(activeDateManager.date));
|
|
}
|
|
|
|
/**
|
|
* Publish or update the internal state with the new value and sections.
|
|
*/
|
|
if (shouldPublish) {
|
|
return publishValue(_extends({}, values, {
|
|
sections: newSections
|
|
}));
|
|
}
|
|
return setState(prevState => _extends({}, prevState, values, {
|
|
sections: newSections,
|
|
tempValueStrAndroid: null
|
|
}));
|
|
};
|
|
const setTempAndroidValueStr = tempValueStrAndroid => setState(prev => _extends({}, prev, {
|
|
tempValueStrAndroid
|
|
}));
|
|
React.useEffect(() => {
|
|
const sections = getSectionsFromValue(state.value);
|
|
validateSections(sections, valueType);
|
|
setState(prevState => _extends({}, prevState, {
|
|
sections
|
|
}));
|
|
}, [format, utils.locale]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
React.useEffect(() => {
|
|
let shouldUpdate = false;
|
|
if (!valueManager.areValuesEqual(utils, state.value, valueFromTheOutside)) {
|
|
shouldUpdate = true;
|
|
} else {
|
|
shouldUpdate = valueManager.getTimezone(utils, state.value) !== valueManager.getTimezone(utils, valueFromTheOutside);
|
|
}
|
|
if (shouldUpdate) {
|
|
setState(prevState => _extends({}, prevState, {
|
|
value: valueFromTheOutside,
|
|
referenceValue: fieldValueManager.updateReferenceValue(utils, valueFromTheOutside, prevState.referenceValue),
|
|
sections: getSectionsFromValue(valueFromTheOutside)
|
|
}));
|
|
}
|
|
}, [valueFromTheOutside]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
return {
|
|
state,
|
|
selectedSectionIndexes,
|
|
setSelectedSections,
|
|
clearValue,
|
|
clearActiveSection,
|
|
updateSectionValue,
|
|
updateValueFromValueStr,
|
|
setTempAndroidValueStr,
|
|
sectionsValueBoundaries,
|
|
placeholder,
|
|
timezone
|
|
};
|
|
}; |