315 lines
12 KiB
JavaScript
315 lines
12 KiB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
const _excluded = ["onClick", "onKeyDown", "onFocus", "onBlur"];
|
|
import * as React from 'react';
|
|
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
|
|
import useEventCallback from '@mui/utils/useEventCallback';
|
|
import { useValidation } from '../validation/useValidation';
|
|
import { useUtils } from '../useUtils';
|
|
import { cleanTrailingZeroInNumericSectionValue, getMonthsMatchingQuery, getSectionValueNumericBoundaries, getSectionVisibleValue, adjustDateSectionValue, adjustInvalidDateSectionValue, setSectionValue } from './useField.utils';
|
|
export const useField = params => {
|
|
const utils = useUtils();
|
|
|
|
if (!utils.formatTokenMap) {
|
|
throw new Error('This adapter is not compatible with the field components');
|
|
}
|
|
|
|
const inputRef = React.useRef(null);
|
|
|
|
const {
|
|
internalProps: {
|
|
value: valueProp,
|
|
defaultValue,
|
|
onChange,
|
|
format = utils.formats.keyboardDate,
|
|
readOnly = false
|
|
},
|
|
forwardedProps: {
|
|
onClick,
|
|
onKeyDown,
|
|
onFocus,
|
|
onBlur
|
|
},
|
|
valueManager,
|
|
fieldValueManager,
|
|
validator
|
|
} = params,
|
|
otherForwardedProps = _objectWithoutPropertiesLoose(params.forwardedProps, _excluded);
|
|
|
|
const firstDefaultValue = React.useRef(defaultValue);
|
|
const focusTimeoutRef = React.useRef(undefined);
|
|
const valueParsed = React.useMemo(() => {
|
|
var _ref, _firstDefaultValue$cu;
|
|
|
|
// TODO: Avoid this type casting, the emptyValues are both valid TDate and TInputDate
|
|
const value = (_ref = (_firstDefaultValue$cu = firstDefaultValue.current) != null ? _firstDefaultValue$cu : valueProp) != null ? _ref : valueManager.emptyValue;
|
|
return valueManager.parseInput(utils, value);
|
|
}, [valueProp, valueManager, utils]);
|
|
const [state, setState] = React.useState(() => {
|
|
const sections = fieldValueManager.getSectionsFromValue(utils, null, valueParsed, format);
|
|
return {
|
|
sections,
|
|
valueParsed,
|
|
valueStr: fieldValueManager.getValueStrFromSections(sections),
|
|
selectedSectionIndexes: null
|
|
};
|
|
});
|
|
|
|
const updateSections = sections => {
|
|
const {
|
|
value: newValueParsed,
|
|
shouldPublish
|
|
} = fieldValueManager.getValueFromSections(utils, state.sections, sections, format);
|
|
setState(prevState => _extends({}, prevState, {
|
|
sections,
|
|
valueStr: fieldValueManager.getValueStrFromSections(sections),
|
|
valueParsed: newValueParsed
|
|
}));
|
|
|
|
if (onChange && shouldPublish) {
|
|
onChange(newValueParsed);
|
|
}
|
|
};
|
|
|
|
const updateSelectedSections = (start, end) => {
|
|
setState(prevState => _extends({}, prevState, {
|
|
selectedSectionIndexes: start == null ? null : {
|
|
start,
|
|
end: end != null ? end : start
|
|
},
|
|
selectedSectionQuery: null
|
|
}));
|
|
};
|
|
|
|
const handleInputClick = useEventCallback((...args) => {
|
|
onClick == null ? void 0 : onClick(...args);
|
|
|
|
if (state.sections.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const nextSectionIndex = state.sections.findIndex(section => {
|
|
var _inputRef$current$sel, _inputRef$current;
|
|
|
|
return section.start > ((_inputRef$current$sel = (_inputRef$current = inputRef.current) == null ? void 0 : _inputRef$current.selectionStart) != null ? _inputRef$current$sel : 0);
|
|
});
|
|
const sectionIndex = nextSectionIndex === -1 ? state.sections.length - 1 : nextSectionIndex - 1;
|
|
updateSelectedSections(sectionIndex);
|
|
});
|
|
const handleInputFocus = useEventCallback((...args) => {
|
|
onFocus == null ? void 0 : onFocus(...args);
|
|
focusTimeoutRef.current = setTimeout(() => {
|
|
var _inputRef$current$sel2, _inputRef$current2, _inputRef$current$sel3, _inputRef$current3;
|
|
|
|
if (((_inputRef$current$sel2 = (_inputRef$current2 = inputRef.current) == null ? void 0 : _inputRef$current2.selectionEnd) != null ? _inputRef$current$sel2 : 0) - ((_inputRef$current$sel3 = (_inputRef$current3 = inputRef.current) == null ? void 0 : _inputRef$current3.selectionStart) != null ? _inputRef$current$sel3 : 0) === 0) {
|
|
handleInputClick();
|
|
} else {
|
|
updateSelectedSections(0, state.sections.length - 1);
|
|
}
|
|
});
|
|
});
|
|
const handleInputBlur = useEventCallback((...args) => {
|
|
onBlur == null ? void 0 : onBlur(...args);
|
|
updateSelectedSections();
|
|
});
|
|
const handleInputKeyDown = useEventCallback(event => {
|
|
onKeyDown == null ? void 0 : onKeyDown(event);
|
|
|
|
if (!inputRef.current || state.sections.length === 0) {
|
|
return;
|
|
} // eslint-disable-next-line default-case
|
|
|
|
|
|
switch (true) {
|
|
// Select all
|
|
case event.key === 'a' && (event.ctrlKey || event.metaKey):
|
|
{
|
|
event.preventDefault();
|
|
updateSelectedSections(0, state.sections.length - 1);
|
|
return;
|
|
}
|
|
// Move selection to next section
|
|
|
|
case event.key === 'ArrowRight':
|
|
{
|
|
event.preventDefault();
|
|
|
|
if (state.selectedSectionIndexes == null) {
|
|
updateSelectedSections(0);
|
|
} else if (state.selectedSectionIndexes.start < state.sections.length - 1) {
|
|
updateSelectedSections(state.selectedSectionIndexes.start + 1);
|
|
} else if (state.selectedSectionIndexes.start !== state.selectedSectionIndexes.end) {
|
|
updateSelectedSections(state.selectedSectionIndexes.end);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// Move selection to previous section
|
|
|
|
case event.key === 'ArrowLeft':
|
|
{
|
|
event.preventDefault();
|
|
|
|
if (state.selectedSectionIndexes == null) {
|
|
updateSelectedSections(state.sections.length - 1);
|
|
} else if (state.selectedSectionIndexes.start !== state.selectedSectionIndexes.end) {
|
|
updateSelectedSections(state.selectedSectionIndexes.start);
|
|
} else if (state.selectedSectionIndexes.start > 0) {
|
|
updateSelectedSections(state.selectedSectionIndexes.start - 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// Reset the value of the selected section
|
|
|
|
case ['Backspace', 'Delete'].includes(event.key):
|
|
{
|
|
event.preventDefault();
|
|
|
|
if (readOnly) {
|
|
return;
|
|
}
|
|
|
|
const resetSections = (startIndex, endIndex) => {
|
|
let sections = state.sections;
|
|
|
|
for (let i = startIndex; i <= endIndex; i += 1) {
|
|
sections = setSectionValue(sections, i, '');
|
|
}
|
|
|
|
return sections;
|
|
};
|
|
|
|
if (state.selectedSectionIndexes == null) {
|
|
updateSections(resetSections(0, state.sections.length));
|
|
} else {
|
|
updateSections(resetSections(state.selectedSectionIndexes.start, state.selectedSectionIndexes.end));
|
|
}
|
|
|
|
break;
|
|
}
|
|
// Increment / decrement the selected section value
|
|
|
|
case ['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(event.key):
|
|
{
|
|
event.preventDefault();
|
|
|
|
if (readOnly || state.selectedSectionIndexes == null) {
|
|
return;
|
|
}
|
|
|
|
const activeSection = state.sections[state.selectedSectionIndexes.start];
|
|
const activeDate = fieldValueManager.getActiveDateFromActiveSection(state.valueParsed, activeSection); // The date is not valid, we have to increment the section value rather than the date
|
|
|
|
if (!utils.isValid(activeDate.value)) {
|
|
const newSectionValue = adjustInvalidDateSectionValue(utils, activeSection, event.key);
|
|
updateSections(setSectionValue(state.sections, state.selectedSectionIndexes.start, newSectionValue));
|
|
} else {
|
|
const newDate = adjustDateSectionValue(utils, activeDate.value, activeSection.dateSectionName, event.key);
|
|
const newValue = activeDate.update(newDate);
|
|
const sections = fieldValueManager.getSectionsFromValue(utils, state.sections, newValue, format);
|
|
updateSections(sections);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// Apply numeric editing on the selected section value
|
|
|
|
case !Number.isNaN(Number(event.key)):
|
|
{
|
|
var _activeDate$value;
|
|
|
|
event.preventDefault();
|
|
|
|
if (readOnly || state.selectedSectionIndexes == null) {
|
|
return;
|
|
}
|
|
|
|
const activeSection = state.sections[state.selectedSectionIndexes.start];
|
|
const activeDate = fieldValueManager.getActiveDateFromActiveSection(state.valueParsed, activeSection);
|
|
const boundaries = getSectionValueNumericBoundaries(utils, (_activeDate$value = activeDate.value) != null ? _activeDate$value : utils.date(), activeSection.dateSectionName);
|
|
const concatenatedSectionValue = `${activeSection.value}${event.key}`;
|
|
const newSectionValue = Number(concatenatedSectionValue) > boundaries.maximum ? event.key : concatenatedSectionValue;
|
|
updateSections(setSectionValue(state.sections, state.selectedSectionIndexes.start, cleanTrailingZeroInNumericSectionValue(newSectionValue, boundaries.maximum)));
|
|
return;
|
|
}
|
|
// Apply full letter editing on the selected section value
|
|
|
|
case event.key.length === 1:
|
|
{
|
|
var _activeSection$query;
|
|
|
|
event.preventDefault();
|
|
|
|
if (readOnly || state.selectedSectionIndexes == null) {
|
|
return;
|
|
}
|
|
|
|
const activeSection = state.sections[state.selectedSectionIndexes.start]; // TODO: Do not hardcode the compatible formatValue
|
|
|
|
if (activeSection.formatValue !== 'MMMM') {
|
|
return;
|
|
}
|
|
|
|
const newQuery = event.key.toLowerCase();
|
|
const concatenatedQuery = `${(_activeSection$query = activeSection.query) != null ? _activeSection$query : ''}${newQuery}`;
|
|
const matchingMonthsWithConcatenatedQuery = getMonthsMatchingQuery(utils, activeSection.formatValue, concatenatedQuery);
|
|
|
|
if (matchingMonthsWithConcatenatedQuery.length > 0) {
|
|
updateSections(setSectionValue(state.sections, state.selectedSectionIndexes.start, matchingMonthsWithConcatenatedQuery[0], concatenatedQuery));
|
|
} else {
|
|
const matchingMonthsWithNewQuery = getMonthsMatchingQuery(utils, activeSection.formatValue, newQuery);
|
|
|
|
if (matchingMonthsWithNewQuery.length > 0) {
|
|
updateSections(setSectionValue(state.sections, state.selectedSectionIndexes.start, matchingMonthsWithNewQuery[0], newQuery));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
useEnhancedEffect(() => {
|
|
if (!inputRef.current || state.selectedSectionIndexes == null) {
|
|
return;
|
|
}
|
|
|
|
const updateSelectionRangeIfChanged = (selectionStart, selectionEnd) => {
|
|
if (selectionStart !== inputRef.current.selectionStart || selectionEnd !== inputRef.current.selectionEnd) {
|
|
inputRef.current.setSelectionRange(selectionStart, selectionEnd);
|
|
}
|
|
};
|
|
|
|
const firstSelectedSection = state.sections[state.selectedSectionIndexes.start];
|
|
const lastSelectedSection = state.sections[state.selectedSectionIndexes.end];
|
|
updateSelectionRangeIfChanged(firstSelectedSection.start, lastSelectedSection.start + getSectionVisibleValue(lastSelectedSection).length);
|
|
});
|
|
React.useEffect(() => {
|
|
if (!valueManager.areValuesEqual(utils, state.valueParsed, valueParsed)) {
|
|
const sections = fieldValueManager.getSectionsFromValue(utils, state.sections, valueParsed, format);
|
|
setState(prevState => _extends({}, prevState, {
|
|
valueParsed,
|
|
valueStr: fieldValueManager.getValueStrFromSections(sections),
|
|
sections
|
|
}));
|
|
}
|
|
}, [valueParsed]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
// TODO: Make validation work with TDate instead of TInputDate
|
|
|
|
const validationError = useValidation(_extends({}, params.internalProps, {
|
|
value: state.valueParsed
|
|
}), validator, fieldValueManager.isSameError);
|
|
const inputError = React.useMemo(() => fieldValueManager.hasError(validationError), [fieldValueManager, validationError]);
|
|
React.useEffect(() => {
|
|
return () => window.clearTimeout(focusTimeoutRef.current);
|
|
}, []);
|
|
return {
|
|
inputProps: _extends({}, otherForwardedProps, {
|
|
value: state.valueStr,
|
|
onClick: handleInputClick,
|
|
onFocus: handleInputFocus,
|
|
onBlur: handleInputBlur,
|
|
onKeyDown: handleInputKeyDown,
|
|
error: inputError
|
|
}),
|
|
inputRef
|
|
};
|
|
}; |