UNPKG

@mui/x-date-pickers

Version:

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

350 lines (345 loc) 15.6 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; const _excluded = ["readOnly", "onClear", "clearable", "clearButtonPosition", "openPickerButtonPosition", "openPickerAriaLabel", "InputProps", "inputProps", "InputLabelProps", "FormHelperTextProps"], _excluded2 = ["ownerState"], _excluded3 = ["ownerState"], _excluded4 = ["ownerState"], _excluded5 = ["ownerState"], _excluded6 = ["InputProps", "inputProps", "InputLabelProps", "FormHelperTextProps"]; import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import useForkRef from '@mui/utils/useForkRef'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import MuiIconButton from '@mui/material/IconButton'; import MuiInputAdornment from '@mui/material/InputAdornment'; import useSlotProps from '@mui/utils/useSlotProps'; import { warnOnce } from '@mui/x-internals/warning'; import { useFieldOwnerState } from "../hooks/useFieldOwnerState.mjs"; import { usePickerTranslations } from "../../hooks/index.mjs"; import { ClearIcon as MuiClearIcon } from "../../icons/index.mjs"; import { useNullablePickerContext } from "../hooks/useNullablePickerContext.mjs"; import { PickersTextField } from "../../PickersTextField/index.mjs"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export const cleanFieldResponse = fieldResponse => { const _ref = fieldResponse, { readOnly, onClear, clearable, clearButtonPosition, openPickerButtonPosition, openPickerAriaLabel, // TODO v10: remove // Explicitly discard legacy props that are no longer supported on `PickersTextField`. // Without this, any leftover values would silently leak into `...other` and end up spread // as unknown attributes on the underlying form control. InputProps: legacyInputProps, inputProps: legacyHtmlInputProps, InputLabelProps: legacyInputLabelProps, FormHelperTextProps: legacyFormHelperTextProps } = _ref, other = _objectWithoutPropertiesLoose(_ref, _excluded); if (process.env.NODE_ENV !== 'production') { if (legacyInputProps || legacyHtmlInputProps || legacyInputLabelProps || legacyFormHelperTextProps) { warnOnce(['MUI X: The `InputProps`, `inputProps`, `InputLabelProps` and `FormHelperTextProps` props are no longer supported on Picker / Field components.', 'They have been silently dropped because they would otherwise be forwarded as unknown attributes on the underlying form control.', 'Use the `slotProps` shape instead (`slotProps.input`, `slotProps.htmlInput`, `slotProps.inputLabel`, `slotProps.formHelperText`).', 'See https://mui.com/x/migration/migration-pickers-v8/#textfield-props for migration details.']); } } return { clearable, onClear, clearButtonPosition, openPickerButtonPosition, openPickerAriaLabel, textFieldProps: _extends({}, other, { slotProps: _extends({}, other?.slotProps, { input: _extends({}, other?.slotProps?.input, { readOnly }) }) }) }; }; export const PickerFieldUIContext = /*#__PURE__*/React.createContext({ slots: {}, slotProps: {}, inputRef: undefined }); /** * Adds the button to open the Picker and the button to clear the value of the field. * @ignore - internal component. */ if (process.env.NODE_ENV !== "production") PickerFieldUIContext.displayName = "PickerFieldUIContext"; export function PickerFieldUI(props) { const { fieldResponse, defaultOpenPickerIcon } = props; const translations = usePickerTranslations(); const pickerContext = useNullablePickerContext(); const pickerFieldUIContext = React.useContext(PickerFieldUIContext); const { textFieldProps, onClear, clearable, openPickerAriaLabel, clearButtonPosition: clearButtonPositionProp = 'end', openPickerButtonPosition: openPickerButtonPositionProp = 'end' } = cleanFieldResponse(fieldResponse); const ownerState = useFieldOwnerState(textFieldProps); const handleClickOpeningButton = useEventCallback(event => { event.preventDefault(); // Force open instead of toggling to avoid conflicts with field-level open-on-focus logic pickerContext?.setOpen(true); }); const triggerStatus = pickerContext ? pickerContext.triggerStatus : 'hidden'; const clearButtonPosition = clearable ? clearButtonPositionProp : null; const openPickerButtonPosition = triggerStatus !== 'hidden' ? openPickerButtonPositionProp : null; const TextField = pickerFieldUIContext.slots.textField ?? PickersTextField; const InputAdornment = pickerFieldUIContext.slots.inputAdornment ?? MuiInputAdornment; const _useSlotProps = useSlotProps({ elementType: InputAdornment, externalSlotProps: pickerFieldUIContext.slotProps.inputAdornment, additionalProps: { position: 'start' }, ownerState: _extends({}, ownerState, { position: 'start' }) }), startInputAdornmentProps = _objectWithoutPropertiesLoose(_useSlotProps, _excluded2); const _useSlotProps2 = useSlotProps({ elementType: InputAdornment, externalSlotProps: pickerFieldUIContext.slotProps.inputAdornment, additionalProps: { position: 'end' }, ownerState: _extends({}, ownerState, { position: 'end' }) }), endInputAdornmentProps = _objectWithoutPropertiesLoose(_useSlotProps2, _excluded3); const OpenPickerButton = pickerFieldUIContext.slots.openPickerButton ?? MuiIconButton; // We don't want to forward the `ownerState` to the `<IconButton />` component, see mui/material-ui#34056 const _useSlotProps3 = useSlotProps({ elementType: OpenPickerButton, externalSlotProps: pickerFieldUIContext.slotProps.openPickerButton, additionalProps: { disabled: triggerStatus === 'disabled', onClick: handleClickOpeningButton, 'aria-label': openPickerAriaLabel, // Mark this element so field handlers can ignore its events 'data-mui-picker-open-button': 'true', edge: // open button is always rendered at the edge textFieldProps.variant !== 'standard' ? openPickerButtonPosition : false }, ownerState }), openPickerButtonProps = _objectWithoutPropertiesLoose(_useSlotProps3, _excluded4); const OpenPickerIcon = pickerFieldUIContext.slots.openPickerIcon ?? defaultOpenPickerIcon; const openPickerIconProps = useSlotProps({ elementType: OpenPickerIcon, externalSlotProps: pickerFieldUIContext.slotProps.openPickerIcon, ownerState }); const ClearButton = pickerFieldUIContext.slots.clearButton ?? MuiIconButton; // We don't want to forward the `ownerState` to the `<IconButton />` component, see mui/material-ui#34056 const _useSlotProps4 = useSlotProps({ elementType: ClearButton, externalSlotProps: pickerFieldUIContext.slotProps.clearButton, className: 'clearButton', additionalProps: { title: translations.fieldClearLabel, tabIndex: -1, onClick: onClear, disabled: fieldResponse.disabled || fieldResponse.readOnly, edge: // clear button can only be at the edge if it's position differs from the open button textFieldProps.variant !== 'standard' && clearButtonPosition !== openPickerButtonPosition ? clearButtonPosition : false }, ownerState }), clearButtonProps = _objectWithoutPropertiesLoose(_useSlotProps4, _excluded5); const ClearIcon = pickerFieldUIContext.slots.clearIcon ?? MuiClearIcon; const clearIconProps = useSlotProps({ elementType: ClearIcon, externalSlotProps: pickerFieldUIContext.slotProps.clearIcon, additionalProps: { fontSize: 'small' }, ownerState }); textFieldProps.ref = useForkRef(textFieldProps.ref, pickerContext?.rootRef); const externalInputSlotProps = textFieldProps.slotProps?.input; const additionalInputSlotProps = {}; const forkedInputRef = useForkRef(externalInputSlotProps?.ref, pickerContext?.triggerRef); if (pickerContext) { additionalInputSlotProps.ref = forkedInputRef; } if (!externalInputSlotProps?.startAdornment && (clearButtonPosition === 'start' || openPickerButtonPosition === 'start')) { additionalInputSlotProps.startAdornment = /*#__PURE__*/_jsxs(InputAdornment, _extends({}, startInputAdornmentProps, { children: [openPickerButtonPosition === 'start' && /*#__PURE__*/_jsx(OpenPickerButton, _extends({}, openPickerButtonProps, { children: /*#__PURE__*/_jsx(OpenPickerIcon, _extends({}, openPickerIconProps)) })), clearButtonPosition === 'start' && /*#__PURE__*/_jsx(ClearButton, _extends({}, clearButtonProps, { children: /*#__PURE__*/_jsx(ClearIcon, _extends({}, clearIconProps)) }))] })); } if (!externalInputSlotProps?.endAdornment && (clearButtonPosition === 'end' || openPickerButtonPosition === 'end')) { additionalInputSlotProps.endAdornment = /*#__PURE__*/_jsxs(InputAdornment, _extends({}, endInputAdornmentProps, { children: [clearButtonPosition === 'end' && /*#__PURE__*/_jsx(ClearButton, _extends({}, clearButtonProps, { children: /*#__PURE__*/_jsx(ClearIcon, _extends({}, clearIconProps)) })), openPickerButtonPosition === 'end' && /*#__PURE__*/_jsx(OpenPickerButton, _extends({}, openPickerButtonProps, { children: /*#__PURE__*/_jsx(OpenPickerIcon, _extends({}, openPickerIconProps)) }))] })); } // handle the case of showing custom `inputAdornment` for Field components if (!additionalInputSlotProps.endAdornment && !additionalInputSlotProps.startAdornment && pickerFieldUIContext.slots.inputAdornment) { additionalInputSlotProps.endAdornment = /*#__PURE__*/_jsx(InputAdornment, _extends({}, endInputAdornmentProps)); } if (clearButtonPosition != null) { textFieldProps.sx = [{ '& .clearButton': { opacity: 1 }, '@media (pointer: fine)': { '& .clearButton': { opacity: 0 }, '&:hover, &:focus-within': { '.clearButton': { opacity: 1 } } } }, ...(Array.isArray(textFieldProps.sx) ? textFieldProps.sx : [textFieldProps.sx])]; } textFieldProps.slotProps = _extends({}, textFieldProps.slotProps, { input: _extends({}, externalInputSlotProps, additionalInputSlotProps) }); return /*#__PURE__*/_jsx(TextField, _extends({}, textFieldProps)); } export function mergeSlotProps(slotPropsA, slotPropsB) { if (!slotPropsA) { return slotPropsB; } if (!slotPropsB) { return slotPropsA; } return ownerState => { return _extends({}, resolveComponentProps(slotPropsB, ownerState), resolveComponentProps(slotPropsA, ownerState)); }; } /** * The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. * TODO v10: Remove the `textField` slot and clean this logic up. */ export function useFieldTextFieldProps(parameters) { const { ref, externalForwardedProps, slotProps } = parameters; const pickerFieldUIContext = React.useContext(PickerFieldUIContext); const pickerContext = useNullablePickerContext(); const ownerState = useFieldOwnerState(externalForwardedProps); // TODO v10: remove // Strip the legacy `InputProps` / `inputProps` / `InputLabelProps` / `FormHelperTextProps` // before they reach `PickersTextField`, which would silently ignore them. JS users without // TypeScript checks would otherwise see their configuration vanish. const _ref2 = externalForwardedProps ?? {}, { InputProps: legacyInputProps, inputProps: legacyHtmlInputProps, InputLabelProps: legacyInputLabelProps, FormHelperTextProps: legacyFormHelperTextProps } = _ref2, sanitizedExternalForwardedProps = _objectWithoutPropertiesLoose(_ref2, _excluded6); if (process.env.NODE_ENV !== 'production') { if (legacyInputProps || legacyHtmlInputProps || legacyInputLabelProps || legacyFormHelperTextProps) { warnOnce(['MUI X: Field components no longer accept the `InputProps`, `inputProps`, `InputLabelProps` and `FormHelperTextProps` props.', 'They have been dropped to avoid leaking unknown attributes onto the underlying form control.', 'Use the nested `slotProps.textField.slotProps.{input,htmlInput,inputLabel,formHelperText}` shape instead.']); } } const textFieldProps = useSlotProps({ elementType: PickersTextField, externalSlotProps: mergeSlotProps(pickerFieldUIContext.slotProps.textField, slotProps?.textField), externalForwardedProps: sanitizedExternalForwardedProps, additionalProps: { ref, sx: pickerContext?.rootSx, label: pickerContext?.label, name: pickerContext?.name, className: pickerContext?.rootClassName, inputRef: pickerFieldUIContext.inputRef }, ownerState }); // When requested, open the picker when the user focuses/clicks the field to edit if (pickerContext && pickerContext.keepOpenDuringFieldFocus && pickerContext.triggerStatus === 'enabled' && !pickerContext.open && !pickerContext.readOnly && !pickerContext.disabled) { const prevOnFocus = textFieldProps.onFocus; const prevOnMouseDown = textFieldProps.onMouseDown; const isFromOpenButton = event => { const nativeEvent = event.nativeEvent ?? event; const path = nativeEvent?.composedPath?.(); if (Array.isArray(path)) { for (const el of path) { if (el && el.getAttribute && el.getAttribute('data-mui-picker-open-button') === 'true') { return true; } } } const target = event.target; if (target && target.closest) { return Boolean(target.closest('[data-mui-picker-open-button="true"]')); } return false; }; textFieldProps.onFocus = event => { prevOnFocus?.(event); // Avoid opening if event was prevented by user code if (!event.isDefaultPrevented() && !isFromOpenButton(event)) { pickerContext.setOpen(true); } }; textFieldProps.onMouseDown = event => { prevOnMouseDown?.(event); if (!event.isDefaultPrevented() && !isFromOpenButton(event)) { pickerContext.setOpen(true); } }; } return textFieldProps; } export function PickerFieldUIContextProvider(props) { const { slots = {}, slotProps = {}, inputRef, children } = props; const contextValue = React.useMemo(() => ({ inputRef, slots: { openPickerButton: slots.openPickerButton, openPickerIcon: slots.openPickerIcon, textField: slots.textField, inputAdornment: slots.inputAdornment, clearIcon: slots.clearIcon, clearButton: slots.clearButton }, slotProps: { openPickerButton: slotProps.openPickerButton, openPickerIcon: slotProps.openPickerIcon, textField: slotProps.textField, inputAdornment: slotProps.inputAdornment, clearIcon: slotProps.clearIcon, clearButton: slotProps.clearButton } }), [inputRef, slots.openPickerButton, slots.openPickerIcon, slots.textField, slots.inputAdornment, slots.clearIcon, slots.clearButton, slotProps.openPickerButton, slotProps.openPickerIcon, slotProps.textField, slotProps.inputAdornment, slotProps.clearIcon, slotProps.clearButton]); return /*#__PURE__*/_jsx(PickerFieldUIContext.Provider, { value: contextValue, children: children }); }