@mui/x-date-pickers
Version:
The community edition of the Date and Time Picker components (MUI X).
253 lines (252 loc) • 9.41 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import useForkRef from '@mui/utils/useForkRef';
import useEventCallback from '@mui/utils/useEventCallback';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { parseSelectedSections } from "./useField.utils.js";
import { getActiveElement } from "../../utils/utils.js";
import { useSplitFieldProps } from "../../../hooks/index.js";
import { useFieldCharacterEditing } from "./useFieldCharacterEditing.js";
import { useFieldState } from "./useFieldState.js";
import { useFieldInternalPropsWithDefaults } from "./useFieldInternalPropsWithDefaults.js";
import { syncSelectionToDOM } from "./syncSelectionToDOM.js";
import { useFieldRootProps } from "./useFieldRootProps.js";
import { useFieldHiddenInputProps } from "./useFieldHiddenInputProps.js";
import { useFieldSectionContainerProps } from "./useFieldSectionContainerProps.js";
import { useFieldSectionContentProps } from "./useFieldSectionContentProps.js";
export const useFieldV7TextField = parameters => {
const {
props,
manager,
skipContextFieldRefAssignment,
manager: {
valueType,
internal_useOpenPickerButtonAriaLabel: useOpenPickerButtonAriaLabel
}
} = parameters;
const {
internalProps,
forwardedProps
} = useSplitFieldProps(props, valueType);
const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({
manager,
internalProps,
skipContextFieldRefAssignment
});
const {
sectionListRef: sectionListRefProp,
onBlur,
onClick,
onFocus,
onInput,
onPaste,
onKeyDown,
onClear,
clearable
} = forwardedProps;
const {
disabled = false,
readOnly = false,
autoFocus = false,
focused: focusedProp,
unstableFieldRef
} = internalPropsWithDefaults;
const sectionListRef = React.useRef(null);
const handleSectionListRef = useForkRef(sectionListRefProp, sectionListRef);
const domGetters = React.useMemo(() => ({
isReady: () => sectionListRef.current != null,
getRoot: () => sectionListRef.current.getRoot(),
getSectionContainer: sectionIndex => sectionListRef.current.getSectionContainer(sectionIndex),
getSectionContent: sectionIndex => sectionListRef.current.getSectionContent(sectionIndex),
getSectionIndexFromDOMElement: element => sectionListRef.current.getSectionIndexFromDOMElement(element)
}), [sectionListRef]);
const stateResponse = useFieldState({
manager,
internalPropsWithDefaults,
forwardedProps
});
const {
// States and derived states
areAllSectionsEmpty,
error,
parsedSelectedSections,
sectionOrder,
state,
value,
// Methods to update the states
clearValue,
setSelectedSections
} = stateResponse;
const applyCharacterEditing = useFieldCharacterEditing({
stateResponse
});
const openPickerAriaLabel = useOpenPickerButtonAriaLabel(value);
const [focused, setFocused] = React.useState(false);
function focusField(newSelectedSections = 0) {
if (disabled || !sectionListRef.current ||
// if the field is already focused, we don't need to focus it again
getActiveSectionIndex(sectionListRef) != null) {
return;
}
const newParsedSelectedSections = parseSelectedSections(newSelectedSections, state.sections);
setFocused(true);
sectionListRef.current.getSectionContent(newParsedSelectedSections).focus();
}
const rootProps = useFieldRootProps({
manager,
internalPropsWithDefaults,
stateResponse,
applyCharacterEditing,
focused,
setFocused,
domGetters
});
const hiddenInputProps = useFieldHiddenInputProps({
manager,
stateResponse
});
const createSectionContainerProps = useFieldSectionContainerProps({
stateResponse,
internalPropsWithDefaults
});
const createSectionContentProps = useFieldSectionContentProps({
manager,
stateResponse,
applyCharacterEditing,
internalPropsWithDefaults,
domGetters,
focused
});
const handleRootKeyDown = useEventCallback(event => {
onKeyDown?.(event);
rootProps.onKeyDown(event);
});
const handleRootBlur = useEventCallback(event => {
onBlur?.(event);
rootProps.onBlur(event);
});
const handleRootFocus = useEventCallback(event => {
onFocus?.(event);
rootProps.onFocus(event);
});
const handleRootClick = useEventCallback(event => {
// The click event on the clear or open button would propagate to the input, trigger this handler and result in an inadvertent section selection.
// We avoid this by checking if the call of `handleInputClick` is actually intended, or a propagated call, which should be skipped.
if (event.isDefaultPrevented()) {
return;
}
onClick?.(event);
rootProps.onClick(event);
});
const handleRootPaste = useEventCallback(event => {
onPaste?.(event);
rootProps.onPaste(event);
});
const handleRootInput = useEventCallback(event => {
onInput?.(event);
rootProps.onInput(event);
});
const handleClear = useEventCallback((event, ...args) => {
event.preventDefault();
onClear?.(event, ...args);
clearValue();
if (!isFieldFocused(sectionListRef)) {
// setSelectedSections is called internally
focusField(0);
} else {
setSelectedSections(sectionOrder.startIndex);
}
});
const elements = React.useMemo(() => {
return state.sections.map((section, sectionIndex) => {
const content = createSectionContentProps(section, sectionIndex);
return {
container: createSectionContainerProps(sectionIndex),
content: createSectionContentProps(section, sectionIndex),
before: {
children: section.startSeparator
},
after: {
children: section.endSeparator,
'data-range-position': section.isEndFormatSeparator ? content['data-range-position'] : undefined
}
};
});
}, [state.sections, createSectionContainerProps, createSectionContentProps]);
React.useEffect(() => {
if (sectionListRef.current == null) {
throw new Error(['MUI X: The `sectionListRef` prop has not been initialized by `PickersSectionList`', 'You probably tried to pass a component to the `textField` slot that contains an `<input />` element instead of a `PickersSectionList`.', '', 'If you want to keep using an `<input />` HTML element for the editing, please add the `enableAccessibleFieldDOMStructure={false}` prop to your Picker or Field component:', '', '<DatePicker enableAccessibleFieldDOMStructure={false} slots={{ textField: MyCustomTextField }} />', '', 'Learn more about the field accessible DOM structure on the MUI documentation: https://mui.com/x/react-date-pickers/fields/#fields-to-edit-a-single-element'].join('\n'));
}
if (autoFocus && !disabled && sectionListRef.current) {
sectionListRef.current.getSectionContent(sectionOrder.startIndex).focus();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
useEnhancedEffect(() => {
if (!focused || !sectionListRef.current) {
return;
}
if (parsedSelectedSections === 'all') {
sectionListRef.current.getRoot().focus();
} else if (typeof parsedSelectedSections === 'number') {
const domElement = sectionListRef.current.getSectionContent(parsedSelectedSections);
if (domElement) {
domElement.focus();
}
}
}, [parsedSelectedSections, focused]);
useEnhancedEffect(() => {
syncSelectionToDOM({
focused,
domGetters,
stateResponse
});
});
React.useImperativeHandle(unstableFieldRef, () => ({
getSections: () => state.sections,
getActiveSectionIndex: () => getActiveSectionIndex(sectionListRef),
setSelectedSections: newSelectedSections => {
if (disabled || !sectionListRef.current) {
return;
}
const newParsedSelectedSections = parseSelectedSections(newSelectedSections, state.sections);
const newActiveSectionIndex = newParsedSelectedSections === 'all' ? 0 : newParsedSelectedSections;
setFocused(newActiveSectionIndex !== null);
setSelectedSections(newSelectedSections);
},
focusField,
isFieldFocused: () => isFieldFocused(sectionListRef)
}));
return _extends({}, forwardedProps, rootProps, {
onBlur: handleRootBlur,
onClick: handleRootClick,
onFocus: handleRootFocus,
onInput: handleRootInput,
onPaste: handleRootPaste,
onKeyDown: handleRootKeyDown,
onClear: handleClear
}, hiddenInputProps, {
error,
clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled),
focused: focusedProp ?? focused,
sectionListRef: handleSectionListRef,
// Additional
enableAccessibleFieldDOMStructure: true,
elements,
areAllSectionsEmpty,
disabled,
readOnly,
autoFocus,
openPickerAriaLabel
});
};
function getActiveSectionIndex(sectionListRef) {
const activeElement = getActiveElement(document);
if (!activeElement || !sectionListRef.current || !sectionListRef.current.getRoot().contains(activeElement)) {
return null;
}
return sectionListRef.current.getSectionIndexFromDOMElement(activeElement);
}
function isFieldFocused(sectionListRef) {
const activeElement = getActiveElement(document);
return !!sectionListRef.current && sectionListRef.current.getRoot().contains(activeElement);
}