255 lines
No EOL
9.3 KiB
JavaScript
255 lines
No EOL
9.3 KiB
JavaScript
'use client';
|
|
|
|
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(sectionListRef.current?.getRoot());
|
|
if (!activeElement || !sectionListRef.current || !sectionListRef.current.getRoot().contains(activeElement)) {
|
|
return null;
|
|
}
|
|
return sectionListRef.current.getSectionIndexFromDOMElement(activeElement);
|
|
}
|
|
function isFieldFocused(sectionListRef) {
|
|
const activeElement = getActiveElement(sectionListRef.current?.getRoot());
|
|
return !!sectionListRef.current && sectionListRef.current.getRoot().contains(activeElement);
|
|
} |