diff --git a/docs/data/date-pickers/base-concepts/base-concepts.md b/docs/data/date-pickers/base-concepts/base-concepts.md index ca61ae0cbe0f..37ee4a4c2c58 100644 --- a/docs/data/date-pickers/base-concepts/base-concepts.md +++ b/docs/data/date-pickers/base-concepts/base-concepts.md @@ -113,10 +113,10 @@ Each _Picker_ is available in a responsive, desktop and mobile variant: - The responsive component (for example `DatePicker`) which renders the desktop component or the mobile one depending on the device it runs on. - The desktop component (for example `DesktopDatePicker`) which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The mobile component (for example `MobileDatePicker`) which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. {{"demo": "ResponsivePickers.js"}} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index 377759e11375..71283c58d1d2 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -25,16 +25,8 @@ const cacheRtl = createCache({ function ButtonDateTimeField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -53,7 +45,7 @@ function ButtonDateTimeField(props) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx index 704dbee32ae7..63bbb7aaaa40 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx @@ -29,16 +29,8 @@ const cacheRtl = createCache({ function ButtonDateTimeField(props: DateTimePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -57,7 +49,7 @@ function ButtonDateTimeField(props: DateTimePickerFieldProps) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/BrowserV7Field.js b/docs/data/date-pickers/custom-field/BrowserV7Field.js index 11f5b168ae3f..f4a7c867b747 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7Field.js +++ b/docs/data/date-pickers/custom-field/BrowserV7Field.js @@ -1,11 +1,14 @@ import * as React from 'react'; import useForkRef from '@mui/utils/useForkRef'; import { styled } from '@mui/material/styles'; +import IconButton from '@mui/material/IconButton'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; +import { usePickerContext } from '@mui/x-date-pickers/hooks'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', @@ -41,6 +44,8 @@ const BrowserDateField = React.forwardRef((props, ref) => { onInput, onPaste, onKeyDown, + // Should be passed to the button that opens the picker + openPickerAriaLabel, // Can be passed to a hidden element onChange, value, @@ -55,16 +60,15 @@ const BrowserDateField = React.forwardRef((props, ref) => { readOnly, focused, error, - InputProps: { ref: InputPropsRef, startAdornment, endAdornment } = {}, // The rest can be passed to the root element ...other } = fieldResponse; - const handleRef = useForkRef(InputPropsRef, ref); + const pickerContext = usePickerContext(); + const handleRef = useForkRef(pickerContext.triggerRef, ref); return ( - {startAdornment} { onKeyDown={onKeyDown} /> - {endAdornment} + pickerContext.setOpen((prev) => !prev)} + sx={{ marginLeft: 1.5 }} + aria-label={openPickerAriaLabel} + > + + ); }); diff --git a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx index a218b603030f..75545b14ce8c 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import useForkRef from '@mui/utils/useForkRef'; import { styled } from '@mui/material/styles'; +import IconButton from '@mui/material/IconButton'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { @@ -10,6 +12,7 @@ import { } from '@mui/x-date-pickers/DatePicker'; import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; +import { usePickerContext } from '@mui/x-date-pickers/hooks'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', @@ -48,6 +51,9 @@ const BrowserDateField = React.forwardRef( onPaste, onKeyDown, + // Should be passed to the button that opens the picker + openPickerAriaLabel, + // Can be passed to a hidden element onChange, value, @@ -66,17 +72,15 @@ const BrowserDateField = React.forwardRef( focused, error, - InputProps: { ref: InputPropsRef, startAdornment, endAdornment } = {}, - // The rest can be passed to the root element ...other } = fieldResponse; - const handleRef = useForkRef(InputPropsRef, ref); + const pickerContext = usePickerContext(); + const handleRef = useForkRef(pickerContext.triggerRef, ref); return ( - {startAdornment} - {endAdornment} + pickerContext.setOpen((prev) => !prev)} + sx={{ marginLeft: 1.5 }} + aria-label={openPickerAriaLabel} + > + + ); }, diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js index 5f7d2d494688..2d9bef9d5a83 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js @@ -100,13 +100,13 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { const startTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: {}, }); const endTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: {}, }); const fieldResponse = useMultiInputDateRangeField({ @@ -131,6 +131,19 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { unstableEndFieldRef, }); + const { + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + onClear: onClearEndDate, + clearable: isEndDateClearable, + openPickerAriaLabel: openPickerEndDateAriaLabel, + ...endDateProps + } = fieldResponse.endDate; + return ( { overflow="auto" className={className} > - + - + ); }); diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx index 4c60152c21d0..8c29e3ba1a7b 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx @@ -136,13 +136,13 @@ const BrowserMultiInputDateRangeField = React.forwardRef( const startTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: {} as any, }) as MultiInputFieldSlotTextFieldProps; const endTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: {} as any, }) as MultiInputFieldSlotTextFieldProps; const fieldResponse = useMultiInputDateRangeField< @@ -170,6 +170,19 @@ const BrowserMultiInputDateRangeField = React.forwardRef( unstableEndFieldRef, }); + const { + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + onClear: onClearEndDate, + clearable: isEndDateClearable, + openPickerAriaLabel: openPickerEndDateAriaLabel, + ...endDateProps + } = fieldResponse.endDate; + return ( - + - + ); }, diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js index ae55770f6078..5c2fdcc30f44 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js @@ -59,13 +59,12 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { readOnly, focused, error, - InputProps: { ref: InputPropsRef, startAdornment, endAdornment } = {}, // The rest can be passed to the root element ...other } = fieldResponse; const pickerContext = usePickerContext(); - const handleRef = useForkRef(InputPropsRef, ref); + const handleRef = useForkRef(pickerContext.triggerRef, ref); return ( { minWidth: 300, }} > - {startAdornment} { onKeyDown={onKeyDown} /> - {endAdornment} pickerContext.setOpen((prev) => !prev)}> diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx index 68f5f4c21737..bcb591ba663a 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx @@ -77,14 +77,12 @@ const BrowserSingleInputDateRangeField = React.forwardRef( focused, error, - InputProps: { ref: InputPropsRef, startAdornment, endAdornment } = {}, - // The rest can be passed to the root element ...other } = fieldResponse; const pickerContext = usePickerContext(); - const handleRef = useForkRef(InputPropsRef, ref); + const handleRef = useForkRef(pickerContext.triggerRef, ref); return ( - {startAdornment} - {endAdornment} pickerContext.setOpen((prev) => !prev)}> diff --git a/docs/data/date-pickers/custom-field/JoyV6Field.js b/docs/data/date-pickers/custom-field/JoyV6Field.js index 156d8200c2f5..d2ec2bfafc74 100644 --- a/docs/data/date-pickers/custom-field/JoyV6Field.js +++ b/docs/data/date-pickers/custom-field/JoyV6Field.js @@ -11,12 +11,20 @@ import { THEME_ID, } from '@mui/joy/styles'; import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; import FormControl from '@mui/joy/FormControl'; import FormLabel from '@mui/joy/FormLabel'; +import { createSvgIcon } from '@mui/joy/utils'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; +import { usePickerContext } from '@mui/x-date-pickers/hooks'; + +const CalendarIcon = createSvgIcon( + , + 'Calendar', +); const joyTheme = extendJoyTheme(); @@ -29,27 +37,42 @@ const JoyDateField = React.forwardRef((props, ref) => { const { // Should be ignored enableAccessibleFieldDOMStructure, + // Should be passed to the button that opens the picker + openPickerAriaLabel, // Can be passed to the button that clears the value onClear, clearable, - disabled, - id, + // Can be used to render a custom label label, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, + // Can be used to style the component + disabled, + readOnly, + focused, + error, inputRef, - slots, + // The rest can be passed to the root element slotProps, + slots, + id, ...other } = fieldResponse; + const pickerContext = usePickerContext(); + return ( {label} pickerContext.setOpen((prev) => !prev)} + aria-label={openPickerAriaLabel} + > + + + } slotProps={{ input: { ref: inputRef }, }} diff --git a/docs/data/date-pickers/custom-field/JoyV6Field.tsx b/docs/data/date-pickers/custom-field/JoyV6Field.tsx index 9e30ca9e0b71..6f4a44d7ecd6 100644 --- a/docs/data/date-pickers/custom-field/JoyV6Field.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6Field.tsx @@ -11,8 +11,10 @@ import { THEME_ID, } from '@mui/joy/styles'; import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; import FormControl from '@mui/joy/FormControl'; import FormLabel from '@mui/joy/FormLabel'; +import { createSvgIcon } from '@mui/joy/utils'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { @@ -21,6 +23,12 @@ import { DatePickerProps, } from '@mui/x-date-pickers/DatePicker'; import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; +import { usePickerContext } from '@mui/x-date-pickers/hooks'; + +const CalendarIcon = createSvgIcon( + , + 'Calendar', +); const joyTheme = extendJoyTheme(); @@ -35,28 +43,46 @@ const JoyDateField = React.forwardRef( // Should be ignored enableAccessibleFieldDOMStructure, + // Should be passed to the button that opens the picker + openPickerAriaLabel, + // Can be passed to the button that clears the value onClear, clearable, - disabled, - id, + // Can be used to render a custom label label, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, + + // Can be used to style the component + disabled, + readOnly, + focused, + error, inputRef, - slots, + + // The rest can be passed to the root element slotProps, + slots, + id, ...other } = fieldResponse; + const pickerContext = usePickerContext(); + return ( {label} pickerContext.setOpen((prev) => !prev)} + aria-label={openPickerAriaLabel} + > + + + } slotProps={{ input: { ref: inputRef }, }} diff --git a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js index 61a8a5d231f7..0f9bbc0edfba 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js @@ -124,13 +124,13 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { const startTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: {}, }); const endTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: {}, }); const fieldResponse = useMultiInputDateRangeField({ diff --git a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx index f4c28e574437..f84c5d9623e0 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx @@ -162,13 +162,13 @@ const JoyMultiInputDateRangeField = React.forwardRef( const startTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: {} as any, }) as MultiInputFieldSlotTextFieldProps; const endTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: {} as any, }) as MultiInputFieldSlotTextFieldProps; const fieldResponse = useMultiInputDateRangeField< diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js index 69123038f850..c332292bca70 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js @@ -19,7 +19,9 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -export const DateRangeIcon = createSvgIcon( +import { usePickerContext } from '@mui/x-date-pickers/hooks'; + +const DateRangeIcon = createSvgIcon( , 'DateRange', ); @@ -41,13 +43,14 @@ const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { disabled, id, label, - InputProps: { ref: containerRef } = {}, inputRef, slots, slotProps, ...other } = fieldResponse; + const pickerContext = usePickerContext(); + return ( { > {label} } slotProps={{ diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx index 822610feac08..b973d37c1be5 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx @@ -23,8 +23,9 @@ import { } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; import { FieldType } from '@mui/x-date-pickers-pro/models'; +import { usePickerContext } from '@mui/x-date-pickers/hooks'; -export const DateRangeIcon = createSvgIcon( +const DateRangeIcon = createSvgIcon( , 'DateRange', ); @@ -53,13 +54,14 @@ const JoySingleInputDateRangeField = React.forwardRef( disabled, id, label, - InputProps: { ref: containerRef } = {}, inputRef, slots, slotProps, ...other } = fieldResponse; + const pickerContext = usePickerContext(); + return ( {label} } slotProps={{ diff --git a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js index 2856b3af1a2d..2eab5b1e68a3 100644 --- a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js @@ -1,19 +1,19 @@ import * as React from 'react'; import dayjs from 'dayjs'; import Autocomplete from '@mui/material/Autocomplete'; +import IconButton from '@mui/material/IconButton'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; import TextField from '@mui/material/TextField'; -import Stack from '@mui/material/Stack'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; +import { usePickerContext, useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; function AutocompleteField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, onChange } = internalProps; const { - InputProps, slotProps, slots, ownerState, @@ -21,10 +21,11 @@ function AutocompleteField(props) { focused, name, options = [], - inputProps, ...other } = forwardedProps; + const pickerContext = usePickerContext(); + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, @@ -32,50 +33,38 @@ function AutocompleteField(props) { props: internalProps, }); - const mergeAdornments = (...adornments) => { - const nonNullAdornments = adornments.filter((el) => el != null); - if (nonNullAdornments.length === 0) { - return null; - } - - if (nonNullAdornments.length === 1) { - return nonNullAdornments[0]; - } - - return ( - - {nonNullAdornments.map((adornment, index) => ( - {adornment} - ))} - - ); - }; - return ( ( - - )} + renderInput={(params) => { + const endAdornment = params.InputProps.endAdornment; + return ( + + pickerContext.setOpen((prev) => !prev)} + size="small" + > + + + {endAdornment.props.children} + + ), + }), + }} + /> + ); + }} getOptionLabel={(option) => { if (!dayjs.isDayjs(option)) { return ''; diff --git a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx index 7633f42d7199..740dece3ade3 100644 --- a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import dayjs, { Dayjs } from 'dayjs'; import Autocomplete from '@mui/material/Autocomplete'; +import IconButton from '@mui/material/IconButton'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; import TextField from '@mui/material/TextField'; -import Stack from '@mui/material/Stack'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { @@ -10,7 +11,7 @@ import { DatePickerFieldProps, DatePickerProps, } from '@mui/x-date-pickers/DatePicker'; -import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; +import { usePickerContext, useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; interface AutocompleteFieldProps extends DatePickerFieldProps { @@ -24,7 +25,6 @@ function AutocompleteField(props: AutocompleteFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, onChange } = internalProps; const { - InputProps, slotProps, slots, ownerState, @@ -32,10 +32,11 @@ function AutocompleteField(props: AutocompleteFieldProps) { focused, name, options = [], - inputProps, ...other } = forwardedProps; + const pickerContext = usePickerContext(); + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, @@ -43,50 +44,38 @@ function AutocompleteField(props: AutocompleteFieldProps) { props: internalProps, }); - const mergeAdornments = (...adornments: React.ReactNode[]) => { - const nonNullAdornments = adornments.filter((el) => el != null); - if (nonNullAdornments.length === 0) { - return null; - } - - if (nonNullAdornments.length === 1) { - return nonNullAdornments[0]; - } - - return ( - - {nonNullAdornments.map((adornment, index) => ( - {adornment} - ))} - - ); - }; - return ( ( - - )} + renderInput={(params) => { + const endAdornment = params.InputProps.endAdornment as React.ReactElement; + return ( + + pickerContext.setOpen((prev) => !prev)} + size="small" + > + + + {endAdornment.props.children} + + ), + }), + }} + /> + ); + }} getOptionLabel={(option) => { if (!dayjs.isDayjs(option)) { return ''; diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js index 0e30e8d2605a..97f69cfb61a0 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js @@ -13,16 +13,8 @@ import { function ButtonDateField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -41,7 +33,7 @@ function ButtonDateField(props) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx index 1da1b675decd..79126dce5e89 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx @@ -17,16 +17,8 @@ import { function ButtonDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -45,7 +37,7 @@ function ButtonDateField(props: DatePickerFieldProps) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js index 0884deadb8b4..fbed88519b25 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js @@ -15,16 +15,8 @@ import { function ButtonDateRangeField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -45,7 +37,7 @@ function ButtonDateRangeField(props) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx index 9361a51c256a..caeb3e0a25f7 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx @@ -19,16 +19,8 @@ import { function ButtonDateRangeField(props: DateRangePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; const pickerContext = usePickerContext(); @@ -49,7 +41,7 @@ function ButtonDateRangeField(props: DateRangePickerFieldProps) { {...other} variant="outlined" color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js index 107fb273f496..fdb3dc2151b5 100644 --- a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js +++ b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.js @@ -2,11 +2,18 @@ import * as React from 'react'; import dayjs from 'dayjs'; import { useRifm } from 'rifm'; import TextField from '@mui/material/TextField'; +import InputAdornment from '@mui/material/InputAdornment'; +import IconButton from '@mui/material/IconButton'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; const MASK_USER_INPUT_SYMBOL = '_'; const ACCEPT_REGEX = /[\d]/gi; @@ -42,6 +49,7 @@ function MaskedDateField(props) { }, [format, value]); const parsedFormat = useParsedFormat(internalProps); + const pickerContext = usePickerContext(); const { hasValidationError, getValidationErrorForNewValue } = useValidation({ value, @@ -127,6 +135,19 @@ function MaskedDateField(props) { error={!!hasValidationError} {...rifmProps} {...forwardedProps} + InputProps={{ + ref: pickerContext.triggerRef, + endAdornment: ( + + pickerContext.setOpen((prev) => !prev)} + edge="end" + > + + + + ), + }} /> ); } diff --git a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx index aa7b8023fcaf..b9241d9caf2b 100644 --- a/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx +++ b/docs/data/date-pickers/custom-field/behavior-masked-text-field/MaskedMaterialTextField.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import dayjs, { Dayjs } from 'dayjs'; import { useRifm } from 'rifm'; import TextField from '@mui/material/TextField'; +import InputAdornment from '@mui/material/InputAdornment'; +import IconButton from '@mui/material/IconButton'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { @@ -9,8 +11,13 @@ import { DatePickerProps, DatePickerFieldProps, } from '@mui/x-date-pickers/DatePicker'; -import { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; const MASK_USER_INPUT_SYMBOL = '_'; const ACCEPT_REGEX = /[\d]/gi; @@ -46,6 +53,7 @@ function MaskedDateField(props: DatePickerFieldProps) { }, [format, value]); const parsedFormat = useParsedFormat(internalProps); + const pickerContext = usePickerContext(); const { hasValidationError, getValidationErrorForNewValue } = useValidation({ value, @@ -131,6 +139,19 @@ function MaskedDateField(props: DatePickerFieldProps) { error={!!hasValidationError} {...rifmProps} {...forwardedProps} + InputProps={{ + ref: pickerContext.triggerRef, + endAdornment: ( + + pickerContext.setOpen((prev) => !prev)} + edge="end" + > + + + + ), + }} /> ); } diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.js new file mode 100644 index 000000000000..5510187e9b85 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.js @@ -0,0 +1,72 @@ +import * as React from 'react'; +import TextField from '@mui/material/TextField'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; +import { DateField } from '@mui/x-date-pickers/DateField'; + +function ReadOnlyDateField(props) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { slotProps, slots, ...other } = forwardedProps; + + const pickerContext = usePickerContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + return ( + , + sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, + }} + error={hasValidationError} + onClick={() => pickerContext.setOpen((prev) => !prev)} + /> + ); +} + +function ReadOnlyOnMobileDateField(props) { + const pickerContext = usePickerContext(); + + if (pickerContext.variant === 'mobile') { + return ; + } + + return ; +} + +function ReadOnlyFieldDatePicker(props) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx new file mode 100644 index 000000000000..071ff1af803d --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx @@ -0,0 +1,76 @@ +import * as React from 'react'; +import TextField from '@mui/material/TextField'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DatePicker, + DatePickerProps, + DatePickerFieldProps, +} from '@mui/x-date-pickers/DatePicker'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; +import { DateField } from '@mui/x-date-pickers/DateField'; + +function ReadOnlyDateField(props: DatePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { slotProps, slots, ...other } = forwardedProps; + + const pickerContext = usePickerContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + return ( + , + sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, + }} + error={hasValidationError} + onClick={() => pickerContext.setOpen((prev) => !prev)} + /> + ); +} + +function ReadOnlyOnMobileDateField(props: DatePickerFieldProps) { + const pickerContext = usePickerContext(); + + if (pickerContext.variant === 'mobile') { + return ; + } + + return ; +} + +function ReadOnlyFieldDatePicker(props: DatePickerProps) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx.preview b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx.preview new file mode 100644 index 000000000000..e3842a12cb5d --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js index c5637b320e0e..16bd9f9bf4cb 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js @@ -14,7 +14,7 @@ import { CalendarIcon } from '@mui/x-date-pickers/icons'; function ReadOnlyDateField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { InputProps, slotProps, slots, ...other } = forwardedProps; + const { slotProps, slots, ...other } = forwardedProps; const pickerContext = usePickerContext(); @@ -32,7 +32,7 @@ function ReadOnlyDateField(props) { value={value == null ? '' : value.format(format)} placeholder={parsedFormat} InputProps={{ - ...InputProps, + ref: pickerContext.triggerRef, readOnly: true, endAdornment: , sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx index a42da5e10407..127876f1a2e3 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx @@ -18,7 +18,7 @@ import { CalendarIcon } from '@mui/x-date-pickers/icons'; function ReadOnlyDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { InputProps, slotProps, slots, ...other } = forwardedProps; + const { slotProps, slots, ...other } = forwardedProps; const pickerContext = usePickerContext(); @@ -36,7 +36,7 @@ function ReadOnlyDateField(props: DatePickerFieldProps) { value={value == null ? '' : value.format(format)} placeholder={parsedFormat} InputProps={{ - ...InputProps, + ref: pickerContext.triggerRef, readOnly: true, endAdornment: , sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index e30850de8502..aed45e606143 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -144,6 +144,12 @@ but you still want the UI to look like a Text Field, you can replace the field w {{"demo": "behavior-read-only-text-field/MaterialDatePicker.js", "defaultCodeOpen": false}} +### Using a read-only Text Field on mobile + +If you want to keep the default behavior on desktop but you want to have a read-only TextField on mobile, you can conditionally render the custom field presented in the previous doc section and the default Date Field: + +{{"demo": "behavior-read-only-mobile-text-field/MaterialDatePicker.js", "defaultCodeOpen": false}} + ### Using a Button If you want users to select a value exclusively through the views diff --git a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js index 5ecabf3b7b20..5331d6123024 100644 --- a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js +++ b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js @@ -16,7 +16,7 @@ export default function CustomPropsOpeningButton() { }, // Targets the `InputAdornment` component. inputAdornment: { - position: 'start', + component: 'span', }, }} /> diff --git a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx index 5ecabf3b7b20..5331d6123024 100644 --- a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx +++ b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx @@ -16,7 +16,7 @@ export default function CustomPropsOpeningButton() { }, // Targets the `InputAdornment` component. inputAdornment: { - position: 'start', + component: 'span', }, }} /> diff --git a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx.preview b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx.preview index 685e218097c6..1487b57d2a90 100644 --- a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx.preview +++ b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.tsx.preview @@ -6,7 +6,7 @@ }, // Targets the `InputAdornment` component. inputAdornment: { - position: 'start', + component: 'span', }, }} /> \ No newline at end of file diff --git a/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.js b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.js new file mode 100644 index 000000000000..04a1a2803acd --- /dev/null +++ b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function StartEdgeOpeningButton() { + return ( + + + + + + ); +} diff --git a/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx new file mode 100644 index 000000000000..04a1a2803acd --- /dev/null +++ b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function StartEdgeOpeningButton() { + return ( + + + + + + ); +} diff --git a/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx.preview b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx.preview new file mode 100644 index 000000000000..0a9731df02dd --- /dev/null +++ b/docs/data/date-pickers/custom-opening-button/StartEdgeOpeningButton.tsx.preview @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md index c0ef70d69eb9..bf7f520ee36b 100644 --- a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md +++ b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md @@ -39,6 +39,12 @@ If you want to track the opening of the picker, you should use the `onOpen` / `o ::: +## Render the opening button at the start of the input + +You can use the `openPickerButtonPosition` on the `field` slot to set the opening button at the start of the input (on the left in lef-to-right direction, on the right in left-to-right direction): + +{{"demo": "StartEdgeOpeningButton.js"}} + ## Add an icon next to the opening button If you want to add an icon next to the opening button, you can use the `inputAdornment` slot. diff --git a/docs/data/date-pickers/date-picker/date-picker.md b/docs/data/date-pickers/date-picker/date-picker.md index 846716836361..f702279ebb37 100644 --- a/docs/data/date-pickers/date-picker/date-picker.md +++ b/docs/data/date-pickers/date-picker/date-picker.md @@ -46,10 +46,10 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen The component is available in four variants: - The `DesktopDatePicker` component which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The `MobileDatePicker` component which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. - The `DatePicker` component which renders `DesktopDatePicker` or `MobileDatePicker` depending on the device it runs on. @@ -125,12 +125,6 @@ You can enable the clearable behavior: See [Field components—Clearable behavior](/x/react-date-pickers/fields/#clearable-behavior) for more details. ::: -:::warning -The clearable prop is not supported yet by the mobile Picker variants. - -See discussion [in this GitHub issue](https://github.com/mui/mui-x/issues/10842#issuecomment-1951887408) for more information. -::: - ## Localization See the [Date format and localization](/x/react-date-pickers/adapters-locale/) and [Translated components](/x/react-date-pickers/localization/) documentation pages for more details. diff --git a/docs/data/date-pickers/date-range-picker/date-range-picker.md b/docs/data/date-pickers/date-range-picker/date-range-picker.md index 43e680df8d33..aae228d483b8 100644 --- a/docs/data/date-pickers/date-range-picker/date-range-picker.md +++ b/docs/data/date-pickers/date-range-picker/date-range-picker.md @@ -46,10 +46,10 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen The component is available in four variants: - The `DesktopDateRangePicker` component which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The `MobileDateRangePicker` component which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. - The `DateRangePicker` component which renders `DesktopDateRangePicker` or `MobileDateRangePicker` depending on the device it runs on. diff --git a/docs/data/date-pickers/date-time-picker/date-time-picker.md b/docs/data/date-pickers/date-time-picker/date-time-picker.md index 075ec25dd156..dcead399e2fc 100644 --- a/docs/data/date-pickers/date-time-picker/date-time-picker.md +++ b/docs/data/date-pickers/date-time-picker/date-time-picker.md @@ -48,10 +48,10 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen The component is available in four variants: - The `DesktopDateTimePicker` component which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The `MobileDateTimePicker` component which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. - The `DateTimePicker` component which renders `DesktopDateTimePicker` or `MobileDateTimePicker` depending on the device it runs on. diff --git a/docs/data/date-pickers/date-time-range-picker/date-time-range-picker.md b/docs/data/date-pickers/date-time-range-picker/date-time-range-picker.md index 92137b72ba81..9bd2cb3da4a4 100644 --- a/docs/data/date-pickers/date-time-range-picker/date-time-range-picker.md +++ b/docs/data/date-pickers/date-time-range-picker/date-time-range-picker.md @@ -47,10 +47,10 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen The component is available in three variants: - The `DesktopDateTimeRangePicker` component which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The `MobileDateTimeRangePicker` component which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. - The `DateTimeRangePicker` component which renders `DesktopDateTimeRangePicker` or `MobileDateTimeRangePicker` depending on the device it runs on. diff --git a/docs/data/date-pickers/time-picker/time-picker.md b/docs/data/date-pickers/time-picker/time-picker.md index d6db6b9d53c9..333237139368 100644 --- a/docs/data/date-pickers/time-picker/time-picker.md +++ b/docs/data/date-pickers/time-picker/time-picker.md @@ -47,10 +47,10 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen The component is available in four variants: - The `DesktopTimePicker` component which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The `MobileTimePicker` component which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. - The `TimePicker` component which renders `DesktopTimePicker` or `MobileTimePicker` depending on the device it runs on. diff --git a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md index e442c6924293..a4fbc0a264c9 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -260,8 +260,112 @@ const theme = createTheme({ }); ``` +## Enable field editing on mobile + +The field are now editable when used inside a mobile picker. +Before v8, when rendered inside a mobile picker, the field were read-only and clicking anywhere on them would open the piker. +The behavior is now the same as inside a desktop picker: + +- clicking on the field value allows to edit it +- clicking on the field end adornment opens the picker + +:::success +If you prefer the old behavior, you can create a custom field that renders a read-only Text Field on mobile. +See [Custom field—Using a read-only Text Field on mobile](/x/react-date-pickers/custom-field/#using-a-read-only-text-field-on-mobile) to learn more. +::: + ## Slots breaking changes +### Slot: `field` + +- The component passed to the `field` slot no longer receives `InputProps` and `inputProps` props. You now need to manually add the UI to open the picker using the `usePickerContext` hook: + + ```diff + import { unstable_useDateField } from '@mui/x-date-pickers/DateField'; + +import { usePickerContext } from '@mui/x-date-pickers/hooks'; + + function CustomField(props) { + + const pickerContext = usePickerContext(); + + return ( + + + pickerContext.setOpen((prev) => !prev)} + + edge="end" + + > + + + + + + + + ), + + }} + /> + ); + } + ``` + + If you are extracting the `ref` from `InputProps` to pass it to another trigger component, you can replace it with `pickerContext.triggerRef`: + + ```diff + +import { usePickerContext } from '@mui/x-date-pickers/hooks'; + + function CustomField(props) { + + const pickerContext = usePickerContext(); + + return ( + + ); + } + ``` + + If you are using a custom editing behavior, instead of using the `openPickerAriaLabel` property returned by the `useXXXField` hooks, you can generate it manually: + + ```diff + +import { usePickerTranslations } from '@mui/x-date-pickers/hooks'; + + function CustomField(props) { + + const translations = usePickerTranslations(); + + const formattedValue = props.value?.isValid() ? value.format('ll') : null; + + const ariaLabel = translations.openDatePickerDialogue(formattedValue); + + return ( + + ); + } + ``` + +### Slot: `inputAdornment` + +- The `position` props passed to the `inputAdornment` slot props no longer sets the position of the opening button. This allow to define the position of the opening button and of the clear button independently. Instead you can use the `openPickerButtonPosition` prop: + + ```diff + + ``` + ### Slot: `layout` - The `` and `` components must now receive the `ownerState` returned by `usePickerLayout` instead of their props: diff --git a/docs/pages/x/api/date-pickers/date-field.json b/docs/pages/x/api/date-pickers/date-field.json index 6a53307f3dc3..90efc47fd660 100644 --- a/docs/pages/x/api/date-pickers/date-field.json +++ b/docs/pages/x/api/date-pickers/date-field.json @@ -2,6 +2,10 @@ "props": { "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", @@ -61,6 +65,10 @@ "describedArgs": ["newValue"] } }, + "openPickerButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "readOnly": { "type": { "name": "bool" }, "default": "false" }, "referenceDate": { "type": { "name": "object" }, diff --git a/docs/pages/x/api/date-pickers/date-time-field.json b/docs/pages/x/api/date-pickers/date-time-field.json index 8ddb6b01c5cc..cadb7130bc60 100644 --- a/docs/pages/x/api/date-pickers/date-time-field.json +++ b/docs/pages/x/api/date-pickers/date-time-field.json @@ -3,6 +3,10 @@ "ampm": { "type": { "name": "bool" }, "default": "utils.is12HourCycleInCurrentLocale()" }, "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", @@ -68,6 +72,10 @@ "describedArgs": ["newValue"] } }, + "openPickerButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "readOnly": { "type": { "name": "bool" }, "default": "false" }, "referenceDate": { "type": { "name": "object" }, diff --git a/docs/pages/x/api/date-pickers/mobile-date-picker.json b/docs/pages/x/api/date-pickers/mobile-date-picker.json index 37e3b9e9be0b..1b489748bf04 100644 --- a/docs/pages/x/api/date-pickers/mobile-date-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-date-picker.json @@ -207,6 +207,18 @@ "default": "PickersCalendarHeader", "class": null }, + { + "name": "clearButton", + "description": "Button to clear the value.", + "default": "IconButton", + "class": null + }, + { + "name": "clearIcon", + "description": "Icon to display inside the clear button.", + "default": "ClearIcon", + "class": null + }, { "name": "day", "description": "Custom component for day.\nCheck the [PickersDay](https://mui.com/x/api/date-pickers/pickers-day/) component.", @@ -224,6 +236,12 @@ "description": "Component used to enter the date with the keyboard.", "class": null }, + { + "name": "inputAdornment", + "description": "Component displayed on the start or end input adornment used to open the picker on desktop.", + "default": "InputAdornment", + "class": null + }, { "name": "layout", "description": "Custom component for wrapping the layout.\nIt wraps the toolbar, views, action bar, and shortcuts.", @@ -259,6 +277,17 @@ "default": "IconButton", "class": null }, + { + "name": "openPickerButton", + "description": "Button to open the picker on desktop.", + "default": "IconButton", + "class": null + }, + { + "name": "openPickerIcon", + "description": "Icon displayed in the open picker button on desktop.", + "class": null + }, { "name": "previousIconButton", "description": "Button allowing to switch to the left view.", @@ -291,8 +320,8 @@ }, { "name": "textField", - "description": "Form control with an input to render the value inside the default field.", - "default": "TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`.", + "description": "Form control with an input to render the value.", + "default": ", or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`.", "class": null }, { diff --git a/docs/pages/x/api/date-pickers/mobile-date-time-picker.json b/docs/pages/x/api/date-pickers/mobile-date-time-picker.json index 23adb960d841..8fe6f17b6f01 100644 --- a/docs/pages/x/api/date-pickers/mobile-date-time-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-date-time-picker.json @@ -226,6 +226,18 @@ "default": "PickersCalendarHeader", "class": null }, + { + "name": "clearButton", + "description": "Button to clear the value.", + "default": "IconButton", + "class": null + }, + { + "name": "clearIcon", + "description": "Icon to display inside the clear button.", + "default": "ClearIcon", + "class": null + }, { "name": "day", "description": "Custom component for day.\nCheck the [PickersDay](https://mui.com/x/api/date-pickers/pickers-day/) component.", @@ -243,6 +255,12 @@ "description": "Component used to enter the date with the keyboard.", "class": null }, + { + "name": "inputAdornment", + "description": "Component displayed on the start or end input adornment used to open the picker on desktop.", + "default": "InputAdornment", + "class": null + }, { "name": "layout", "description": "Custom component for wrapping the layout.\nIt wraps the toolbar, views, action bar, and shortcuts.", @@ -278,6 +296,17 @@ "default": "IconButton", "class": null }, + { + "name": "openPickerButton", + "description": "Button to open the picker on desktop.", + "default": "IconButton", + "class": null + }, + { + "name": "openPickerIcon", + "description": "Icon displayed in the open picker button on desktop.", + "class": null + }, { "name": "previousIconButton", "description": "Button allowing to switch to the left view.", @@ -316,8 +345,8 @@ }, { "name": "textField", - "description": "Form control with an input to render the value inside the default field.", - "default": "TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`.", + "description": "Form control with an input to render the value.", + "default": ", or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`.", "class": null }, { diff --git a/docs/pages/x/api/date-pickers/mobile-time-picker.json b/docs/pages/x/api/date-pickers/mobile-time-picker.json index c3898c0d4a92..17c84e66f763 100644 --- a/docs/pages/x/api/date-pickers/mobile-time-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-time-picker.json @@ -143,6 +143,18 @@ "default": "PickersActionBar", "class": null }, + { + "name": "clearButton", + "description": "Button to clear the value.", + "default": "IconButton", + "class": null + }, + { + "name": "clearIcon", + "description": "Icon to display inside the clear button.", + "default": "ClearIcon", + "class": null + }, { "name": "dialog", "description": "Custom component for the dialog inside which the views are rendered on mobile.", @@ -154,6 +166,12 @@ "description": "Component used to enter the date with the keyboard.", "class": null }, + { + "name": "inputAdornment", + "description": "Component displayed on the start or end input adornment used to open the picker on desktop.", + "default": "InputAdornment", + "class": null + }, { "name": "layout", "description": "Custom component for wrapping the layout.\nIt wraps the toolbar, views, action bar, and shortcuts.", @@ -183,6 +201,17 @@ "default": "IconButton", "class": null }, + { + "name": "openPickerButton", + "description": "Button to open the picker on desktop.", + "default": "IconButton", + "class": null + }, + { + "name": "openPickerIcon", + "description": "Icon displayed in the open picker button on desktop.", + "class": null + }, { "name": "previousIconButton", "description": "Button allowing to switch to the left view.", @@ -203,8 +232,8 @@ }, { "name": "textField", - "description": "Form control with an input to render the value inside the default field.", - "default": "TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`.", + "description": "Form control with an input to render the value.", + "default": ", or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`.", "class": null }, { diff --git a/docs/pages/x/api/date-pickers/single-input-date-range-field.json b/docs/pages/x/api/date-pickers/single-input-date-range-field.json index 179b58765222..b12c8e6071cd 100644 --- a/docs/pages/x/api/date-pickers/single-input-date-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-date-range-field.json @@ -2,6 +2,10 @@ "props": { "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", diff --git a/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json b/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json index 483f9c001649..119936cbddf1 100644 --- a/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json @@ -3,6 +3,10 @@ "ampm": { "type": { "name": "bool" }, "default": "utils.is12HourCycleInCurrentLocale()" }, "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", diff --git a/docs/pages/x/api/date-pickers/single-input-time-range-field.json b/docs/pages/x/api/date-pickers/single-input-time-range-field.json index fbfcdcd2781e..ceeded9aa680 100644 --- a/docs/pages/x/api/date-pickers/single-input-time-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-time-range-field.json @@ -3,6 +3,10 @@ "ampm": { "type": { "name": "bool" }, "default": "utils.is12HourCycleInCurrentLocale()" }, "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", diff --git a/docs/pages/x/api/date-pickers/time-field.json b/docs/pages/x/api/date-pickers/time-field.json index fb99ace144ee..3bb471d4c5f0 100644 --- a/docs/pages/x/api/date-pickers/time-field.json +++ b/docs/pages/x/api/date-pickers/time-field.json @@ -3,6 +3,10 @@ "ampm": { "type": { "name": "bool" }, "default": "utils.is12HourCycleInCurrentLocale()" }, "autoFocus": { "type": { "name": "bool" }, "default": "false" }, "clearable": { "type": { "name": "bool" }, "default": "false" }, + "clearButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "color": { "type": { "name": "enum", @@ -64,6 +68,10 @@ "describedArgs": ["newValue"] } }, + "openPickerButtonPosition": { + "type": { "name": "enum", "description": "'end'
| 'start'" }, + "default": "'end'" + }, "readOnly": { "type": { "name": "bool" }, "default": "false" }, "referenceDate": { "type": { "name": "object" }, diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index fb531c0e5204..36e289e2b8bd 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -10,8 +10,7 @@ import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; function ButtonDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; - const { InputProps, slotProps, slots, ownerState, label, focused, name, ...other } = - forwardedProps; + const { slotProps, slots, ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); @@ -34,7 +33,7 @@ function ButtonDateField(props: DatePickerFieldProps) { sx={{ minWidth: 'fit-content' }} fullWidth color={hasValidationError ? 'error' : 'primary'} - ref={InputProps?.ref} + ref={pickerContext.triggerRef} onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/translations/api-docs/date-pickers/date-field/date-field.json b/docs/translations/api-docs/date-pickers/date-field/date-field.json index 08b247cab667..6022893a1b8c 100644 --- a/docs/translations/api-docs/date-pickers/date-field/date-field.json +++ b/docs/translations/api-docs/date-pickers/date-field/date-field.json @@ -7,6 +7,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, @@ -78,6 +81,9 @@ "description": "Callback fired when the selected sections change.", "typeDescriptions": { "newValue": "The new selected sections." } }, + "openPickerButtonPosition": { + "description": "The position at which the opening button is placed. If there is no picker to open, the button is not rendered" + }, "readOnly": { "description": "If true, the component is read-only. When read-only, the value cannot be changed but the user can interact with the interface." }, diff --git a/docs/translations/api-docs/date-pickers/date-time-field/date-time-field.json b/docs/translations/api-docs/date-pickers/date-time-field/date-time-field.json index 30b020a97b08..3a6bc00dd54f 100644 --- a/docs/translations/api-docs/date-pickers/date-time-field/date-time-field.json +++ b/docs/translations/api-docs/date-pickers/date-time-field/date-time-field.json @@ -8,6 +8,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, @@ -95,6 +98,9 @@ "description": "Callback fired when the selected sections change.", "typeDescriptions": { "newValue": "The new selected sections." } }, + "openPickerButtonPosition": { + "description": "The position at which the opening button is placed. If there is no picker to open, the button is not rendered" + }, "readOnly": { "description": "If true, the component is read-only. When read-only, the value cannot be changed but the user can interact with the interface." }, diff --git a/docs/translations/api-docs/date-pickers/mobile-date-picker/mobile-date-picker.json b/docs/translations/api-docs/date-pickers/mobile-date-picker/mobile-date-picker.json index de8638284e24..f3d7f4877503 100644 --- a/docs/translations/api-docs/date-pickers/mobile-date-picker/mobile-date-picker.json +++ b/docs/translations/api-docs/date-pickers/mobile-date-picker/mobile-date-picker.json @@ -170,21 +170,26 @@ "slotDescriptions": { "actionBar": "Custom component for the action bar, it is placed below the picker views.", "calendarHeader": "Custom component for calendar header. Check the PickersCalendarHeader component.", + "clearButton": "Button to clear the value.", + "clearIcon": "Icon to display inside the clear button.", "day": "Custom component for day. Check the PickersDay component.", "dialog": "Custom component for the dialog inside which the views are rendered on mobile.", "field": "Component used to enter the date with the keyboard.", + "inputAdornment": "Component displayed on the start or end input adornment used to open the picker on desktop.", "layout": "Custom component for wrapping the layout. It wraps the toolbar, views, action bar, and shortcuts.", "leftArrowIcon": "Icon displayed in the left view switch button.", "mobilePaper": "Custom component for the paper rendered inside the mobile picker's Dialog.", "mobileTransition": "Custom component for the mobile dialog Transition.", "monthButton": "Button displayed to render a single month in the month view.", "nextIconButton": "Button allowing to switch to the right view.", + "openPickerButton": "Button to open the picker on desktop.", + "openPickerIcon": "Icon displayed in the open picker button on desktop.", "previousIconButton": "Button allowing to switch to the left view.", "rightArrowIcon": "Icon displayed in the right view switch button.", "shortcuts": "Custom component for the shortcuts.", "switchViewButton": "Button displayed to switch between different calendar views.", "switchViewIcon": "Icon displayed in the SwitchViewButton. Rotated by 180° when the open view is year.", - "textField": "Form control with an input to render the value inside the default field.", + "textField": "Form control with an input to render the value.", "toolbar": "Custom component for the toolbar rendered above the views.", "yearButton": "Button displayed to render a single year in the year view." } diff --git a/docs/translations/api-docs/date-pickers/mobile-date-time-picker/mobile-date-time-picker.json b/docs/translations/api-docs/date-pickers/mobile-date-time-picker/mobile-date-time-picker.json index 43e3030a0a43..ade4cedfddf9 100644 --- a/docs/translations/api-docs/date-pickers/mobile-date-time-picker/mobile-date-time-picker.json +++ b/docs/translations/api-docs/date-pickers/mobile-date-time-picker/mobile-date-time-picker.json @@ -198,22 +198,27 @@ "slotDescriptions": { "actionBar": "Custom component for the action bar, it is placed below the picker views.", "calendarHeader": "Custom component for calendar header. Check the PickersCalendarHeader component.", + "clearButton": "Button to clear the value.", + "clearIcon": "Icon to display inside the clear button.", "day": "Custom component for day. Check the PickersDay component.", "dialog": "Custom component for the dialog inside which the views are rendered on mobile.", "field": "Component used to enter the date with the keyboard.", + "inputAdornment": "Component displayed on the start or end input adornment used to open the picker on desktop.", "layout": "Custom component for wrapping the layout. It wraps the toolbar, views, action bar, and shortcuts.", "leftArrowIcon": "Icon displayed in the left view switch button.", "mobilePaper": "Custom component for the paper rendered inside the mobile picker's Dialog.", "mobileTransition": "Custom component for the mobile dialog Transition.", "monthButton": "Button displayed to render a single month in the month view.", "nextIconButton": "Button allowing to switch to the right view.", + "openPickerButton": "Button to open the picker on desktop.", + "openPickerIcon": "Icon displayed in the open picker button on desktop.", "previousIconButton": "Button allowing to switch to the left view.", "rightArrowIcon": "Icon displayed in the right view switch button.", "shortcuts": "Custom component for the shortcuts.", "switchViewButton": "Button displayed to switch between different calendar views.", "switchViewIcon": "Icon displayed in the SwitchViewButton. Rotated by 180° when the open view is year.", "tabs": "Tabs enabling toggling between date and time pickers.", - "textField": "Form control with an input to render the value inside the default field.", + "textField": "Form control with an input to render the value.", "toolbar": "Custom component for the toolbar rendered above the views.", "yearButton": "Button displayed to render a single year in the year view." } diff --git a/docs/translations/api-docs/date-pickers/mobile-time-picker/mobile-time-picker.json b/docs/translations/api-docs/date-pickers/mobile-time-picker/mobile-time-picker.json index 2f933308ab83..1f16a1e9aa05 100644 --- a/docs/translations/api-docs/date-pickers/mobile-time-picker/mobile-time-picker.json +++ b/docs/translations/api-docs/date-pickers/mobile-time-picker/mobile-time-picker.json @@ -129,17 +129,22 @@ "classDescriptions": {}, "slotDescriptions": { "actionBar": "Custom component for the action bar, it is placed below the picker views.", + "clearButton": "Button to clear the value.", + "clearIcon": "Icon to display inside the clear button.", "dialog": "Custom component for the dialog inside which the views are rendered on mobile.", "field": "Component used to enter the date with the keyboard.", + "inputAdornment": "Component displayed on the start or end input adornment used to open the picker on desktop.", "layout": "Custom component for wrapping the layout. It wraps the toolbar, views, action bar, and shortcuts.", "leftArrowIcon": "Icon displayed in the left view switch button.", "mobilePaper": "Custom component for the paper rendered inside the mobile picker's Dialog.", "mobileTransition": "Custom component for the mobile dialog Transition.", "nextIconButton": "Button allowing to switch to the right view.", + "openPickerButton": "Button to open the picker on desktop.", + "openPickerIcon": "Icon displayed in the open picker button on desktop.", "previousIconButton": "Button allowing to switch to the left view.", "rightArrowIcon": "Icon displayed in the right view switch button.", "shortcuts": "Custom component for the shortcuts.", - "textField": "Form control with an input to render the value inside the default field.", + "textField": "Form control with an input to render the value.", "toolbar": "Custom component for the toolbar rendered above the views." } } diff --git a/docs/translations/api-docs/date-pickers/single-input-date-range-field/single-input-date-range-field.json b/docs/translations/api-docs/date-pickers/single-input-date-range-field/single-input-date-range-field.json index 72e27d93359f..03f4bf83d3a7 100644 --- a/docs/translations/api-docs/date-pickers/single-input-date-range-field/single-input-date-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-date-range-field/single-input-date-range-field.json @@ -7,6 +7,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, diff --git a/docs/translations/api-docs/date-pickers/single-input-date-time-range-field/single-input-date-time-range-field.json b/docs/translations/api-docs/date-pickers/single-input-date-time-range-field/single-input-date-time-range-field.json index 871afaaa2574..08f57b1fb609 100644 --- a/docs/translations/api-docs/date-pickers/single-input-date-time-range-field/single-input-date-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-date-time-range-field/single-input-date-time-range-field.json @@ -8,6 +8,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, diff --git a/docs/translations/api-docs/date-pickers/single-input-time-range-field/single-input-time-range-field.json b/docs/translations/api-docs/date-pickers/single-input-time-range-field/single-input-time-range-field.json index 11ab48673c0f..32b0d8c393e7 100644 --- a/docs/translations/api-docs/date-pickers/single-input-time-range-field/single-input-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-time-range-field/single-input-time-range-field.json @@ -8,6 +8,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, diff --git a/docs/translations/api-docs/date-pickers/time-field/time-field.json b/docs/translations/api-docs/date-pickers/time-field/time-field.json index 376ab6e3e422..9a6ba3f53aab 100644 --- a/docs/translations/api-docs/date-pickers/time-field/time-field.json +++ b/docs/translations/api-docs/date-pickers/time-field/time-field.json @@ -8,6 +8,9 @@ "clearable": { "description": "If true, a clear button will be shown in the field allowing value clearing." }, + "clearButtonPosition": { + "description": "The position at which the clear button is placed. If the field is not clearable, the button is not rendered." + }, "color": { "description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide." }, @@ -87,6 +90,9 @@ "description": "Callback fired when the selected sections change.", "typeDescriptions": { "newValue": "The new selected sections." } }, + "openPickerButtonPosition": { + "description": "The position at which the opening button is placed. If there is no picker to open, the button is not rendered" + }, "readOnly": { "description": "If true, the component is read-only. When read-only, the value cannot be changed but the user can interact with the interface." }, diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 807b5511d78a..b58a5b9ca70f 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -36,14 +36,14 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByText('May 2019')).toBeVisible(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(screen.getByText('October 2019')).toBeVisible(); // scroll back - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByText('May 2019')).toBeVisible(); }); @@ -52,7 +52,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); }); @@ -168,7 +168,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onOpen.callCount).to.equal(1); expect(screen.getByRole('tooltip')).toBeVisible(); @@ -179,7 +179,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onOpen.callCount).to.equal(1); expect(screen.getByRole('tooltip')).toBeVisible(); @@ -236,7 +236,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -278,7 +278,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -311,7 +311,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Change the end date fireEvent.click(getPickerDay('3')); @@ -338,7 +338,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(getPickerDay('3')); @@ -365,7 +365,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Dismiss the picker const input = document.getElementById('test-id')!; @@ -403,7 +403,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(getPickerDay('3')); @@ -461,7 +461,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); document.querySelector('#test')!.focus(); @@ -493,7 +493,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); // Change the start date (already tested) @@ -531,7 +531,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -557,7 +557,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -584,10 +584,10 @@ describe('', () => { ); // Open the picker (already tested) - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Switch to end date - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -608,10 +608,10 @@ describe('', () => { ); // Open the picker (already tested) - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Switch to start date - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -622,7 +622,7 @@ describe('', () => { it('should respect the disablePast prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('8')).to.have.attribute('disabled'); expect(getPickerDay('9')).to.have.attribute('disabled'); @@ -634,7 +634,7 @@ describe('', () => { it('should respect the disableFuture prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('8')).not.to.have.attribute('disabled'); expect(getPickerDay('9')).not.to.have.attribute('disabled'); @@ -646,7 +646,7 @@ describe('', () => { it('should respect the minDate prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('13')).to.have.attribute('disabled'); expect(getPickerDay('14')).to.have.attribute('disabled'); @@ -658,7 +658,7 @@ describe('', () => { it('should respect the maxDate prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('13')).not.to.have.attribute('disabled'); expect(getPickerDay('14')).not.to.have.attribute('disabled'); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx index f9255bdd65af..0ece845e3939 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx @@ -28,6 +28,7 @@ describe(' - Describes', () => { clock, componentFamily: 'picker', views: ['day'], + variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx index e8537c5cfc1d..4dea6eb3e186 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx @@ -20,7 +20,7 @@ describe('', () => { it('should allow to select range within the same day', () => { render(); - openPicker({ type: 'date-time-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-time-range', initialFocus: 'start' }); // select start date range fireEvent.click(screen.getByRole('gridcell', { name: '11' })); @@ -45,7 +45,7 @@ describe('', () => { , ); - openPicker({ type: 'date-time-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-time-range', initialFocus: 'start' }); fireEvent.click(screen.getByRole('gridcell', { name: '11' })); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx index 83fee1c9814b..83e2457bd23b 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx @@ -44,7 +44,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -55,7 +55,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -80,7 +80,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -120,7 +120,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -151,7 +151,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Change the end date fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -181,7 +181,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -213,7 +213,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -246,7 +246,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -272,7 +272,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx index 830468015211..1b8a91aec129 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx @@ -84,7 +84,7 @@ describe(' - Describes', () => { } if (!isOpened) { - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); } fireEvent.click( diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx index 90a5dc4057ec..fe4f71d11525 100644 --- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx @@ -88,7 +88,7 @@ describe(' - Describes', () => { if (!isOpened) { openPicker({ type: 'date-time-range', - variant: 'mobile', + initialFocus: setEndDate ? 'end' : 'start', }); } diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx index 50cd4f22cbb7..884e069ee3ed 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -166,8 +163,8 @@ const MultiInputDateRangeField = React.forwardRef(function MultiInputDateRangeFi unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); return ( diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx index 06b579877035..db9e69a034ee 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -164,8 +161,8 @@ const MultiInputDateTimeRangeField = React.forwardRef(function MultiInputDateTim unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); return ( diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx index e9e5c24a2ea2..e13e853a49e6 100644 --- a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -167,8 +164,8 @@ const MultiInputTimeRangeField = React.forwardRef(function MultiInputTimeRangeFi unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); return ( diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx index 86707618138f..1e38cadcbc0f 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx @@ -1,15 +1,11 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; +import { useFieldOwnerState, PickerFieldUI } from '@mui/x-date-pickers/internals'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { SingleInputDateRangeFieldProps } from './SingleInputDateRangeField.types'; import { useSingleInputDateRangeField } from './useSingleInputDateRangeField'; @@ -45,9 +41,9 @@ const SingleInputDateRangeField = React.forwardRef(function SingleInputDateRange const ownerState = useFieldOwnerState(themeProps); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + // The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. + // Once the non-accessible DOM structure will be removed, we will be able to remove the `textField` slot and clean this logic. + const TextField = PickersTextField; const textFieldProps = useSlotProps({ elementType: TextField, externalSlotProps: slotProps?.textField, @@ -66,15 +62,14 @@ const SingleInputDateRangeField = React.forwardRef(function SingleInputDateRange TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateRangeField.fieldType = 'single-input'; @@ -95,6 +90,12 @@ SingleInputDateRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts index 027ffbfbdb1d..a97713d8e076 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts @@ -1,20 +1,16 @@ -import * as React from 'react'; -import { TextFieldProps } from '@mui/material/TextField'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; + PickerRangeValue, + UseFieldInternalProps, + ExportedPickerFieldUIProps, + ExportedPickerFieldUISlots, + PickerFieldUISlotProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import type { DateRangeValidationError, UseDateRangeFieldProps } from '../models'; export interface UseSingleInputDateRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseDateRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -22,7 +18,9 @@ export interface UseSingleInputDateRangeFieldProps< DateRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputDateRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -43,18 +41,6 @@ export type SingleInputDateRangeFieldProps< slotProps?: SingleInputDateRangeFieldSlotProps; }; -export interface SingleInputDateRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputDateRangeFieldSlots extends ExportedPickerFieldUISlots {} -export interface SingleInputDateRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputDateRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts index e1b71090039e..de4de0d86976 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts @@ -36,5 +36,7 @@ export const useSingleInputDateRangeField = < fieldValueManager, validator: validateDateRange, valueType: 'date', + // TODO v8: Add a real aria label before enabling the button. + getOpenDialogAriaLabel: () => '', }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx index f81834fd4ff7..b1db1dd9ec3c 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx @@ -2,15 +2,12 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import MuiTextField from '@mui/material/TextField'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { useThemeProps } from '@mui/material/styles'; import { refType } from '@mui/utils'; import useSlotProps from '@mui/utils/useSlotProps'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; import { SingleInputDateTimeRangeFieldProps } from './SingleInputDateTimeRangeField.types'; import { useSingleInputDateTimeRangeField } from './useSingleInputDateTimeRangeField'; import { FieldType } from '../models'; @@ -66,15 +63,14 @@ const SingleInputDateTimeRangeField = React.forwardRef(function SingleInputDateT TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateTimeRangeField.fieldType = 'single-input'; @@ -100,6 +96,12 @@ SingleInputDateTimeRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts index 9624ffa1c73e..44441fe6f60a 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts @@ -1,21 +1,17 @@ -import * as React from 'react'; -import { TextFieldProps } from '@mui/material/TextField'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; + ExportedPickerFieldUIProps, + ExportedPickerFieldUISlots, + PickerFieldUISlotProps, + PickerRangeValue, + UseFieldInternalProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import { UseDateTimeRangeFieldProps } from '../internals/models'; import { DateTimeRangeValidationError } from '../models'; export interface UseSingleInputDateTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseDateTimeRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -23,7 +19,9 @@ export interface UseSingleInputDateTimeRangeFieldProps< DateTimeRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputDateTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -44,18 +42,6 @@ export type SingleInputDateTimeRangeFieldProps< slotProps?: SingleInputDateTimeRangeFieldSlotProps; }; -export interface SingleInputDateTimeRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputDateTimeRangeFieldSlots extends ExportedPickerFieldUISlots {} -export interface SingleInputDateTimeRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputDateTimeRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts index 1fbed22a25d0..17c7ef01605c 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts @@ -40,5 +40,7 @@ export const useSingleInputDateTimeRangeField = < fieldValueManager, validator: validateDateTimeRange, valueType: 'date-time', + // TODO v8: Add a real aria label before enabling the button. + getOpenDialogAriaLabel: () => '', }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx index b820b6a27f60..09adefda7eaa 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx @@ -2,11 +2,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import MuiTextField from '@mui/material/TextField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { ClockIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { useThemeProps } from '@mui/material/styles'; import useSlotProps from '@mui/utils/useSlotProps'; @@ -66,15 +63,14 @@ const SingleInputTimeRangeField = React.forwardRef(function SingleInputTimeRange TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputTimeRangeField.fieldType = 'single-input'; @@ -100,6 +96,12 @@ SingleInputTimeRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts index 46b019e84e95..818c6ca2cb37 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts @@ -1,21 +1,17 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; + ExportedPickerFieldUIProps, + ExportedPickerFieldUISlots, + PickerFieldUISlotProps, + PickerRangeValue, + UseFieldInternalProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import { UseTimeRangeFieldProps } from '../internals/models'; import { TimeRangeValidationError } from '../models'; export interface UseSingleInputTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseTimeRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -23,7 +19,9 @@ export interface UseSingleInputTimeRangeFieldProps< TimeRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -44,18 +42,6 @@ export type SingleInputTimeRangeFieldProps< slotProps?: SingleInputTimeRangeFieldSlotProps; }; -export interface SingleInputTimeRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputTimeRangeFieldSlots extends ExportedPickerFieldUISlots {} -export interface SingleInputTimeRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputTimeRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts index a55e35d42c97..367372d9a946 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts @@ -36,5 +36,7 @@ export const useSingleInputTimeRangeField = < fieldValueManager, validator: validateTimeRange, valueType: 'time', + // TODO v8: Add a real aria label before enabling the button. + getOpenDialogAriaLabel: () => '', }); }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx index 4dabe621a1e7..aeea33b20121 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx @@ -67,7 +67,6 @@ export const useDesktopRangePicker = < } = props; const fieldContainerRef = React.useRef(null); - const anchorRef = React.useRef(null); const popperRef = React.useRef(null); const startFieldRef = React.useRef>(null); const endFieldRef = React.useRef>(null); @@ -111,6 +110,9 @@ export const useDesktopRangePicker = < }, }); + // Temporary hack to hide the opening button on the range pickers until we have migrate them to the new opening logic. + providerProps.contextValue.triggerStatus = 'hidden'; + React.useEffect(() => { if (layoutProps.view) { initialView.current = layoutProps.view; @@ -182,7 +184,7 @@ export const useDesktopRangePicker = < pickerSlotProps: slotProps, pickerSlots: slots, fieldProps, - anchorRef, + anchorRef: providerProps.contextValue.triggerRef, startFieldRef, endFieldRef, singleInputFieldRef, @@ -213,7 +215,7 @@ export const useDesktopRangePicker = < role="tooltip" placement="bottom-start" containerRef={popperRef} - anchorEl={anchorRef.current} + anchorEl={providerProps.contextValue.triggerRef.current} onBlur={handleBlur} {...actions} open={open} diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts index a858ae3df40b..1f91cf51afa6 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts @@ -108,7 +108,7 @@ export interface UseEnrichedRangePickerFieldPropsParams< TEnableAccessibleFieldDOMStructure, TError >; - anchorRef?: React.Ref; + anchorRef?: React.Ref; currentView?: TView | null; initialView?: TView; onViewChange?: (view: TView) => void; @@ -309,7 +309,6 @@ const useSingleInputFieldSlotProps = < pickerSlots, pickerSlotProps, fieldProps, - anchorRef, currentView, }: UseEnrichedRangePickerFieldPropsParams< true, @@ -398,10 +397,6 @@ const useSingleInputFieldSlotProps = < onKeyDown: onSpaceOrEnter(openPicker, fieldProps.onKeyDown), onSelectedSectionsChange: handleSelectedSectionsChange, onBlur, - InputProps: { - ref: anchorRef, - ...fieldProps?.InputProps, - }, focused: open ? true : undefined, ...(labelId != null && { id: labelId }), ...(variant === 'mobile' && { readOnly: true }), diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx index 32b4ac1dab03..6f4df1737d99 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx @@ -106,8 +106,10 @@ export const useMobileRangePicker = < }, }); - const Field = slots.field; + // Temporary hack to hide the opening button on the range pickers until we have migrate them to the new opening logic. + providerProps.contextValue.triggerStatus = 'hidden'; + const Field = slots.field; const fieldProps: RangePickerPropsForFieldSlot< boolean, TEnableAccessibleFieldDOMStructure, diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts deleted file mode 100644 index f3568f99ad13..000000000000 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* TODO: remove this when a clearable behavior for multiple input range fields is implemented */ -export const excludeProps = ( - props: TProps, - excludedProps: Array, -): TProps => { - return (Object.keys(props) as Array).reduce((acc, key) => { - if (!excludedProps.includes(key)) { - acc[key] = props[key]; - } - return acc; - }, {} as TProps); -}; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts index 081fbf140f0e..724cc8791a36 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts @@ -19,7 +19,6 @@ import { validateDateRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; import { DateRangeValidationError } from '../../../models'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; export const useMultiInputDateRangeField = < @@ -144,9 +143,8 @@ export const useMultiInputDateRangeField = < endFieldProps, ) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts index 3a006da7c913..d42bfdc7b867 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts @@ -19,7 +19,6 @@ import { DateTimeRangeValidationError } from '../../../models'; import { validateDateTimeRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; export const useMultiInputDateTimeRangeField = < @@ -145,9 +144,8 @@ export const useMultiInputDateTimeRangeField = < typeof endFieldProps >(endFieldProps) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts index 9f8b36291f5f..b8ee4cd81730 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts @@ -19,7 +19,6 @@ import type { } from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; export const useMultiInputTimeRangeField = < @@ -144,9 +143,8 @@ export const useMultiInputTimeRangeField = < endFieldProps, ) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/models/fields.ts b/packages/x-date-pickers-pro/src/models/fields.ts index 648dc4c4f277..0fe3c8c4415a 100644 --- a/packages/x-date-pickers-pro/src/models/fields.ts +++ b/packages/x-date-pickers-pro/src/models/fields.ts @@ -6,7 +6,6 @@ import { PickerRangeValue, } from '@mui/x-date-pickers/internals'; import { FieldRef, PickerFieldSlotProps } from '@mui/x-date-pickers/models'; -import { UseClearableFieldResponse } from '@mui/x-date-pickers/hooks'; export type { FieldRangeSection } from '@mui/x-date-pickers/internals'; @@ -62,6 +61,13 @@ export type PickerRangeFieldSlotProps = UseClearableFieldResponse< - UseFieldResponse +> = Omit< + UseFieldResponse, + | 'slots' + | 'slotProps' + | 'clearable' + | 'onClear' + | 'openPickerButtonPosition' + | 'clearButtonPosition' + | 'openPickerAriaLabel' >; diff --git a/packages/x-date-pickers/src/DateField/DateField.tsx b/packages/x-date-pickers/src/DateField/DateField.tsx index 2d8704ab3310..46cd1acb8f4e 100644 --- a/packages/x-date-pickers/src/DateField/DateField.tsx +++ b/packages/x-date-pickers/src/DateField/DateField.tsx @@ -1,16 +1,15 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { DateFieldProps } from './DateField.types'; import { useDateField } from './useDateField'; -import { useClearableField } from '../hooks'; import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateFieldComponent = (( props: DateFieldProps & React.RefAttributes, @@ -38,11 +37,10 @@ const DateField = React.forwardRef(function DateField< const ownerState = useFieldOwnerState(themeProps); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + // The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. + // Once the non-accessible DOM structure will be removed, we will be able to remove the `textField` slot and clean this logic. const textFieldProps = useSlotProps({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, externalForwardedProps: other, additionalProps: { @@ -58,15 +56,14 @@ const DateField = React.forwardRef(function DateField< const fieldResponse = useDateField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateFieldComponent; DateField.propTypes = { @@ -85,6 +82,12 @@ DateField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -228,6 +231,12 @@ DateField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/DateField/DateField.types.ts b/packages/x-date-pickers/src/DateField/DateField.types.ts index 7226c4ac6579..f401d3c5cc67 100644 --- a/packages/x-date-pickers/src/DateField/DateField.types.ts +++ b/packages/x-date-pickers/src/DateField/DateField.types.ts @@ -1,16 +1,13 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; -import { DateValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; +import { MakeOptional } from '@mui/x-internals/types'; +import { DateValidationError, BuiltInFieldTextFieldProps } from '../models'; import { UseFieldInternalProps } from '../internals/hooks/useField'; import { ExportedValidateDateProps } from '../validation/validateDate'; -import { PickersTextFieldProps } from '../PickersTextField'; import { PickerValue } from '../internals/models'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + ExportedPickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseDateFieldProps extends MakeOptional< @@ -18,7 +15,7 @@ export interface UseDateFieldProps, ExportedValidateDateProps, - ExportedUseClearableFieldProps {} + ExportedPickerFieldUIProps {} export type DateFieldProps = // The hook props @@ -43,18 +40,6 @@ export type DateFieldProps = DateFieldProps; -export interface DateFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface DateFieldSlots extends ExportedPickerFieldUISlots {} -export interface DateFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface DateFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/DateField/useDateField.ts b/packages/x-date-pickers/src/DateField/useDateField.ts index 42d7b01fe3a8..0a479b80806b 100644 --- a/packages/x-date-pickers/src/DateField/useDateField.ts +++ b/packages/x-date-pickers/src/DateField/useDateField.ts @@ -9,6 +9,7 @@ import { validateDate } from '../validation'; import { useSplitFieldProps } from '../hooks'; import { useDefaultizedDateField } from '../internals/hooks/defaultizedFieldProps'; import { PickerValue } from '../internals/models'; +import { useGetOpenDialogAriaLabel } from '../internals/hooks/useGetOpenDialogAriaLabel'; export const useDateField = < TEnableAccessibleFieldDOMStructure extends boolean, @@ -23,6 +24,11 @@ export const useDateField = < const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); + const getOpenDialogAriaLabel = useGetOpenDialogAriaLabel({ + formatKey: 'fullDate', + translationKey: 'openDatePickerDialogue', + }); + return useField< PickerValue, TEnableAccessibleFieldDOMStructure, @@ -35,5 +41,6 @@ export const useDateField = < fieldValueManager: singleItemFieldValueManager, validator: validateDate, valueType: 'date', + getOpenDialogAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx index 3ad845d85883..ba65234304fa 100644 --- a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx +++ b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; +import { dialogClasses } from '@mui/material/Dialog'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +14,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.getByRole('dialog')).to.have.class(dialogClasses.paper); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx index 876e25f77ed4..1d1cd36a845a 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx @@ -7,10 +7,10 @@ import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { DateTimeFieldProps } from './DateTimeField.types'; import { useDateTimeField } from './useDateTimeField'; -import { useClearableField } from '../hooks'; import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateTimeFieldComponent = (( props: DateTimeFieldProps & @@ -62,15 +62,14 @@ const DateTimeField = React.forwardRef(function DateTimeField< const fieldResponse = useDateTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateTimeFieldComponent; DateTimeField.propTypes = { @@ -94,6 +93,12 @@ DateTimeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -265,6 +270,12 @@ DateTimeField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts index 2c3964472c39..f381a5c85711 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts @@ -1,17 +1,14 @@ -import * as React from 'react'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { TextFieldProps } from '@mui/material/TextField'; -import { DateTimeValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; +import { MakeOptional } from '@mui/x-internals/types'; +import { DateTimeValidationError, BuiltInFieldTextFieldProps } from '../models'; import { UseFieldInternalProps } from '../internals/hooks/useField'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; import { ExportedValidateDateTimeProps } from '../validation/validateDateTime'; import { AmPmProps } from '../internals/models/props/time'; import { PickerValue } from '../internals/models'; -import { PickersTextFieldProps } from '../PickersTextField'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + ExportedPickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseDateTimeFieldProps extends MakeOptional< @@ -23,7 +20,7 @@ export interface UseDateTimeFieldProps, ExportedValidateDateTimeProps, - ExportedUseClearableFieldProps, + ExportedPickerFieldUIProps, AmPmProps {} export type DateTimeFieldProps = @@ -46,18 +43,6 @@ export type DateTimeFieldProps, or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface DateTimeFieldSlots extends ExportedPickerFieldUISlots {} -export interface DateTimeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface DateTimeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts index bd3b0b585352..a1c6f0a35870 100644 --- a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts +++ b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts @@ -9,6 +9,7 @@ import { validateDateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; import { useDefaultizedDateTimeField } from '../internals/hooks/defaultizedFieldProps'; import { PickerValue } from '../internals/models'; +import { useGetOpenDialogAriaLabel } from '../internals/hooks/useGetOpenDialogAriaLabel'; export const useDateTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, @@ -23,6 +24,11 @@ export const useDateTimeField = < const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date-time'); + const getOpenDialogAriaLabel = useGetOpenDialogAriaLabel({ + formatKey: 'fullDate', + translationKey: 'openDatePickerDialogue', + }); + return useField< PickerValue, TEnableAccessibleFieldDOMStructure, @@ -35,5 +41,6 @@ export const useDateTimeField = < fieldValueManager: singleItemFieldValueManager, validator: validateDateTime, valueType: 'date-time', + getOpenDialogAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx index 933f61c48f16..d5c489cefc78 100644 --- a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; +import { dialogClasses } from '@mui/material/Dialog'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +14,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.getByRole('dialog')).to.have.class(dialogClasses.paper); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx index f5f035ca05f0..ef4f0dae88a3 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx @@ -6,16 +6,13 @@ import { refType } from '@mui/utils'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { DesktopDatePickerProps } from './DesktopDatePicker.types'; import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { validateDate, extractValidationProps } from '../validation'; import { DateView, PickerOwnerState } from '../models'; import { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; -import { CalendarIcon } from '../icons'; import { DateField } from '../DateField'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { resolveDateFormat } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type DesktopDatePickerComponent = (( props: DesktopDatePickerProps & @@ -38,7 +35,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< inProps: DesktopDatePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date pickers @@ -60,7 +56,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< format: resolveDateFormat(utils, defaultizedProps, false), yearsPerRow: defaultizedProps.yearsPerRow ?? 4, slots: { - openPickerIcon: CalendarIcon, field: DateField, ...defaultizedProps.slots, }, @@ -86,12 +81,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< props, valueManager: singleItemValueManager, valueType: 'date', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDate, }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx index fe9e436f6926..ab12fd1f66e3 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx @@ -40,7 +40,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByLabelText(/switch to year view/i)); expect(handleViewChange.callCount).to.equal(1); @@ -49,7 +49,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(handleViewChange.callCount).to.equal(2); expect(handleViewChange.lastCall.firstArg).to.equal('day'); }); @@ -66,7 +66,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByLabelText(/switch to year view/i)); expect(handleViewChange.callCount).to.equal(1); @@ -75,7 +75,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(handleViewChange.callCount).to.equal(2); expect(handleViewChange.lastCall.firstArg).to.equal('month'); }); @@ -85,7 +85,7 @@ describe('', () => { , ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByRole('radio', { checked: true, name: '2018' })).not.to.equal(null); @@ -93,7 +93,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ views: ['month', 'year'] }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // wait for all pending changes to be flushed clock.runToLast(); @@ -107,7 +107,7 @@ describe('', () => { } render(); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(document.activeElement).to.have.text('2019'); fireEvent.click(screen.getByText('2020')); @@ -123,7 +123,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); @@ -131,7 +131,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ view: 'year' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // wait for all pending changes to be flushed clock.runToLast(); @@ -245,7 +245,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // Select year fireEvent.click(screen.getByRole('radio', { name: '2025' })); @@ -272,7 +272,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).to.have.attribute('disabled'); }); @@ -285,7 +285,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).not.to.have.attribute('disabled'); }); @@ -298,7 +298,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).to.have.attribute('disabled'); }); @@ -311,7 +311,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).not.to.have.attribute('disabled'); }); @@ -356,7 +356,7 @@ describe('', () => { expect(() => { render(); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); }).toWarnDev('MUI X: `openTo="month"` is not a valid prop.'); }); }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx index 1c79140c4807..1bb38f834e57 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx @@ -23,7 +23,6 @@ describe(' - Describes', () => { clock, views: ['year', 'month', 'day'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx index 4f14a97c0548..ceee590d5546 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx @@ -13,11 +13,9 @@ import { DateTimePickerViewRenderers, } from '../DateTimePicker/shared'; import { renderDateViewCalendar } from '../dateViewRenderers/dateViewRenderers'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { validateDateTime, extractValidationProps } from '../validation'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../internals/models'; -import { CalendarIcon } from '../icons'; import { UseDesktopPickerProps, useDesktopPicker } from '../internals/hooks/useDesktopPicker'; import { PickerViewsRendererProps } from '../internals/hooks/usePicker'; import { @@ -41,7 +39,6 @@ import { VIEW_HEIGHT } from '../internals/constants/dimensions'; import { UsePickerViewsProps } from '../internals/hooks/usePicker/usePickerViews'; import { isInternalTimeView } from '../internals/utils/time-utils'; import { isDatePickerView } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; import { PickerLayoutOwnerState } from '../PickersLayout'; const rendererInterceptor = function rendererInterceptor< @@ -130,7 +127,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< inProps: DesktopDateTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date time pickers @@ -185,7 +181,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< slots: { field: DateTimeField, layout: DesktopDateTimePickerLayout, - openPickerIcon: CalendarIcon, ...defaultizedProps.slots, }, slotProps: { @@ -219,12 +214,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< props, valueManager: singleItemValueManager, valueType: 'date-time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDateTime, rendererInterceptor, }); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx index ea815c570641..5d8534b3971e 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx @@ -35,7 +35,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + openPicker({ type: 'date-time' }); // Select year fireEvent.click(screen.getByRole('radio', { name: '2025' })); @@ -75,7 +75,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + openPicker({ type: 'date-time' }); // Change the date multiple times to check that picker doesn't close after cycling through all views internally fireEvent.click(screen.getByRole('gridcell', { name: '2' })); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx index 5d6ab646c7b9..e02ad8b1ebf3 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx @@ -36,7 +36,6 @@ describe(' - Describes', () => { clock, views: ['year', 'month', 'day', 'hours', 'minutes'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index 75528d45cd42..aa0fbf865eb4 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -7,10 +7,8 @@ import { singleItemValueManager } from '../internals/utils/valueManagers'; import { TimeField } from '../TimeField'; import { DesktopTimePickerProps } from './DesktopTimePicker.types'; import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateTime } from '../validation'; -import { ClockIcon } from '../icons'; import { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; import { renderDigitalClockTimeView, @@ -21,7 +19,6 @@ import { TimeViewWithMeridiem } from '../internals/models'; import { resolveTimeFormat } from '../internals/utils/time-utils'; import { resolveTimeViewsResponse } from '../internals/utils/date-time-utils'; import { TimeView, PickerOwnerState } from '../models'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type DesktopTimePickerComponent = (( props: DesktopTimePickerProps & @@ -44,7 +41,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< inProps: DesktopTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all time pickers @@ -94,7 +90,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< views: shouldRenderTimeInASingleColumn ? ['hours' as TimeViewWithMeridiem] : views, slots: { field: TimeField, - openPickerIcon: ClockIcon, ...defaultizedProps.slots, }, slotProps: { @@ -124,12 +119,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< props, valueManager: singleItemValueManager, valueType: 'time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullTime', - contextTranslation: translations.openTimePickerDialogue, - propsTranslation: props.localeText?.openTimePickerDialogue, - }), validator: validateTime, }); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx index f6d1314d7694..2ddfa4243ec1 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx @@ -75,7 +75,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '09:00 AM' })); expect(onChange.callCount).to.equal(1); @@ -99,7 +99,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '2 hours' })); expect(onChange.callCount).to.equal(1); @@ -132,7 +132,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '15 minutes' })); expect(onChange.callCount).to.equal(1); @@ -170,7 +170,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(1); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx index 700a92a4156f..77caadcfb15c 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx @@ -28,7 +28,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx index c4f5013abc99..4ec445f96f69 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx @@ -21,7 +21,6 @@ describe(' - Describes', () => { clock, views: ['hours'], componentFamily: 'digital-clock', - variant: 'desktop', })); describeConformance(, () => ({ @@ -37,7 +36,6 @@ describe(' - Describes', () => { render, componentFamily: 'digital-clock', type: 'time', - variant: 'desktop', defaultProps: { views: ['hours'], }, diff --git a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx index 90e9db74e87d..d6df29951a56 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx @@ -6,7 +6,6 @@ import { refType } from '@mui/utils'; import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { MobileDatePickerProps } from './MobileDatePicker.types'; import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateDate } from '../validation'; import { DateView, PickerOwnerState } from '../models'; @@ -14,7 +13,6 @@ import { DateField } from '../DateField'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { resolveDateFormat } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type MobileDatePickerComponent = (( props: MobileDatePickerProps & @@ -37,7 +35,6 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker< inProps: MobileDatePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date pickers @@ -83,12 +80,6 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker< props, valueManager: singleItemValueManager, valueType: 'date', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDate, }); diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx index 586d963955a2..1c6255189b9e 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx @@ -11,7 +11,6 @@ import { expectFieldValueV7, buildFieldInteractions, openPicker, - getFieldSectionsContainer, } from 'test/utils/pickers'; describe('', () => { @@ -132,17 +131,6 @@ describe('', () => { }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - it('should call `onAccept` even if controlled', () => { const onAccept = spy(); @@ -154,7 +142,7 @@ describe('', () => { render(); - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByText('15', { selector: 'button' })); fireEvent.click(screen.getByText('OK', { selector: 'button' })); @@ -176,7 +164,7 @@ describe('', () => { expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); // Open and Dismiss the picker - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); clock.runToLast(); diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx index 2a75c950b93d..ed5371703071 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx @@ -61,7 +61,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); } const newValue = applySameValue ? value! : adapterToUse.addDays(value!, 1); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx index cdc1c91a5abe..b7da2a82bfed 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx @@ -10,7 +10,6 @@ import { DateTimePickerViewRenderers, useDateTimePickerDefaultizedProps, } from '../DateTimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateDateTime } from '../validation'; import { DateOrTimeView, PickerOwnerState } from '../models'; @@ -18,7 +17,6 @@ import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { renderTimeViewClock } from '../timeViewRenderers'; import { resolveDateTimeFormat } from '../internals/utils/date-time-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type MobileDateTimePickerComponent = (( props: MobileDateTimePickerProps & @@ -41,7 +39,6 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< inProps: MobileDateTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date time pickers @@ -98,12 +95,6 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< props, valueManager: singleItemValueManager, valueType: 'date-time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDateTime, }); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx index da2eb24c177e..474374f61806 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx @@ -8,7 +8,6 @@ import { createPickerRenderer, openPicker, getClockTouchEvent, - getFieldSectionsContainer, } from 'test/utils/pickers'; describe('', () => { @@ -83,17 +82,6 @@ describe('', () => { }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - it('should call onChange when selecting each view', function test() { if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { this.skip(); @@ -114,7 +102,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'mobile' }); + openPicker({ type: 'date-time' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx index 30ae63e192e4..88975bfea2bd 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx @@ -28,7 +28,6 @@ describe(' - Describes', () => { clock, views: ['year', 'day', 'hours', 'minutes'], componentFamily: 'picker', - variant: 'mobile', })); describeConformance(, () => ({ @@ -73,7 +72,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'date-time', variant: 'mobile' }); + openPicker({ type: 'date-time' }); } const newValue = applySameValue diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx index 4428a5c28a14..4f3c8ef317d7 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx @@ -7,14 +7,12 @@ import { singleItemValueManager } from '../internals/utils/valueManagers'; import { TimeField } from '../TimeField'; import { MobileTimePickerProps } from './MobileTimePicker.types'; import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateTime } from '../validation'; import { PickerOwnerState, TimeView } from '../models'; import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { renderTimeViewClock } from '../timeViewRenderers'; import { resolveTimeFormat } from '../internals/utils/time-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type MobileTimePickerComponent = (( props: MobileTimePickerProps & @@ -37,7 +35,6 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker< inProps: MobileTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all time pickers @@ -87,12 +84,6 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker< props, valueManager: singleItemValueManager, valueType: 'time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullTime', - contextTranslation: translations.openTimePickerDialogue, - propsTranslation: props.localeText?.openTimePickerDialogue, - }), validator: validateTime, }); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx index e5442cc50276..6fd96148682b 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx @@ -8,24 +8,12 @@ import { adapterToUse, openPicker, getClockTouchEvent, - getFieldSectionsContainer, } from 'test/utils/pickers'; describe('', () => { const { render } = createPickerRenderer({ clock: 'fake' }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - it('should fire a change event when meridiem changes', () => { const handleChange = spy(); render( @@ -64,7 +52,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'mobile' }); + openPicker({ type: 'time' }); // Change the hours const hourClockEvent = getClockTouchEvent(11, '12hours'); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx index 5cff45ba0c9c..cb211de30b1d 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx @@ -29,7 +29,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'picker', - variant: 'mobile', })); describeConformance(, () => ({ @@ -71,7 +70,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'time', variant: 'mobile' }); + openPicker({ type: 'time' }); } const newValue = applySameValue diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx index 3ab127d6bafa..992c33dc6dec 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx @@ -23,7 +23,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'multi-section-digital-clock', - variant: 'desktop', })); describeConformance(, () => ({ @@ -39,7 +38,6 @@ describe(' - Describes', () => { render, componentFamily: 'multi-section-digital-clock', type: 'time', - variant: 'desktop', values: [adapterToUse.date('2018-01-01T11:30:00'), adapterToUse.date('2018-01-01T12:35:00')], emptyValue: null, clock, diff --git a/packages/x-date-pickers/src/TimeField/TimeField.tsx b/packages/x-date-pickers/src/TimeField/TimeField.tsx index 571fd5c6b723..1057e4a8909c 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.tsx +++ b/packages/x-date-pickers/src/TimeField/TimeField.tsx @@ -1,16 +1,15 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { TimeFieldProps } from './TimeField.types'; import { useTimeField } from './useTimeField'; -import { useClearableField } from '../hooks'; import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI } from '../internals/components/PickerFieldUI'; +import { ClockIcon } from '../icons'; type TimeFieldComponent = (( props: TimeFieldProps & React.RefAttributes, @@ -38,11 +37,10 @@ const TimeField = React.forwardRef(function TimeField< const ownerState = useFieldOwnerState(themeProps); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + // The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. + // Once the non-accessible DOM structure will be removed, we will be able to remove the `textField` slot and clean this logic. const textFieldProps = useSlotProps({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, externalForwardedProps: other, ownerState, @@ -58,15 +56,14 @@ const TimeField = React.forwardRef(function TimeField< const fieldResponse = useTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as TimeFieldComponent; TimeField.propTypes = { @@ -90,6 +87,12 @@ TimeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -243,6 +246,12 @@ TimeField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/TimeField/TimeField.types.ts b/packages/x-date-pickers/src/TimeField/TimeField.types.ts index 819535baa8fb..9c0a273bd0b3 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.types.ts +++ b/packages/x-date-pickers/src/TimeField/TimeField.types.ts @@ -1,17 +1,14 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { MakeOptional } from '@mui/x-internals/types'; import { UseFieldInternalProps } from '../internals/hooks/useField'; -import { TimeValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; +import { TimeValidationError, BuiltInFieldTextFieldProps } from '../models'; import { ExportedValidateTimeProps } from '../validation/validateTime'; import { AmPmProps } from '../internals/models/props/time'; import { PickerValue } from '../internals/models'; -import { PickersTextFieldProps } from '../PickersTextField'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + ExportedPickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseTimeFieldProps extends MakeOptional< @@ -19,7 +16,7 @@ export interface UseTimeFieldProps, ExportedValidateTimeProps, - ExportedUseClearableFieldProps, + ExportedPickerFieldUIProps, AmPmProps {} export type TimeFieldProps = @@ -42,18 +39,6 @@ export type TimeFieldProps, or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface TimeFieldSlots extends ExportedPickerFieldUISlots {} -export interface TimeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface TimeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/TimeField/useTimeField.ts b/packages/x-date-pickers/src/TimeField/useTimeField.ts index 51a88016a8dc..a8f0f5a16d35 100644 --- a/packages/x-date-pickers/src/TimeField/useTimeField.ts +++ b/packages/x-date-pickers/src/TimeField/useTimeField.ts @@ -9,6 +9,7 @@ import { validateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; import { useDefaultizedTimeField } from '../internals/hooks/defaultizedFieldProps'; import { PickerValue } from '../internals/models'; +import { useGetOpenDialogAriaLabel } from '../internals/hooks/useGetOpenDialogAriaLabel'; export const useTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, @@ -23,6 +24,11 @@ export const useTimeField = < const { forwardedProps, internalProps } = useSplitFieldProps(props, 'time'); + const getOpenDialogAriaLabel = useGetOpenDialogAriaLabel({ + formatKey: 'fullTime', + translationKey: 'openTimePickerDialogue', + }); + return useField< PickerValue, TEnableAccessibleFieldDOMStructure, @@ -35,5 +41,6 @@ export const useTimeField = < fieldValueManager: singleItemFieldValueManager, validator: validateTime, valueType: 'time', + getOpenDialogAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx index aaa91fd391fa..36cbebcdf621 100644 --- a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx +++ b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; import { expect } from 'chai'; +import { dialogClasses } from '@mui/material/Dialog'; +import { TimePicker } from '@mui/x-date-pickers/TimePicker'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +14,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose time/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose time/)); + expect(screen.getByRole('dialog')).to.have.class(dialogClasses.paper); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx b/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx new file mode 100644 index 000000000000..b6a2477f9853 --- /dev/null +++ b/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx @@ -0,0 +1,329 @@ +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import MuiTextField, { TextFieldProps } from '@mui/material/TextField'; +import MuiIconButton, { IconButtonProps } from '@mui/material/IconButton'; +import MuiInputAdornment, { InputAdornmentProps } from '@mui/material/InputAdornment'; +import { SvgIconProps } from '@mui/material/SvgIcon'; +import useSlotProps from '@mui/utils/useSlotProps'; +import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { FieldOwnerState } from '../../models'; +import { useFieldOwnerState } from '../hooks/useFieldOwnerState'; +import { usePickerTranslations } from '../../hooks'; +import { ClearIcon as MuiClearIcon } from '../../icons'; +import { useNullablePickerContext } from '../hooks/useNullablePickerContext'; +import type { UseFieldResponse } from '../hooks/useField'; +import { PickersTextField, PickersTextFieldProps } from '../../PickersTextField'; + +export const cleanFieldResponse = < + TFieldResponse extends UseFieldResponse, +>({ + enableAccessibleFieldDOMStructure, + ...fieldResponse +}: TFieldResponse): ExportedPickerFieldUIProps & { + openPickerAriaLabel: string; + textFieldProps: TextFieldProps | PickersTextFieldProps; +} => { + if (enableAccessibleFieldDOMStructure) { + const { + InputProps, + readOnly, + onClear, + clearable, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + ...other + } = fieldResponse; + + return { + clearable, + onClear, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + textFieldProps: { + ...other, + InputProps: { ...(InputProps ?? {}), readOnly }, + }, + }; + } + + const { + onPaste, + onKeyDown, + inputMode, + readOnly, + InputProps, + inputProps, + inputRef, + onClear, + clearable, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + ...other + } = fieldResponse; + + return { + clearable, + onClear, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + textFieldProps: { + ...other, + InputProps: { ...(InputProps ?? {}), readOnly }, + inputProps: { ...(inputProps ?? {}), inputMode, onPaste, onKeyDown, ref: inputRef }, + }, + }; +}; + +/** + * Adds the button to open the picker and the button to clear the value of the field. + * @ignore - internal component. + */ +export function PickerFieldUI(props: PickerFieldUIProps) { + const { slots, slotProps, fieldResponse } = props; + + const translations = usePickerTranslations(); + const pickerContext = useNullablePickerContext(); + const { + textFieldProps, + onClear, + clearable, + openPickerAriaLabel, + clearButtonPosition: clearButtonPositionProp = 'end', + openPickerButtonPosition: openPickerButtonPositionProp = 'end', + } = cleanFieldResponse(fieldResponse); + const ownerState = useFieldOwnerState(textFieldProps); + + const handleTogglePicker = useEventCallback(() => pickerContext?.setOpen((prev) => !prev)); + + const triggerStatus = pickerContext ? pickerContext.triggerStatus : 'hidden'; + const clearButtonPosition = clearable ? clearButtonPositionProp : null; + const openPickerButtonPosition = triggerStatus !== 'hidden' ? openPickerButtonPositionProp : null; + + const TextField = + slots.textField ?? + (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + + const InputAdornment = slots.inputAdornment ?? MuiInputAdornment; + const { ownerState: startInputAdornmentOwnerState, ...startInputAdornmentProps } = useSlotProps({ + elementType: InputAdornment, + externalSlotProps: slotProps?.inputAdornment, + additionalProps: { + position: 'start' as const, + }, + ownerState: { ...ownerState, position: 'start' }, + }); + const { ownerState: endInputAdornmentOwnerState, ...endInputAdornmentProps } = useSlotProps({ + elementType: InputAdornment, + externalSlotProps: slotProps?.inputAdornment, + additionalProps: { + position: 'end' as const, + }, + ownerState: { ...ownerState, position: 'end' }, + }); + + const OpenPickerButton = slots.openPickerButton ?? MuiIconButton; + // We don't want to forward the `ownerState` to the `` component, see mui/material-ui#34056 + const { + ownerState: openPickerButtonOwnerState, + ...openPickerButtonProps + }: IconButtonProps & { ownerState: any } = useSlotProps({ + elementType: OpenPickerButton, + externalSlotProps: slotProps?.openPickerButton, + additionalProps: { + disabled: triggerStatus === 'disabled', + onClick: handleTogglePicker, + 'aria-label': openPickerAriaLabel, + edge: + clearButtonPosition === 'start' && openPickerButtonPosition === 'start' + ? undefined + : openPickerButtonPosition, + }, + ownerState, + }); + + const OpenPickerIcon = slots.openPickerIcon; + const openPickerIconProps = useSlotProps({ + elementType: OpenPickerIcon, + externalSlotProps: slotProps?.openPickerIcon, + ownerState, + }); + + const ClearButton = slots.clearButton ?? MuiIconButton; + // We don't want to forward the `ownerState` to the `` component, see mui/material-ui#34056 + const { ownerState: clearButtonOwnerState, ...clearButtonProps } = useSlotProps({ + elementType: ClearButton, + externalSlotProps: slotProps?.clearButton, + className: 'clearButton', + additionalProps: { + title: translations.fieldClearLabel, + tabIndex: -1, + onClick: onClear, + edge: + clearButtonPosition === 'end' && openPickerButtonPosition === 'end' + ? undefined + : clearButtonPosition, + }, + ownerState, + }); + + const ClearIcon = slots.clearIcon ?? MuiClearIcon; + const clearIconProps = useSlotProps({ + elementType: ClearIcon, + externalSlotProps: slotProps?.clearIcon, + additionalProps: { + fontSize: 'small', + }, + ownerState, + }); + + if (!textFieldProps.InputProps) { + textFieldProps.InputProps = {}; + } + + if (pickerContext) { + textFieldProps.InputProps.ref = pickerContext.triggerRef; + } + + if ( + !textFieldProps.InputProps?.startAdornment && + (clearButtonPosition === 'start' || openPickerButtonPosition === 'start') + ) { + textFieldProps.InputProps.startAdornment = ( + + {openPickerButtonPosition === 'start' && ( + + + + )} + {clearButtonPosition === 'start' && ( + + + + )} + + ); + } + + if ( + !textFieldProps.InputProps?.endAdornment && + (clearButtonPosition === 'end' || openPickerButtonPosition === 'end') + ) { + textFieldProps.InputProps.endAdornment = ( + + {clearButtonPosition === 'end' && ( + + + + )} + {openPickerButtonPosition === 'end' && ( + + + + )} + + ); + } + + return ; +} + +export interface ExportedPickerFieldUIProps { + /** + * If `true`, a clear button will be shown in the field allowing value clearing. + * @default false + */ + clearable?: boolean; + /** + * Callback fired when the clear button is clicked. + */ + onClear?: React.MouseEventHandler; + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition?: 'start' | 'end'; + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition?: 'start' | 'end'; +} + +export interface PickerFieldUIProps { + /** + * Overridable component slots. + * @default {} + */ + slots: PickerFieldUISlots; + /** + * The props used for each component slot. + * @default {} + */ + slotProps?: PickerFieldUISlotProps; + /** + * Object returned by the `useField` hook or one of its wrapper (for example `useDateField`). + */ + fieldResponse: UseFieldResponse; +} + +interface PickerFieldUISlots { + /** + * Form control with an input to render the value. + * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. + */ + textField?: React.ElementType; + /** + * Component displayed on the start or end input adornment used to open the picker on desktop. + * @default InputAdornment + */ + inputAdornment?: React.ElementType; + /** + * Button to open the picker on desktop. + * @default IconButton + */ + openPickerButton?: React.ElementType; + /** + * Icon displayed in the open picker button on desktop. + */ + openPickerIcon: React.ElementType; + /** + * Icon to display inside the clear button. + * @default ClearIcon + */ + clearIcon?: React.ElementType; + /** + * Button to clear the value. + * @default IconButton + */ + clearButton?: React.ElementType; +} + +export interface ExportedPickerFieldUISlots + extends MakeOptional {} + +export interface PickerFieldUISlotProps { + textField?: SlotComponentPropsFromProps< + PickersTextFieldProps | TextFieldProps, + {}, + FieldOwnerState + >; + inputAdornment?: SlotComponentPropsFromProps< + InputAdornmentProps, + {}, + FieldInputAdornmentOwnerState + >; + openPickerButton?: SlotComponentPropsFromProps; + openPickerIcon?: SlotComponentPropsFromProps; + clearIcon?: SlotComponentPropsFromProps; + clearButton?: SlotComponentPropsFromProps; +} + +interface FieldInputAdornmentOwnerState extends FieldOwnerState { + position: 'start' | 'end'; +} diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index 0f558077d4fc..6c8f23c9393a 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -70,6 +70,18 @@ export interface PickerContextValue extends UsePickerValueContextValue { * Is always equal to "portrait" if the component you are accessing the ownerState from is not wrapped by a picker. */ orientation: PickerOrientation; + /** + * The ref that should be attached to the element that triggers the picker opening. + * When using a built-in field component, this property is automatically handled. + */ + triggerRef: React.RefObject; + /** + * The status of the element that triggers the picker opening. + * If it is "hidden", the field should not render the UI to open the picker. + * If it is "disabled", the field should render the UI to open the picker, but it should not have any behavior attached to it. + * If it is "enabled", the field should render the UI to open the picker and interacting with it should open the picker. + */ + triggerStatus: 'hidden' | 'disabled' | 'enabled'; } export interface PickerPrivateContextValue { /** diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx index 3ac24e4b6227..c8cdc20a92ec 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; -import MuiInputAdornment from '@mui/material/InputAdornment'; -import IconButton from '@mui/material/IconButton'; import useForkRef from '@mui/utils/useForkRef'; import useId from '@mui/utils/useId'; import { PickersPopper } from '../../components/PickersPopper'; @@ -29,7 +27,6 @@ export const useDesktopPicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseDesktopPickerParams) => { const { @@ -53,7 +50,6 @@ export const useDesktopPicker = < reduceAnimations, } = props; - const containerRef = React.useRef(null); const fieldRef = React.useRef>(null); const labelId = useId(); @@ -62,7 +58,6 @@ export const useDesktopPicker = < const { open, actions, - hasUIView, layoutProps, providerProps, renderCurrentView, @@ -79,36 +74,6 @@ export const useDesktopPicker = < variant: 'desktop', }); - const InputAdornment = slots.inputAdornment ?? MuiInputAdornment; - const { ownerState: inputAdornmentOwnerState, ...inputAdornmentProps } = useSlotProps({ - elementType: InputAdornment, - externalSlotProps: innerSlotProps?.inputAdornment, - additionalProps: { - position: 'end' as const, - }, - ownerState, - }); - - const OpenPickerButton = slots.openPickerButton ?? IconButton; - const { ownerState: openPickerButtonOwnerState, ...openPickerButtonProps } = useSlotProps({ - elementType: OpenPickerButton, - externalSlotProps: innerSlotProps?.openPickerButton, - additionalProps: { - disabled: disabled || readOnly, - onClick: open ? actions.onClose : actions.onOpen, - 'aria-label': getOpenDialogAriaText(pickerFieldProps.value), - edge: inputAdornmentProps.position, - }, - ownerState, - }); - - const OpenPickerIcon = slots.openPickerIcon; - const openPickerIconProps = useSlotProps({ - elementType: OpenPickerIcon, - externalSlotProps: innerSlotProps?.openPickerIcon, - ownerState, - }); - const Field = slots.field; const fieldProps: BaseSingleInputFieldProps< PickerValue, @@ -142,30 +107,6 @@ export const useDesktopPicker = < ownerState, }); - // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged - if (hasUIView) { - fieldProps.InputProps = { - ...fieldProps.InputProps, - ref: containerRef, - ...(!props.disableOpenPicker && { - [`${inputAdornmentProps.position}Adornment`]: ( - - - - - - ), - }), - } as typeof fieldProps.InputProps; - } - - const slotsForField = { - textField: slots.textField, - clearIcon: slots.clearIcon, - clearButton: slots.clearButton, - ...fieldProps.slots, - }; - const Layout = slots.layout ?? PickersLayout; let labelledById = labelId; @@ -188,6 +129,16 @@ export const useDesktopPicker = < }, }; + const slotsForField = { + ...slots, + ...fieldProps.slots, + }; + + const slotPropsForField = { + ...slotProps, + ...fieldProps.slotProps, + }; + const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( @@ -195,13 +146,13 @@ export const useDesktopPicker = < @@ -37,7 +25,7 @@ export interface UseDesktopPickerSlots 'desktopPaper' | 'desktopTransition' | 'desktopTrapFocus' | 'popper' >, ExportedPickersLayoutSlots, - UseClearableFieldSlots { + ExportedPickerFieldUISlots { /** * Component used to enter the date with the keyboard. */ @@ -47,49 +35,27 @@ export interface UseDesktopPickerSlots * @default TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`. */ textField?: React.ElementType; - /** - * Component displayed on the start or end input adornment used to open the picker on desktop. - * @default InputAdornment - */ - inputAdornment?: React.ElementType; - /** - * Button to open the picker on desktop. - * @default IconButton - */ - openPickerButton?: React.ElementType; - /** - * Icon displayed in the open picker button on desktop. - */ - openPickerIcon: React.ElementType; } -export interface UseDesktopPickerSlotProps< - TView extends DateOrTimeViewWithMeridiem, - TEnableAccessibleFieldDOMStructure extends boolean, -> extends ExportedUseDesktopPickerSlotProps, - Pick, 'toolbar'> {} - export interface ExportedUseDesktopPickerSlotProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersPopperSlotProps, ExportedPickersLayoutSlotProps, - UseClearableFieldSlotProps { + PickerFieldUISlotProps { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; - inputAdornment?: SlotComponentPropsFromProps; - openPickerButton?: SlotComponentPropsFromProps; - openPickerIcon?: SlotComponentPropsFromProps, {}, PickerOwnerState>; } +export interface UseDesktopPickerSlotProps< + TView extends DateOrTimeViewWithMeridiem, + TEnableAccessibleFieldDOMStructure extends boolean, +> extends ExportedUseDesktopPickerSlotProps, + Pick, 'toolbar'> {} + export interface DesktopOnlyPickerProps extends BaseNonStaticPickerProps, BaseNonRangeNonStaticPickerProps, @@ -135,5 +101,4 @@ export interface UseDesktopPickerParams< 'valueManager' | 'valueType' | 'validator' | 'rendererInterceptor' > { props: TExternalProps; - getOpenDialogAriaText: (date: PickerValidDate | null) => string; } diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 8bec0c4e9afa..636146760326 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -52,6 +52,7 @@ export const useField = < fieldValueManager, valueManager, validator, + getOpenDialogAriaLabel: getOpenDialogAriaText, } = params; const isRtl = useRtl(); @@ -279,9 +280,15 @@ export const useField = < clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled), }; + const openPickerAriaLabel = React.useMemo( + () => getOpenDialogAriaText(state.value), + [getOpenDialogAriaText, state.value], + ); + const commonAdditionalProps: UseFieldCommonAdditionalProps = { disabled, readOnly, + openPickerAriaLabel, }; return { diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts index e28638917817..cead4431240f 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts @@ -18,8 +18,8 @@ import type { Validator } from '../../../validation'; import type { UseFieldStateResponse } from './useFieldState'; import type { UseFieldCharacterEditingResponse } from './useFieldCharacterEditing'; import { PickersSectionElement, PickersSectionListRef } from '../../../PickersSectionList'; -import { ExportedUseClearableFieldProps } from '../../../hooks/useClearableField'; import { FormProps, InferNonNullablePickerValue, PickerValidValue } from '../../models'; +import type { ExportedPickerFieldUIProps } from '../../components/PickerFieldUI'; export interface UseFieldParams< TValue extends PickerValidValue, @@ -34,6 +34,7 @@ export interface UseFieldParams< fieldValueManager: FieldValueManager; validator: Validator, TInternalProps>; valueType: PickerValueType; + getOpenDialogAriaLabel: (value: TValue) => string; } export interface UseFieldInternalProps< @@ -122,9 +123,15 @@ export interface UseFieldInternalProps< } export interface UseFieldCommonAdditionalProps - extends Required, 'disabled' | 'readOnly'>> {} + extends Required, 'disabled' | 'readOnly'>> { + /** + * The aria label to set on the button that opens the picker. + */ + openPickerAriaLabel: string; +} -export interface UseFieldCommonForwardedProps extends ExportedUseClearableFieldProps { +export interface UseFieldCommonForwardedProps + extends Pick { onKeyDown?: React.KeyboardEventHandler; error?: boolean; } diff --git a/packages/x-date-pickers/src/internals/hooks/useGetOpenDialogAriaLabel.ts b/packages/x-date-pickers/src/internals/hooks/useGetOpenDialogAriaLabel.ts new file mode 100644 index 000000000000..b5accebc5589 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/useGetOpenDialogAriaLabel.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { usePickerTranslations } from '../../hooks'; +import { AdapterFormats } from '../../models'; +import { PickerValue } from '../models'; +import { useUtils } from './useUtils'; + +export const useGetOpenDialogAriaLabel = (params: { + formatKey: keyof AdapterFormats; + translationKey: 'openDatePickerDialogue' | 'openTimePickerDialogue'; +}) => { + const utils = useUtils(); + const translations = usePickerTranslations(); + const { formatKey, translationKey } = params; + + return React.useCallback( + (value: PickerValue) => { + const formattedValue = + value !== null && utils.isValid(value) ? utils.format(value, formatKey) : null; + return translations[translationKey](formattedValue); + }, + [formatKey, translationKey, translations, utils], + ); +}; diff --git a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx index fc1ef14d56be..34dba00cc177 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -5,7 +5,6 @@ import useId from '@mui/utils/useId'; import { PickersModalDialog } from '../../components/PickersModalDialog'; import { UseMobilePickerParams, UseMobilePickerProps } from './useMobilePicker.types'; import { usePicker } from '../usePicker'; -import { onSpaceOrEnter } from '../../utils/utils'; import { PickersLayout } from '../../../PickersLayout'; import { FieldRef, InferError } from '../../../models'; import { BaseSingleInputFieldProps, DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; @@ -28,7 +27,6 @@ export const useMobilePicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseMobilePickerParams) => { const { @@ -47,6 +45,7 @@ export const useMobilePicker = < inputRef, readOnly, disabled, + autoFocus, localeText, } = props; @@ -83,7 +82,7 @@ export const useMobilePicker = < externalSlotProps: innerSlotProps?.field, additionalProps: { // Internal props - readOnly: readOnly ?? true, + readOnly, disabled, format, formatDensity, @@ -91,6 +90,7 @@ export const useMobilePicker = < selectedSections, onSelectedSectionsChange, timezone, + autoFocus: autoFocus && !props.open, ...pickerFieldProps, // onChange and value // Forwarded props @@ -98,27 +98,13 @@ export const useMobilePicker = < sx, label, name, + focused: open ? true : undefined, ...(isToolbarHidden && { id: labelId }), - ...(!(disabled || readOnly) && { - onClick: actions.onOpen, - onKeyDown: onSpaceOrEnter(actions.onOpen), - }), ...(!!inputRef && { inputRef }), }, ownerState, }); - // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged - fieldProps.inputProps = { - ...fieldProps.inputProps, - 'aria-label': getOpenDialogAriaText(pickerFieldProps.value), - } as typeof fieldProps.inputProps; - - const slotsForField = { - textField: slots.textField, - ...fieldProps.slots, - }; - const Layout = slots.layout ?? PickersLayout; let labelledById = labelId; @@ -141,6 +127,16 @@ export const useMobilePicker = < }, }; + const slotsForField = { + ...slots, + ...fieldProps.slots, + }; + + const slotPropsForField = { + ...slotProps, + ...fieldProps.slotProps, + }; + const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( @@ -148,7 +144,7 @@ export const useMobilePicker = < diff --git a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts index edbfeef65b9d..37dea729d592 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts @@ -1,5 +1,4 @@ import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { BaseNonStaticPickerProps, @@ -11,12 +10,7 @@ import { PickersModalDialogSlotProps, } from '../../components/PickersModalDialog'; import { UsePickerParams } from '../usePicker'; -import { - FieldOwnerState, - PickerFieldSlotProps, - PickerOwnerState, - PickerValidDate, -} from '../../../models'; +import { PickerFieldSlotProps, PickerOwnerState } from '../../../models'; import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, @@ -25,38 +19,30 @@ import { import { UsePickerValueNonStaticProps } from '../usePicker/usePickerValue.types'; import { UsePickerViewsProps } from '../usePicker/usePickerViews'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; -import { PickersTextFieldProps } from '../../../PickersTextField'; import { UsePickerProviderNonStaticProps } from '../usePicker/usePickerProvider'; +import { ExportedPickerFieldUISlots, PickerFieldUISlotProps } from '../../components/PickerFieldUI'; export interface UseMobilePickerSlots extends PickersModalDialogSlots, - ExportedPickersLayoutSlots { + ExportedPickersLayoutSlots, + ExportedPickerFieldUISlots { /** * Component used to enter the date with the keyboard. */ field: React.ElementType; - /** - * Form control with an input to render the value inside the default field. - * @default TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`. - */ - textField?: React.ElementType; } export interface ExportedUseMobilePickerSlotProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersModalDialogSlotProps, - ExportedPickersLayoutSlotProps { + ExportedPickersLayoutSlotProps, + PickerFieldUISlotProps { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; } export interface UseMobilePickerSlotProps< @@ -104,5 +90,4 @@ export interface UseMobilePickerParams< 'valueManager' | 'valueType' | 'validator' > { props: TExternalProps; - getOpenDialogAriaText: (date: PickerValidDate | null) => string; } diff --git a/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts b/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts new file mode 100644 index 000000000000..a6600ee37c67 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts @@ -0,0 +1,9 @@ +'use client'; +import * as React from 'react'; +import { PickerContext } from '../components/PickerProvider'; + +/** + * Returns the context passed by the picker that wraps the current component. + * If the context is not found, returns `null`. + */ +export const useNullablePickerContext = () => React.useContext(PickerContext); diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 8fff61acb1c9..d0a5d726cd26 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -70,7 +70,6 @@ export const usePicker = < // Picker views renderCurrentView: pickerViewsResponse.renderCurrentView, - hasUIView: pickerViewsResponse.provider.hasUIView, shouldRestoreFocus: pickerViewsResponse.shouldRestoreFocus, // Picker layout diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts index d77e131106eb..40a8ce569347 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts @@ -68,6 +68,4 @@ export interface UsePickerResponse< providerProps: UsePickerProviderReturnValue; layoutProps: UsePickerValueResponse['layoutProps'] & UsePickerViewsResponse['layoutProps']; - // TODO v8: Remove in https://github.com/mui/mui-x/pull/15671 - hasUIView: boolean; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts index 10a3f4f0a50a..5790da77fb18 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -75,6 +75,7 @@ export function usePickerProvider< const utils = useUtils(); const orientation = usePickerOrientation(paramsFromUsePickerViews.views, props.orientation); + const triggerRef = React.useRef(null); const ownerState = React.useMemo( () => ({ @@ -101,6 +102,18 @@ export function usePickerProvider< ], ); + const triggerStatus = React.useMemo(() => { + if (props.disableOpenPicker || !paramsFromUsePickerViews.hasUIView) { + return 'hidden'; + } + + if (props.disabled || props.readOnly) { + return 'disabled'; + } + + return 'enabled'; + }, [props.disableOpenPicker, paramsFromUsePickerViews.hasUIView, props.disabled, props.readOnly]); + const contextValue = React.useMemo( () => ({ ...paramsFromUsePickerValue.contextValue, @@ -108,8 +121,17 @@ export function usePickerProvider< readOnly: props.readOnly ?? false, variant, orientation, + triggerRef, + triggerStatus, }), - [paramsFromUsePickerValue.contextValue, variant, orientation, props.disabled, props.readOnly], + [ + paramsFromUsePickerValue.contextValue, + variant, + orientation, + props.disabled, + props.readOnly, + triggerStatus, + ], ); const privateContextValue = React.useMemo( @@ -128,7 +150,7 @@ export interface UsePickerProviderParameters< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, > extends Pick { - props: UsePickerProps; + props: UsePickerProps & UsePickerProviderNonStaticProps; valueManager: PickerValueManager; variant: PickerVariant; paramsFromUsePickerValue: UsePickerValueProviderParams; diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index d96774401a83..e6b71e857677 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -4,6 +4,12 @@ export type { PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps, } from './components/PickersArrowSwitcher'; +export { PickerFieldUI, cleanFieldResponse } from './components/PickerFieldUI'; +export type { + ExportedPickerFieldUIProps, + ExportedPickerFieldUISlots, + PickerFieldUISlotProps, +} from './components/PickerFieldUI'; export { PickerProvider } from './components/PickerProvider'; export { PickersModalDialog } from './components/PickersModalDialog'; export type { @@ -132,7 +138,6 @@ export type { PickerValidValue, } from './models/value'; -export { convertFieldResponseIntoMuiTextFieldProps } from './utils/convertFieldResponseIntoMuiTextFieldProps'; export { applyDefaultDate, replaceInvalidDateByNull, diff --git a/packages/x-date-pickers/src/internals/models/fields.ts b/packages/x-date-pickers/src/internals/models/fields.ts index b7476ca12f04..c1b59a019815 100644 --- a/packages/x-date-pickers/src/internals/models/fields.ts +++ b/packages/x-date-pickers/src/internals/models/fields.ts @@ -1,20 +1,21 @@ import { SxProps } from '@mui/material/styles'; import { MakeRequired } from '@mui/x-internals/types'; -import type { - ExportedUseClearableFieldProps, - UseClearableFieldSlotProps, - UseClearableFieldSlots, -} from '../../hooks/useClearableField'; import type { FieldSection, PickerOwnerState } from '../../models'; import type { UseFieldInternalProps } from '../hooks/useField'; import { RangePosition } from './pickers'; import { PickerValidValue } from './value'; +import type { + ExportedPickerFieldUIProps, + ExportedPickerFieldUISlots, + PickerFieldUISlotProps, +} from '../components/PickerFieldUI'; export interface FieldRangeSection extends FieldSection { dateName: RangePosition; } -export interface BaseForwardedSingleInputFieldProps extends ExportedUseClearableFieldProps { +export interface BaseForwardedSingleInputFieldProps + extends Pick { className: string | undefined; sx: SxProps | undefined; label: React.ReactNode | undefined; @@ -25,16 +26,8 @@ export interface BaseForwardedSingleInputFieldProps extends ExportedUseClearable onBlur?: React.FocusEventHandler; ref?: React.Ref; inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - inputProps?: { - 'aria-label'?: string; - }; - slots?: UseClearableFieldSlots; - slotProps?: UseClearableFieldSlotProps & { + slots?: ExportedPickerFieldUISlots; + slotProps?: PickerFieldUISlotProps & { textField?: {}; }; ownerState: PickerOwnerState; diff --git a/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts b/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts deleted file mode 100644 index 7124c455941a..000000000000 --- a/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TextFieldProps } from '@mui/material/TextField'; -import { UseFieldResponse } from '../hooks/useField'; - -export const convertFieldResponseIntoMuiTextFieldProps = < - TFieldResponse extends UseFieldResponse, ->({ - enableAccessibleFieldDOMStructure, - ...fieldResponse -}: TFieldResponse): TextFieldProps => { - if (enableAccessibleFieldDOMStructure) { - const { InputProps, readOnly, ...other } = fieldResponse; - - return { - ...other, - InputProps: { ...(InputProps ?? {}), readOnly }, - } as any; - } - - const { onPaste, onKeyDown, inputMode, readOnly, InputProps, inputProps, inputRef, ...other } = - fieldResponse; - - return { - ...other, - InputProps: { ...(InputProps ?? {}), readOnly }, - inputProps: { ...(inputProps ?? {}), inputMode, onPaste, onKeyDown, ref: inputRef }, - } as any; -}; diff --git a/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts b/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts index 598e90d7079e..a6cf8e68da86 100644 --- a/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts +++ b/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts @@ -1,4 +1,3 @@ -import { AdapterFormats, MuiPickersAdapter, PickerValidDate } from '../../models'; import { PickersLocaleText } from './pickersLocaleTextApi'; export const getPickersLocalization = (pickersTranslations: Partial) => { @@ -12,19 +11,3 @@ export const getPickersLocalization = (pickersTranslations: Partial string; - propsTranslation: ((formattedValue: string | null) => string) | undefined; -}) => { - const { utils, formatKey, contextTranslation, propsTranslation } = params; - - return (value: PickerValidDate | null) => { - const formattedValue = - value !== null && utils.isValid(value) ? utils.format(value, formatKey) : null; - const translation = propsTranslation ?? contextTranslation; - return translation(formattedValue); - }; -}; diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index 05c0c4e0460f..9a2bf3fd3ca1 100644 --- a/packages/x-date-pickers/src/models/fields.ts +++ b/packages/x-date-pickers/src/models/fields.ts @@ -1,9 +1,5 @@ import * as React from 'react'; import { TextFieldProps } from '@mui/material/TextField'; -import type { - ExportedUseClearableFieldProps, - UseClearableFieldResponse, -} from '../hooks/useClearableField'; import { ExportedPickersSectionListProps } from '../PickersSectionList'; import type { UseFieldInternalProps, UseFieldResponse } from '../internals/hooks/useField'; import type { PickersTextFieldProps } from '../PickersTextField'; @@ -14,6 +10,7 @@ import { PickerValidValue, } from '../internals/models'; import { PickerOwnerState } from './pickers'; +import type { ExportedPickerFieldUIProps } from '../internals/components/PickerFieldUI'; // Update PickersComponentAgnosticLocaleText -> viewNames when adding new entries export type FieldSectionType = @@ -150,7 +147,7 @@ export interface FieldOwnerState extends PickerOwnerState { export type PickerFieldSlotProps< TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, -> = ExportedUseClearableFieldProps & +> = ExportedPickerFieldUIProps & Pick< UseFieldInternalProps, 'shouldRespectLeadingZeros' | 'readOnly' @@ -160,13 +157,20 @@ export type PickerFieldSlotProps< }; /** - * Props the text field receives when used with a single input picker. + * Props the text field receives when used inside a single input picker. * Only contains what the MUI components are passing to the text field, not what users can pass using the `props.slotProps.field` and `props.slotProps.textField`. */ export type BaseSingleInputPickersTextFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, -> = UseClearableFieldResponse< - UseFieldResponse +> = Omit< + UseFieldResponse, + | 'slots' + | 'slotProps' + | 'clearable' + | 'onClear' + | 'openPickerButtonPosition' + | 'clearButtonPosition' + | 'openPickerAriaLabel' >; /** diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index f9145efca503..5925c48e52d2 100644 --- a/test/e2e/index.test.ts +++ b/test/e2e/index.test.ts @@ -13,7 +13,6 @@ import { WebError, Locator, } from '@playwright/test'; -import { pickersTextFieldClasses } from '@mui/x-date-pickers/PickersTextField'; import { pickersSectionListClasses } from '@mui/x-date-pickers/PickersSectionList'; function sleep(timeoutMS: number): Promise { @@ -799,19 +798,13 @@ async function initializeEnvironment( it('should allow selecting a value', async () => { await renderFixture('DatePicker/BasicMobileDatePicker'); - // Old selector: await page.getByRole('textbox').click({ position: { x: 10, y: 2 } }); - await page - .locator(`.${pickersTextFieldClasses.root}`) - .click({ position: { x: 10, y: 2 } }); + await page.getByRole('button').click(); await page.getByRole('gridcell', { name: '11' }).click(); await page.getByRole('button', { name: 'OK' }).click(); - await waitFor(async () => { - // assert that the dialog has been closed and the focused element is the input - expect(await page.evaluate(() => document.activeElement?.className)).to.contain( - pickersSectionListClasses.sectionContent, - ); - }); + // assert that the dialog closes after selection is complete + // could run into race condition otherwise + await page.waitForSelector('[role="dialog"]', { state: 'detached' }); expect(await page.getByRole('textbox', { includeHidden: true }).inputValue()).to.equal( '04/11/2022', ); @@ -824,7 +817,7 @@ async function initializeEnvironment( const input = page.getByRole('textbox'); - await input.click({ position: { x: 10, y: 2 } }); + await page.getByRole('button').click(); await page.getByRole('button', { name: 'Clear' }).click(); await input.blur(); diff --git a/test/utils/pickers/describePicker/describePicker.tsx b/test/utils/pickers/describePicker/describePicker.tsx index 4332a4c15710..82638cd3f052 100644 --- a/test/utils/pickers/describePicker/describePicker.tsx +++ b/test/utils/pickers/describePicker/describePicker.tsx @@ -62,9 +62,7 @@ function innerDescribePicker(ElementToTest: React.ElementType, options: Describe />, ); - const shouldRenderOpenPickerIcon = !hasNoView && variant !== 'mobile'; - - expect(queryAllByTestId('component-test')).to.have.length(shouldRenderOpenPickerIcon ? 1 : 0); + expect(queryAllByTestId('component-test')).to.have.length(hasNoView ? 0 : 1); }); }); diff --git a/test/utils/pickers/describeValue/describeValue.types.ts b/test/utils/pickers/describeValue/describeValue.types.ts index c1cb3b358d3f..91e42ace1a1c 100644 --- a/test/utils/pickers/describeValue/describeValue.types.ts +++ b/test/utils/pickers/describeValue/describeValue.types.ts @@ -29,6 +29,7 @@ export type DescribeValueOptions< > = DescribeValueBaseOptions & (C extends 'picker' ? OpenPickerParams & { + variant: 'desktop' | 'mobile'; setNewValue: ( value: InferNonNullablePickerValue, options: { diff --git a/test/utils/pickers/describeValue/testControlledUnControlled.tsx b/test/utils/pickers/describeValue/testControlledUnControlled.tsx index 272360c609c0..77be94a9414a 100644 --- a/test/utils/pickers/describeValue/testControlledUnControlled.tsx +++ b/test/utils/pickers/describeValue/testControlledUnControlled.tsx @@ -157,11 +157,13 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); }); - it('should not allow editing with keyboard in mobile pickers', () => { + it('should allow editing in field on non-range mobile pickers', () => { if (componentFamily !== 'picker' || params.variant !== 'mobile') { return; } + const hasMobileFieldEditing = ['time', 'date', 'date-time'].includes(params.type); + const handleChange = spy(); const v7Response = renderWithProps({ @@ -170,7 +172,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); v7Response.selectSection(undefined); fireUserEvent.keyPress(v7Response.getActiveSection(0), { key: 'ArrowUp' }); - expect(handleChange.callCount).to.equal(0); + expect(handleChange.callCount).to.equal(hasMobileFieldEditing ? 1 : 0); }); it('should have correct labelledby relationship when toolbar is shown', () => { diff --git a/test/utils/pickers/misc.ts b/test/utils/pickers/misc.ts index 76e9ba1d5068..8fe2f45957bb 100644 --- a/test/utils/pickers/misc.ts +++ b/test/utils/pickers/misc.ts @@ -24,7 +24,7 @@ const getChangeCountForComponentFamily = (componentFamily: PickerComponentFamily export const getExpectedOnChangeCount = ( componentFamily: PickerComponentFamily, - params: OpenPickerParams, + params: OpenPickerParams & { variant: 'desktop' | 'mobile' }, ) => { if (componentFamily === 'digital-clock') { return getChangeCountForComponentFamily(componentFamily); diff --git a/test/utils/pickers/openPicker.ts b/test/utils/pickers/openPicker.ts index b34715ce5f8b..1a4285de7b39 100644 --- a/test/utils/pickers/openPicker.ts +++ b/test/utils/pickers/openPicker.ts @@ -5,11 +5,9 @@ import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; export type OpenPickerParams = | { type: 'date' | 'date-time' | 'time'; - variant: 'mobile' | 'desktop'; } | { type: 'date-range' | 'date-time-range'; - variant: 'mobile' | 'desktop'; initialFocus: 'start' | 'end'; /** * @default false @@ -36,12 +34,6 @@ export const openPicker = (params: OpenPickerParams) => { return true; } - if (params.variant === 'mobile') { - fireEvent.click(fieldSectionsContainer); - - return true; - } - const target = params.type === 'time' ? screen.getByLabelText(/choose time/i)