diff --git a/docs/data/date-pickers/base-concepts/base-concepts.md b/docs/data/date-pickers/base-concepts/base-concepts.md index ca61ae0cbe0f0..37ee4a4c2c584 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 3260e904fba9b..fb8974424621a 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -24,16 +24,7 @@ const cacheRtl = createCache({ function ButtonDateTimeField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -54,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 39689b23af0fc..fc6da7e2e11bb 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx @@ -28,16 +28,7 @@ const cacheRtl = createCache({ function ButtonDateTimeField(props: DateTimePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -58,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 11f5b168ae3fc..f4a7c867b747a 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 a218b603030fb..75545b14ce8cc 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 adca0c631ed59..7743be94934f5 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js @@ -94,13 +94,13 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { const startTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: { position: 'start' }, }); const endTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: { position: 'end' }, }); const fieldResponse = useMultiInputDateRangeField({ @@ -119,6 +119,21 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { unstableEndFieldRef, }); + const { + // The multi input range field do not support clearable and onClear + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + // The multi input range field do not support clearable and onClear + 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 7477ed13aaa04..b5c605dc0ac8f 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx @@ -106,7 +106,11 @@ interface BrowserMultiInputDateRangeFieldProps DateRangePickerFieldProps, 'unstableFieldRef' | 'clearable' | 'onClear' >, - MultiInputFieldRefs {} + MultiInputFieldRefs { + slotProps: { + textField: any; + }; +} type BrowserMultiInputDateRangeFieldComponent = (( props: BrowserMultiInputDateRangeFieldProps & React.RefAttributes, @@ -130,13 +134,13 @@ const BrowserMultiInputDateRangeField = React.forwardRef( const startTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: { position: 'start' } as any, }) as MultiInputFieldSlotTextFieldProps; const endTextFieldProps = useSlotProps({ elementType: 'input', externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: { position: 'end' } as any, }) as MultiInputFieldSlotTextFieldProps; const fieldResponse = useMultiInputDateRangeField< @@ -158,6 +162,21 @@ const BrowserMultiInputDateRangeField = React.forwardRef( unstableEndFieldRef, }); + const { + // The multi input range field do not support clearable and onClear + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + // The multi input range field do not support clearable and onClear + 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 ae55770f60785..5c2fdcc30f448 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 68f5f4c21737b..bcb591ba663af 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 e445dc0c0fb50..6efa50f33ba40 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(); @@ -26,27 +34,40 @@ 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, - slotProps, + // The rest can be passed to the root element + 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 8cddb46983906..97d8c85b4be5d 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(); @@ -32,28 +40,44 @@ 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, - slotProps, + + // The rest can be passed to the root element + 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 af4a282bd9b6f..6adab583bfa13 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js @@ -118,13 +118,13 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { const startTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: { position: 'start' }, }); const endTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: { position: 'end' }, }); const fieldResponse = useMultiInputDateRangeField({ @@ -143,11 +143,26 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { unstableEndFieldRef, }); + const { + // The multi input range field do not support clearable, onClear and openPickerAriaLabel + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + // The multi input range field do not support clearable, onClear and openPickerAriaLabel + onClear: onClearEndDate, + clearable: isEndDateClearable, + openPickerAriaLabel: openPickerEndDateAriaLabel, + ...endDateProps + } = fieldResponse.endDate; + return ( - + - + ); }); diff --git a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx index f7d9fa0bd3e29..9f5cbc60eff72 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx @@ -132,7 +132,11 @@ interface JoyMultiInputDateRangeFieldProps DateRangePickerFieldProps, 'unstableFieldRef' | 'clearable' | 'onClear' >, - MultiInputFieldRefs {} + MultiInputFieldRefs { + slotProps: { + textField: any; + }; +} type JoyMultiInputDateRangeFieldComponent = (( props: JoyMultiInputDateRangeFieldProps & React.RefAttributes, @@ -156,13 +160,13 @@ const JoyMultiInputDateRangeField = React.forwardRef( const startTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, + ownerState: { position: 'start' } as any, }) as MultiInputFieldSlotTextFieldProps; const endTextFieldProps = useSlotProps({ elementType: FormControl, externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, + ownerState: { position: 'end' } as any, }) as MultiInputFieldSlotTextFieldProps; const fieldResponse = useMultiInputDateRangeField< @@ -184,11 +188,26 @@ const JoyMultiInputDateRangeField = React.forwardRef( unstableEndFieldRef, }); + const { + // The multi input range field do not support clearable, onClear and openPickerAriaLabel + onClear: onClearStartDate, + clearable: isStartDateClearable, + openPickerAriaLabel: openPickerStartDateAriaLabel, + ...startDateProps + } = fieldResponse.startDate; + const { + // The multi input range field do not support clearable, onClear and openPickerAriaLabel + onClear: onClearEndDate, + clearable: isEndDateClearable, + openPickerAriaLabel: openPickerEndDateAriaLabel, + ...endDateProps + } = fieldResponse.endDate; + return ( - + - + ); }, diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js index 7e0c7f6ed10a7..eadea36d43b24 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', ); @@ -38,13 +40,12 @@ 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 07b8927cc1820..31da6b6756efe 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', ); @@ -50,13 +51,12 @@ 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 32bc8082bad18..324a11699329c 100644 --- a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js @@ -1,8 +1,9 @@ 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'; @@ -13,18 +14,16 @@ function AutocompleteField(props) { const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); const { timezone, value, setValue } = usePickerContext(); const { - InputProps, - slotProps, - slots, ownerState, label, focused, name, options = [], - inputProps, ...other } = forwardedProps; + const pickerContext = usePickerContext(); + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, @@ -32,50 +31,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 43870edeb6e9a..e4884ca5b144d 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 { @@ -24,18 +25,16 @@ function AutocompleteField(props: AutocompleteFieldProps) { const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); const { timezone, value, setValue } = usePickerContext(); const { - InputProps, - slotProps, - slots, ownerState, label, focused, name, options = [], - inputProps, ...other } = forwardedProps; + const pickerContext = usePickerContext(); + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, @@ -43,50 +42,39 @@ 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 e9ae897f8f41a..d6690a9fee09a 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js @@ -12,16 +12,7 @@ import { function ButtonDateField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -42,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 28793f7cad3c7..56af8ea31b4dc 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx @@ -16,16 +16,7 @@ import { function ButtonDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -46,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 201f32fa8344f..84c9ce3f03eb0 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js @@ -14,16 +14,7 @@ import { function ButtonDateRangeField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -45,7 +36,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 cb66e4a2935a5..135ddbd46ec80 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx @@ -18,16 +18,7 @@ import { function ButtonDateRangeField(props: DateRangePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -49,7 +40,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 2c7f4751d7e87..4ee50736a08b2 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,6 +2,8 @@ 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'; @@ -11,6 +13,7 @@ import { 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; @@ -27,9 +30,7 @@ function getInputValueFromValue(value, format) { } function MaskedDateField(props) { - const { slots, slotProps, ...other } = props; - - const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -131,9 +132,22 @@ function MaskedDateField(props) { return ( + 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 918c1c302ef32..7e61770e25ad6 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 { @@ -15,6 +17,7 @@ import { 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; @@ -31,9 +34,7 @@ function getInputValueFromValue(value: Dayjs | null, format: string) { } function MaskedDateField(props: DatePickerFieldProps) { - const { slots, slotProps, ...other } = props; - - const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -135,9 +136,22 @@ function MaskedDateField(props: DatePickerFieldProps) { return ( + 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 0000000000000..a386d83c03392 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.js @@ -0,0 +1,74 @@ +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 pickerContext = usePickerContext(); + + const parsedFormat = useParsedFormat(); + const { hasValidationError } = useValidation({ + validator: validateDate, + value: pickerContext.value, + timezone: pickerContext.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 0000000000000..3b3e0e22bdc96 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-read-only-mobile-text-field/MaterialDatePicker.tsx @@ -0,0 +1,78 @@ +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 pickerContext = usePickerContext(); + + const parsedFormat = useParsedFormat(); + const { hasValidationError } = useValidation({ + validator: validateDate, + value: pickerContext.value, + timezone: pickerContext.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 0000000000000..e3842a12cb5d3 --- /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 213c1df27df35..c1dbf31a52867 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 @@ -13,7 +13,6 @@ import { CalendarIcon } from '@mui/x-date-pickers/icons'; function ReadOnlyDateField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { InputProps, slotProps, slots, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -26,7 +25,7 @@ function ReadOnlyDateField(props) { return ( , 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 16f52863cc24a..ab5c6c38f0cc7 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 @@ -17,7 +17,6 @@ import { CalendarIcon } from '@mui/x-date-pickers/icons'; function ReadOnlyDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { InputProps, slotProps, slots, ...other } = forwardedProps; const pickerContext = usePickerContext(); const parsedFormat = useParsedFormat(); @@ -30,7 +29,7 @@ function ReadOnlyDateField(props: DatePickerFieldProps) { return ( , sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, diff --git a/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.js new file mode 100644 index 0000000000000..24f16cc407460 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.js @@ -0,0 +1,86 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +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 { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +function CustomDateField(props) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + + const pickerContext = usePickerContext(); + const placeholder = useParsedFormat(); + const [inputValue, setInputValue] = useInputValue(); + + // Check if the current value is valid or not. + const { hasValidationError } = useValidation({ + value: pickerContext.value, + timezone: pickerContext.timezone, + props: internalProps, + validator: validateDate, + }); + + const handleChange = (event) => { + const newInputValue = event.target.value; + const newValue = dayjs(newInputValue, pickerContext.fieldFormat); + setInputValue(newInputValue); + pickerContext.setValue(newValue); + }; + + return ( + + ); +} + +function useInputValue() { + const pickerContext = usePickerContext(); + const [lastValueProp, setLastValueProp] = React.useState(pickerContext.value); + const [inputValue, setInputValue] = React.useState(() => + createInputValue(pickerContext.value, pickerContext.fieldFormat), + ); + + if (lastValueProp !== pickerContext.value) { + setLastValueProp(pickerContext.value); + if (pickerContext.value && pickerContext.value.isValid()) { + setInputValue( + createInputValue(pickerContext.value, pickerContext.fieldFormat), + ); + } + } + + return [inputValue, setInputValue]; +} + +function createInputValue(value, format) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function CustomFieldDatePicker(props) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx new file mode 100644 index 0000000000000..9d5f1711c88c6 --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +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 { + useSplitFieldProps, + useParsedFormat, + usePickerContext, +} from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +function CustomDateField(props: DatePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + + const pickerContext = usePickerContext(); + const placeholder = useParsedFormat(); + const [inputValue, setInputValue] = useInputValue(); + + // Check if the current value is valid or not. + const { hasValidationError } = useValidation({ + value: pickerContext.value, + timezone: pickerContext.timezone, + props: internalProps, + validator: validateDate, + }); + + const handleChange = (event: React.ChangeEvent) => { + const newInputValue = event.target.value; + const newValue = dayjs(newInputValue, pickerContext.fieldFormat); + setInputValue(newInputValue); + pickerContext.setValue(newValue); + }; + + return ( + + ); +} + +function useInputValue() { + const pickerContext = usePickerContext(); + const [lastValueProp, setLastValueProp] = React.useState(pickerContext.value); + const [inputValue, setInputValue] = React.useState(() => + createInputValue(pickerContext.value, pickerContext.fieldFormat), + ); + + if (lastValueProp !== pickerContext.value) { + setLastValueProp(pickerContext.value); + if (pickerContext.value && pickerContext.value.isValid()) { + setInputValue( + createInputValue(pickerContext.value, pickerContext.fieldFormat), + ); + } + } + + return [inputValue, setInputValue] as const; +} + +function createInputValue(value: Dayjs | null, format: string) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function CustomFieldDatePicker(props: DatePickerProps) { + return ( + + ); +} + +export default function MaterialDatePicker() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx.preview b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx.preview new file mode 100644 index 0000000000000..63be53d3e536f --- /dev/null +++ b/docs/data/date-pickers/custom-field/behavior-tutorial/MaterialDatePicker.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index e30850de8502d..197857b20fc4f 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 have a read-only TextField on mobile, you can conditionally render the custom field presented in the previous section: + +{{"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 @@ -154,3 +160,157 @@ and you don't want the UI to look like a Text Field, you can replace the field w The same logic can be applied to any Range Picker: {{"demo": "behavior-button/MaterialDateRangePicker.js", "defaultCodeOpen": false}} + +## Build your own custom field + +:::success +The sections below show how to build a field for your Picker. +Unlike the field components exposed by `@mui/x-date-pickers` and `@mui/x-date-pickers-pro`, those fields are not suitable for a standalone usage. +::: + +### Typing + +Each Picker component exposes an interface describing the props it passes to its field. +You can import it from the same endpoint as the Picker component and use it to type the props of your field: + +```tsx +import { DatePickerFieldProps } from '@mui/x-date-pickers/DatePicker'; +import { DateRangePickerFieldProps } from '@mui/x-date-pickers-pro/DateRangePicker'; + +function CustomDateField(props: DatePickerFieldProps) { + // Your custom field +} + +function CustomDateRangeField(props: DateRangePickerFieldProps) { + // Your custom field +} +``` + +#### Import + +| Picker component | Field props interface | +| ---------------------: | :------------------------------ | +| Date Picker | `DatePickerFieldProps` | +| Time Picker | `TimePickerFieldProps` | +| Date Time Picker | `DateTimePickerFieldProps` | +| Date Range Picker | `DateRangePickerFieldProps` | +| Date Time Range Picker | `DateTimeRangePickerFieldProps` | + +### Validation + +You can use the `useValidation` hook to check if the current value passed to your field is valid or not: + +```ts +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +const { + // The error associated with the current value. + // For example: "minDate" if `props.value < props.minDate`. + validationError, + // `true` if the value is invalid. + // On range Pickers it is true if the start date or the end date is invalid. + hasValidationError, + // Imperatively get the error of a value. + getValidationErrorForNewValue, +} = useValidation({ + // If you have a value in an internal state, you should pass it here. + // Otherwise, you can pass the value returned by `usePickerContext()`. + value, + timezone, + props, + validator: validateDate, +}); +``` + +#### Import + +Each Picker component has a validator adapted to its value type: + +| Picker component | Import validator | +| ---------------------: | :--------------------------------------------------------------------------- | +| Date Picker | `import { validateDate } from '@mui/x-date-pickers/validation'` | +| Time Picker | `import { validateTime } from '@mui/x-date-pickers/validation'` | +| Date Time Picker | `import { validateDateTime } from '@mui/x-date-pickers/validation'` | +| Date Range Picker | `import { validateDateRange } from '@mui/x-date-pickers-pro/validation'` | +| Date Time Range Picker | `import { validateDateTimeRange } from '@mui/x-date-pickers-pro/validation'` | + +### Localized placeholder + +You can use the `useParsedFormat` to get a clean placeholder. +This hook applies two main transformations on the format: + +1. It replaces all the localized tokens (for example `L` for a date with `dayjs`) with their expanded value (`DD/MM/YYYY` for the same date with `dayjs`). +2. It replaces each token with its token from the localization object (for example `YYYY` remains `YYYY` for the English locale but becomes `AAAA` for the French locale). + +:::warning +The format returned by `useParsedFormat` cannot be parsed by your date library. +::: + +```js +import { useParsedFormat } from '@mui/x-date-pickers/hooks'; + +// Uses the format defined by your Picker +const parsedFormat = useParsedFormat(); + +// Uses the custom format provided +const parsedFormat = useParsedFormat({ format: 'MM/DD/YYYY' }); +``` + +### Spread props to the DOM + +The field receives a lot of props that cannot be forwarded to the DOM element without warnings. +You can use the `useSplitFieldProps` hook to get the props that can be forwarded safely to the DOM: + +```tsx +const { internalProps, forwardedProps } = useSplitFieldProps( + // The props received by the field component + props, + // The value type ("date", "time" or "date-time") + 'date', +); + +return ( + +) +``` + +:::success +The `forwardedProps` contains the `sx` which is specific to MUI. +You can omit it if the component your are forwarding the props to does not support this concept: + +```jsx +const { sx, ...other } = props; +const { internalProps, forwardedProps } = useSplitFieldProps(other, 'date'); + +return ( + +) +``` + +::: + +### Pass the field to the Picker + +You can pass your custom field to your Picker using the `field` slot: + +```tsx +function DatePickerWithCustomField() { + return ( + + ) +} + +// Also works with the other variants of the component +function DesktopDatePickerWithCustomField() { + return ( + + ) +} + +``` + +### Full custom example + +Here is a live demo of the example created in all the previous sections: + +{{"demo": "behavior-tutorial/MaterialDatePicker.js", "defaultCodeOpen": false}} diff --git a/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js b/docs/data/date-pickers/custom-opening-button/CustomPropsOpeningButton.js index 5ecabf3b7b204..5331d61230248 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 5ecabf3b7b204..5331d61230248 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 685e218097c64..1487b57d2a908 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 0000000000000..04a1a2803acd9 --- /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 0000000000000..04a1a2803acd9 --- /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 0000000000000..0a9731df02dd7 --- /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 c0ef70d69eb9f..eb5061b66a5c7 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 position the opening button at the start or the end of the input: + +{{"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 8467168363613..f702279ebb37d 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 43e680df8d33a..227d9b971b183 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 does not allow editing values with the keyboard in the field. - 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 075ec25dd1567..dcead399e2fc0 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 92137b72ba818..3b94e397925fb 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 does not allow editing values with the keyboard in the field. - 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 d6db6b9d53c99..3332371393684 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 e581a783ba8f8..d8b17b13012ec 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -262,6 +262,20 @@ const theme = createTheme({ }); ``` +### Field editing on mobile Pickers + +The field is now editable if rendered inside a mobile Picker. +Before v8, if rendered inside a mobile Picker, the field was read-only, and clicking anywhere on it would open the Picker. +The mobile and desktop Pickers now behave similarly: + +- clicking on the field allows editing the value with the keyboard +- clicking on the input 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. +::: + ### Month Calendar To simplify the theme and class structure, the `` component has been moved inside the Month Calendar component. @@ -357,6 +371,82 @@ If the updated values do not fit your use case, you can [override them](/x/react ### 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" + + aria-label={fieldResponse.openPickerAriaLabel} + + > + + + + + + + + ), + + }} + /> + ); + } + ``` + + 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 ( + + ); + } + ``` + - The component passed to the `field` slot no longer receives the `value`, `onChange`, `timezone`, `format` and `disabled` props. You can use the `usePickerContext` hook instead: @@ -418,6 +508,21 @@ If the updated values do not fit your use case, you can [override them](/x/react If you are using a hook like `useDateField`, you don't have to do anything, the value from the context are automatically applied. ::: +### Slot: `inputAdornment` + +- The `position` props passed to the `inputAdornment` slot props no longer sets the position of the opening button. + This allows defining the position of the opening and clear buttons independently. + You can use the `openPickerButtonPosition` prop instead: + + ```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/charts/chart-data-provider.json b/docs/pages/x/api/charts/chart-data-provider.json index 2f7893bf38fec..dd5b141136850 100644 --- a/docs/pages/x/api/charts/chart-data-provider.json +++ b/docs/pages/x/api/charts/chart-data-provider.json @@ -1,7 +1,6 @@ { "props": { "colors": { "type": { "name": "any" }, "default": "blueberryTwilightPalette" }, - "dataset": { "type": { "name": "any" } }, "height": { "type": { "name": "any" } }, "highlightedItem": { "type": { "name": "any" } }, "id": { "type": { "name": "any" } }, @@ -9,8 +8,7 @@ "onHighlightChange": { "type": { "name": "any" } }, "series": { "type": { "name": "any" } }, "skipAnimation": { "type": { "name": "any" } }, - "width": { "type": { "name": "any" } }, - "zAxis": { "type": { "name": "any" } } + "width": { "type": { "name": "any" } } }, "name": "ChartDataProvider", "imports": [ diff --git a/docs/pages/x/api/date-pickers/date-field.json b/docs/pages/x/api/date-pickers/date-field.json index 6a53307f3dc31..90efc47fd6603 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 8ddb6b01c5cc3..cadb7130bc609 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 840d432dd53a4..08b336b439cb0 100644 --- a/docs/pages/x/api/date-pickers/mobile-date-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-date-picker.json @@ -204,6 +204,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.", @@ -221,6 +233,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.", @@ -256,6 +274,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.", @@ -288,8 +317,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 bfc1d0d67b717..5547ade99e31d 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 @@ -223,6 +223,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.", @@ -240,6 +252,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.", @@ -275,6 +293,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.", @@ -313,8 +342,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 42e778868e5eb..30f368b62370c 100644 --- a/docs/pages/x/api/date-pickers/mobile-time-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-time-picker.json @@ -140,6 +140,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.", @@ -151,6 +163,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.", @@ -180,6 +198,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.", @@ -200,8 +229,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 179b58765222e..b12c8e6071cd9 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 483f9c0016493..119936cbddf19 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 fbfcdcd2781ec..ceeded9aa6802 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 fb99ace144ee2..3bb471d4c5f04 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 3a823c2c492c1..e90418b6c09ee 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -9,8 +9,7 @@ import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; function ButtonDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { InputProps, slotProps, slots, ownerState, label, focused, name, ...other } = - forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); @@ -36,7 +35,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/charts/chart-data-provider/chart-data-provider.json b/docs/translations/api-docs/charts/chart-data-provider/chart-data-provider.json index 0679bb83a3f88..ead6d2b938eec 100644 --- a/docs/translations/api-docs/charts/chart-data-provider/chart-data-provider.json +++ b/docs/translations/api-docs/charts/chart-data-provider/chart-data-provider.json @@ -2,9 +2,6 @@ "componentDescription": "Orchestrates the data providers for the chart components and hooks.\n\nUse this component if you have custom HTML components that need to access the chart data.", "propDescriptions": { "colors": { "description": "Color palette used to colorize multiple series." }, - "dataset": { - "description": "An array of objects that can be used to populate series and axes data using their dataKey property." - }, "height": { "description": "The height of the chart in px. If not defined, it takes the height of the parent element." }, @@ -26,8 +23,7 @@ }, "width": { "description": "The width of the chart in px. If not defined, it takes the width of the parent element." - }, - "zAxis": { "description": "The configuration of the z-axes." } + } }, "classDescriptions": {} } 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 08b247cab6671..6022893a1b8c9 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 30b020a97b080..3a6bc00dd54fb 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 d3bf52f7d9609..698da88d81492 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 e08489562eeb6..661fad9c125c6 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 8d76eacc2d628..10d1272f67d3d 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 72e27d93359fe..03f4bf83d3a7a 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 871afaaa25741..08f57b1fb609a 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 11ab48673c0fa..32b0d8c393e7b 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 376ab6e3e4225..9a6ba3f53aab4 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-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx index 8fb844eb2ca29..7cd6613028db6 100644 --- a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx +++ b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx @@ -6,16 +6,13 @@ import { Watermark } from '@mui/x-license/Watermark'; import { useLicenseVerifier } from '@mui/x-license/useLicenseVerifier'; import { ChartsSurface, ChartsSurfaceProps } from '@mui/x-charts/ChartsSurface'; import { ChartDataProvider, ChartDataProviderProps } from '@mui/x-charts/context'; -import { ChartSeriesType, UseChartCartesianAxisSignature } from '@mui/x-charts/internals'; +import { ChartSeriesType } from '@mui/x-charts/internals'; import { getReleaseInfo } from '../internals/utils/releaseInfo'; import { useChartContainerProProps } from './useChartContainerProProps'; -import { UseChartProZoomSignature } from '../internals/plugins/useChartProZoom/useChartProZoom.types'; +import { AllPluginSignatures } from '../internals/plugins/allPlugins'; export interface ChartContainerProProps - extends ChartDataProviderProps< - [UseChartCartesianAxisSignature, UseChartProZoomSignature], - TSeries - >, + extends ChartDataProviderProps>, ChartsSurfaceProps {} const releaseInfo = getReleaseInfo(); @@ -53,9 +50,7 @@ const ChartContainerPro = React.forwardRef(function ChartContainerProInner< useLicenseVerifier('x-charts-pro', releaseInfo); return ( - , UseChartProZoomSignature]> - {...chartDataProviderProProps} - > + > {...chartDataProviderProProps}> {children} diff --git a/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts index c5bb363ae5c70..3c127fb68a6eb 100644 --- a/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts +++ b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts @@ -1,26 +1,19 @@ 'use client'; import { ChartDataProviderProps, - ChartPlugin, ChartSeriesType, - useChartCartesianAxis, - UseChartCartesianAxisSignature, useChartContainerProps, UseChartContainerPropsReturnValue, } from '@mui/x-charts/internals'; import * as React from 'react'; import type { ChartContainerProProps } from './ChartContainerPro'; -import { useChartProZoom } from '../internals/plugins/useChartProZoom'; -import { UseChartProZoomSignature } from '../internals/plugins/useChartProZoom/useChartProZoom.types'; +import { ALL_PLUGINS, AllPluginsType, AllPluginSignatures } from '../internals/plugins/allPlugins'; export type UseChartContainerProPropsReturnValue = Pick< UseChartContainerPropsReturnValue, 'chartsSurfaceProps' | 'children' > & { - chartDataProviderProProps: ChartDataProviderProps< - [UseChartCartesianAxisSignature, UseChartProZoomSignature], - TSeries - >; + chartDataProviderProProps: ChartDataProviderProps>; }; export const useChartContainerProProps = ( @@ -30,10 +23,7 @@ export const useChartContainerProProps = , UseChartProZoomSignature], - TSeries - >, + ChartDataProviderProps>, 'initialZoom' | 'onZoomChange' > = { initialZoom, @@ -50,12 +40,7 @@ export const useChartContainerProProps = >, - // eslint-disable-next-line react-compiler/react-compiler - useChartProZoom, - ], + plugins: plugins ?? (ALL_PLUGINS as unknown as AllPluginsType), }, chartsSurfaceProps, children, diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx index 3fa127fd00ec9..011536b3a1e99 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; import { ChartsOverlay } from '@mui/x-charts/ChartsOverlay'; import { ScatterChartProps, ScatterPlot } from '@mui/x-charts/ScatterChart'; -import { ChartDataProvider, ZAxisContextProvider } from '@mui/x-charts/context'; +import { ChartDataProvider } from '@mui/x-charts/context'; import { ChartsVoronoiHandler } from '@mui/x-charts/ChartsVoronoiHandler'; import { ChartsAxis } from '@mui/x-charts/ChartsAxis'; import { ChartsGrid } from '@mui/x-charts/ChartsGrid'; @@ -39,7 +39,6 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro( const { chartsWrapperProps, chartContainerProps, - zAxisProps, voronoiHandlerProps, chartsAxisProps, gridProps, @@ -66,19 +65,17 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro( {!props.hideLegend && } - - {!props.disableVoronoi && } - - - - {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} - - - - - {!props.loading && } - {children} - + {!props.disableVoronoi && } + + + + {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} + + + + + {!props.loading && } + {children} diff --git a/packages/x-charts-pro/src/internals/plugins/allPlugins.ts b/packages/x-charts-pro/src/internals/plugins/allPlugins.ts new file mode 100644 index 0000000000000..f7c3b8feeb125 --- /dev/null +++ b/packages/x-charts-pro/src/internals/plugins/allPlugins.ts @@ -0,0 +1,30 @@ +// This file should be removed after creating all plugins in favor of a file per chart type. + +import { + ChartSeriesType, + ConvertSignaturesIntoPlugins, + useChartCartesianAxis, + UseChartCartesianAxisSignature, + useChartInteraction, + UseChartInteractionSignature, + useChartZAxis, + UseChartZAxisSignature, +} from '@mui/x-charts/internals'; +import { useChartProZoom, UseChartProZoomSignature } from './useChartProZoom'; + +export type AllPluginSignatures = [ + UseChartZAxisSignature, + UseChartCartesianAxisSignature, + UseChartInteractionSignature, + UseChartProZoomSignature, +]; + +export type AllPluginsType = + ConvertSignaturesIntoPlugins>; + +export const ALL_PLUGINS = [ + useChartZAxis, + useChartCartesianAxis, + useChartInteraction, + useChartProZoom, +]; diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProZoom/index.ts b/packages/x-charts-pro/src/internals/plugins/useChartProZoom/index.ts index 48ce05ccb5283..7cdc3e4f2cd33 100644 --- a/packages/x-charts-pro/src/internals/plugins/useChartProZoom/index.ts +++ b/packages/x-charts-pro/src/internals/plugins/useChartProZoom/index.ts @@ -1,2 +1,3 @@ export * from './useChartProZoom.selectors'; export * from './useChartProZoom'; +export * from './useChartProZoom.types'; diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 015aaab009bf6..cc7951aa287a4 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -5,14 +5,10 @@ import { ChartSeriesType } from '../models/seriesType/config'; import { ChartDataProvider, ChartDataProviderProps } from '../context/ChartDataProvider'; import { useChartContainerProps } from './useChartContainerProps'; import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface'; -import { UseChartCartesianAxisSignature } from '../internals/plugins/featurePlugins/useChartCartesianAxis'; -import { UseChartInteractionSignature } from '../internals/plugins/featurePlugins/useChartInteraction'; +import { AllPluginSignatures } from '../internals/plugins/allPlugins'; export interface ChartContainerProps - extends Omit< - ChartDataProviderProps<[UseChartCartesianAxisSignature], SeriesType>, - 'children' - >, + extends Omit>, 'children'>, ChartsSurfaceProps {} /** @@ -49,12 +45,7 @@ const ChartContainer = React.forwardRef(function ChartContainer, UseChartInteractionSignature] - > - {...chartDataProviderProps} - > + > {...chartDataProviderProps}> {children} ); diff --git a/packages/x-charts/src/ChartContainer/useChartContainerProps.ts b/packages/x-charts/src/ChartContainer/useChartContainerProps.ts index e45d511eb9713..9e9b14a3e064d 100644 --- a/packages/x-charts/src/ChartContainer/useChartContainerProps.ts +++ b/packages/x-charts/src/ChartContainer/useChartContainerProps.ts @@ -3,21 +3,11 @@ import * as React from 'react'; import { ChartsSurfaceProps } from '../ChartsSurface'; import { ChartDataProviderProps } from '../context/ChartDataProvider'; import type { ChartContainerProps } from './ChartContainer'; -import { - useChartCartesianAxis, - UseChartCartesianAxisSignature, -} from '../internals/plugins/featurePlugins/useChartCartesianAxis'; -import { - useChartInteraction, - UseChartInteractionSignature, -} from '../internals/plugins/featurePlugins/useChartInteraction'; import { ChartSeriesType } from '../models/seriesType/config'; +import { ALL_PLUGINS, AllPluginSignatures, AllPluginsType } from '../internals/plugins/allPlugins'; export type UseChartContainerPropsReturnValue = { - chartDataProviderProps: ChartDataProviderProps< - [UseChartCartesianAxisSignature, UseChartInteractionSignature], - TSeries - >; + chartDataProviderProps: ChartDataProviderProps>; chartsSurfaceProps: ChartsSurfaceProps & { ref: React.Ref }; children: React.ReactNode; }; @@ -58,10 +48,7 @@ export const useChartContainerProps = , UseChartInteractionSignature], - TSeries - >, + ChartDataProviderProps>, 'children' > = { margin, @@ -77,7 +64,7 @@ export const useChartContainerProps = , }; return { diff --git a/packages/x-charts/src/ChartsLegend/useAxis.ts b/packages/x-charts/src/ChartsLegend/useAxis.ts index 4ae98a2ad5e95..790fdca1536f5 100644 --- a/packages/x-charts/src/ChartsLegend/useAxis.ts +++ b/packages/x-charts/src/ChartsLegend/useAxis.ts @@ -1,7 +1,7 @@ 'use client'; import { AxisDefaultized } from '../models/axis'; import { ZAxisDefaultized } from '../models/z-axis'; -import { useZAxis } from '../hooks/useZAxis'; +import { useZAxes } from '../hooks/useZAxis'; import { useXAxes, useYAxes } from '../hooks/useAxis'; import { ColorLegendSelector } from './colorLegend.types'; @@ -14,7 +14,7 @@ export function useAxis({ }: ColorLegendSelector): ZAxisDefaultized | AxisDefaultized { const { xAxis, xAxisIds } = useXAxes(); const { yAxis, yAxisIds } = useYAxes(); - const { zAxis, zAxisIds } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); switch (axisDirection) { case 'x': { diff --git a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx index 0004b28032981..2a239a72afa4d 100644 --- a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx @@ -9,7 +9,7 @@ import { getLabel } from '../internals/getLabel'; import { isCartesianSeriesType } from '../internals/isCartesian'; import { utcFormatter } from './utils'; import { useXAxes, useXAxis, useYAxes, useYAxis } from '../hooks/useAxis'; -import { useZAxis } from '../hooks/useZAxis'; +import { useZAxes } from '../hooks/useZAxis'; import { selectorChartsInteractionXAxis, selectorChartsInteractionYAxis, @@ -53,7 +53,7 @@ export function useAxisTooltip(): UseAxisTooltipReturnValue | null { const { xAxis } = useXAxes(); const { yAxis } = useYAxes(); - const { zAxis, zAxisIds } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); const colorProcessors = useColorProcessor(); if (axisData === null) { diff --git a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx index 885856da21b0c..b8dc93fcfd80f 100644 --- a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx @@ -11,8 +11,8 @@ import { getLabel } from '../internals/getLabel'; import { selectorChartsInteractionItem } from '../internals/plugins/featurePlugins/useChartInteraction'; import { useSelector } from '../internals/store/useSelector'; import { useStore } from '../internals/store/useStore'; -import { useXAxes, useYAxes } from '../hooks'; -import { useZAxis } from '../hooks/useZAxis'; +import { useXAxes, useYAxes } from '../hooks/useAxis'; +import { useZAxes } from '../hooks/useZAxis'; import { ChartsLabelMarkProps } from '../ChartsLabel'; export interface UseItemTooltipReturnValue { @@ -32,7 +32,7 @@ export function useItemTooltip(): null | UseItemToolt const { xAxis, xAxisIds } = useXAxes(); const { yAxis, yAxisIds } = useYAxes(); - const { zAxis, zAxisIds } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); const colorProcessors = useColorProcessor(); const xAxisId = (series as any).xAxisId ?? xAxisIds[0]; diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index b93da945a0557..6261eeb7384a2 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -28,7 +28,6 @@ import { ChartsVoronoiHandlerProps, } from '../ChartsVoronoiHandler/ChartsVoronoiHandler'; import { ChartsGrid, ChartsGridProps } from '../ChartsGrid'; -import { ZAxisContextProvider, ZAxisContextProviderProps } from '../context/ZAxisContextProvider'; import { useScatterChartProps } from './useScatterChartProps'; import { useChartContainerProps } from '../ChartContainer/useChartContainerProps'; import { ChartDataProvider } from '../context'; @@ -50,7 +49,6 @@ export interface ScatterChartSlotProps export interface ScatterChartProps extends Omit, - Omit, Omit, Omit, Omit { @@ -114,7 +112,6 @@ const ScatterChart = React.forwardRef(function ScatterChart( const { chartsWrapperProps, chartContainerProps, - zAxisProps, voronoiHandlerProps, chartsAxisProps, gridProps, @@ -136,19 +133,17 @@ const ScatterChart = React.forwardRef(function ScatterChart( {!props.hideLegend && } - - {!props.disableVoronoi && } - - - - {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} - - - - - {!props.loading && } - {children} - + {!props.disableVoronoi && } + + + + {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} + + + + + {!props.loading && } + {children} diff --git a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx index e52f99bb7ac78..d50a68fe829c4 100644 --- a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx @@ -5,7 +5,7 @@ import { Scatter, ScatterProps } from './Scatter'; import getColor from './getColor'; import { useScatterSeries } from '../hooks/useSeries'; import { useXAxes, useYAxes } from '../hooks'; -import { useZAxis } from '../hooks/useZAxis'; +import { useZAxes } from '../hooks/useZAxis'; export interface ScatterPlotSlots { scatter?: React.JSXElementConstructor; @@ -43,7 +43,7 @@ function ScatterPlot(props: ScatterPlotProps) { const seriesData = useScatterSeries(); const { xAxis, xAxisIds } = useXAxes(); const { yAxis, yAxisIds } = useYAxes(); - const { zAxis, zAxisIds } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); if (seriesData === undefined) { return null; diff --git a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts index b600574bf1a67..44a579aeed422 100644 --- a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts +++ b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts @@ -6,7 +6,6 @@ import { ChartsLegendSlotExtension } from '../ChartsLegend'; import { ChartsOverlayProps } from '../ChartsOverlay'; import type { ChartsVoronoiHandlerProps } from '../ChartsVoronoiHandler'; import { ChartContainerProps } from '../ChartContainer'; -import { ZAxisContextProviderProps } from '../context'; import type { ScatterChartProps } from './ScatterChart'; import type { ScatterPlotProps } from './ScatterPlot'; import type { ChartsWrapperProps } from '../internals/components/ChartsWrapper'; @@ -59,13 +58,12 @@ export const useScatterChartProps = (props: ScatterChartProps) => { colors, xAxis, yAxis, + zAxis, highlightedItem, onHighlightChange, className, }; - const zAxisProps: Omit = { - zAxis, - }; + const voronoiHandlerProps: ChartsVoronoiHandlerProps = { voronoiMaxRadius, onItemClick: onItemClick as ChartsVoronoiHandlerProps['onItemClick'], @@ -116,7 +114,6 @@ export const useScatterChartProps = (props: ScatterChartProps) => { return { chartsWrapperProps, chartContainerProps, - zAxisProps, voronoiHandlerProps, chartsAxisProps, gridProps, diff --git a/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx b/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx index dc0ea9c6f8ef0..a2c4752fd3d71 100644 --- a/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx +++ b/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx @@ -3,25 +3,22 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useChartDataProviderProps } from './useChartDataProviderProps'; import { AnimationProvider, AnimationProviderProps } from '../AnimationProvider'; -import { ZAxisContextProvider, ZAxisContextProviderProps } from '../ZAxisContextProvider'; import { HighlightedProvider, HighlightedProviderProps } from '../HighlightedProvider'; import { ChartProvider, ChartProviderProps } from '../ChartProvider'; import { ChartSeriesType } from '../../models/seriesType/config'; import { ChartAnyPluginSignature } from '../../internals/plugins/models/plugin'; -import { UseChartCartesianAxisSignature } from '../../internals/plugins/featurePlugins/useChartCartesianAxis'; -import { UseChartInteractionSignature } from '../../internals/plugins/featurePlugins/useChartInteraction'; +import { AllPluginSignatures } from '../../internals/plugins/allPlugins'; export type ChartDataProviderProps< - TSignatures extends readonly ChartAnyPluginSignature[], TSeries extends ChartSeriesType = ChartSeriesType, + TSignatures extends readonly ChartAnyPluginSignature[] = AllPluginSignatures, > = Omit< - ZAxisContextProviderProps & - HighlightedProviderProps & + HighlightedProviderProps & AnimationProviderProps & - ChartProviderProps['pluginParams'], + ChartProviderProps['pluginParams'], 'children' > & - Pick, 'seriesConfig' | 'plugins'> & { + Pick, 'seriesConfig' | 'plugins'> & { children?: React.ReactNode; }; @@ -54,26 +51,16 @@ export type ChartDataProviderProps< */ function ChartDataProvider< TSeries extends ChartSeriesType = ChartSeriesType, - TSignatures extends readonly ChartAnyPluginSignature[] = [ - UseChartCartesianAxisSignature, - UseChartInteractionSignature, - ], ->(props: ChartDataProviderProps) { - const { - children, - zAxisContextProps, - highlightedProviderProps, - animationProviderProps, - chartProviderProps, - } = useChartDataProviderProps(props); + TSignatures extends readonly ChartAnyPluginSignature[] = AllPluginSignatures, +>(props: ChartDataProviderProps) { + const { children, highlightedProviderProps, animationProviderProps, chartProviderProps } = + useChartDataProviderProps(props); return ( - {...chartProviderProps}> - - - {children} - - + {...chartProviderProps}> + + {children} + ); } @@ -90,9 +77,6 @@ ChartDataProvider.propTypes = { * @default blueberryTwilightPalette */ colors: PropTypes.any, - /** - * An array of objects that can be used to populate series and axes data using their `dataKey` property. - */ dataset: PropTypes.any, /** * The height of the chart in px. If not defined, it takes the height of the parent element. @@ -135,10 +119,6 @@ ChartDataProvider.propTypes = { * The width of the chart in px. If not defined, it takes the width of the parent element. */ width: PropTypes.any, - /** - * The configuration of the z-axes. - */ - zAxis: PropTypes.any, } as any; export { ChartDataProvider }; diff --git a/packages/x-charts/src/context/ChartDataProvider/useChartDataProviderProps.ts b/packages/x-charts/src/context/ChartDataProvider/useChartDataProviderProps.ts index ab4b65e871ae0..6905d4c2b0937 100644 --- a/packages/x-charts/src/context/ChartDataProvider/useChartDataProviderProps.ts +++ b/packages/x-charts/src/context/ChartDataProvider/useChartDataProviderProps.ts @@ -1,6 +1,5 @@ 'use client'; import { useTheme } from '@mui/material/styles'; -import type { ZAxisContextProviderProps } from '../ZAxisContextProvider'; import type { ChartDataProviderProps } from './ChartDataProvider'; import { HighlightedProviderProps } from '../HighlightedProvider'; import { AnimationProviderProps } from '../AnimationProvider'; @@ -8,12 +7,13 @@ import { ChartProviderProps } from '../ChartProvider'; import { ChartAnyPluginSignature, MergeSignaturesProperty } from '../../internals/plugins/models'; import { ChartSeriesType } from '../../models/seriesType/config'; import { ChartCorePluginSignatures } from '../../internals/plugins/corePlugins'; +import { AllPluginSignatures } from '../../internals/plugins/allPlugins'; export const useChartDataProviderProps = < - TSignatures extends readonly ChartAnyPluginSignature[], TSeries extends ChartSeriesType = ChartSeriesType, + TSignatures extends readonly ChartAnyPluginSignature[] = AllPluginSignatures, >( - props: ChartDataProviderProps, + props: ChartDataProviderProps, ) => { const { apiRef, @@ -21,7 +21,6 @@ export const useChartDataProviderProps = < height, series, margin, - zAxis, colors, dataset, highlightedItem, @@ -35,7 +34,7 @@ export const useChartDataProviderProps = < const theme = useTheme(); - const chartProviderProps: Omit, 'children'> = { + const chartProviderProps: Omit, 'children'> = { plugins, seriesConfig, pluginParams: { @@ -58,11 +57,6 @@ export const useChartDataProviderProps = < skipAnimation, }; - const zAxisContextProps: Omit = { - zAxis, - dataset, - }; - const highlightedProviderProps: Omit = { highlightedItem, onHighlightChange, @@ -70,7 +64,6 @@ export const useChartDataProviderProps = < return { children, - zAxisContextProps, highlightedProviderProps, animationProviderProps, chartProviderProps, diff --git a/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx b/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx index 2bc17b87e380a..a7a8e623f4279 100644 --- a/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx +++ b/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx @@ -10,6 +10,7 @@ import { import { ChartSeriesConfig } from '../../internals/plugins/models/seriesConfig'; import { useChartCartesianAxis } from '../../internals/plugins/featurePlugins/useChartCartesianAxis'; import { useChartInteraction } from '../../internals/plugins/featurePlugins/useChartInteraction'; +import { useChartZAxis } from '../../internals/plugins/featurePlugins/useChartZAxis'; import { plugin as barPlugin } from '../../BarChart/plugin'; import { plugin as scatterPlugin } from '../../ScatterChart/plugin'; import { plugin as linePlugin } from '../../LineChart/plugin'; @@ -25,12 +26,12 @@ export const defaultSeriesConfig: ChartSeriesConfig<'bar' | 'scatter' | 'line' | // For consistency with the v7, the cartesian axes are set by default. // To remove them, you can provide a `plugins` props. -const defaultPlugins = [useChartCartesianAxis, useChartInteraction]; +const defaultPlugins = [useChartZAxis, useChartCartesianAxis, useChartInteraction]; function ChartProvider< - TSignatures extends readonly ChartAnyPluginSignature[], TSeriesType extends ChartSeriesType, ->(props: ChartProviderProps) { + TSignatures extends readonly ChartAnyPluginSignature[], +>(props: ChartProviderProps) { const { children, plugins = defaultPlugins as unknown as ConvertSignaturesIntoPlugins, @@ -38,7 +39,7 @@ function ChartProvider< seriesConfig = defaultSeriesConfig as ChartSeriesConfig, } = props; - const { contextValue } = useCharts(plugins, pluginParams, seriesConfig); + const { contextValue } = useCharts(plugins, pluginParams, seriesConfig); return {children}; } diff --git a/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts b/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts index 250ed16ae325e..da2c8eef42adb 100644 --- a/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts +++ b/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts @@ -9,6 +9,7 @@ import { import { ChartStore } from '../../internals/plugins/utils/ChartStore'; import { ChartCorePluginSignatures } from '../../internals/plugins/corePlugins'; import { ChartSeriesConfig } from '../../internals/plugins/models/seriesConfig'; +import { AllPluginSignatures } from '../../internals/plugins/allPlugins'; import { UseChartBaseProps } from '../../internals/store/useCharts.types'; import { ChartSeriesType } from '../../models/seriesType/config'; @@ -35,8 +36,8 @@ export type ChartContextValue< }; export interface ChartProviderProps< - TSignatures extends readonly ChartAnyPluginSignature[], TSeries extends ChartSeriesType = ChartSeriesType, + TSignatures extends readonly ChartAnyPluginSignature[] = AllPluginSignatures, > { /** * Array of plugins used to add features to the chart. diff --git a/packages/x-charts/src/context/ZAxisContextProvider.tsx b/packages/x-charts/src/context/ZAxisContextProvider.tsx deleted file mode 100644 index 67763d9f7cc68..0000000000000 --- a/packages/x-charts/src/context/ZAxisContextProvider.tsx +++ /dev/null @@ -1,141 +0,0 @@ -'use client'; -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { MakeOptional } from '@mui/x-internals/types'; -import { DatasetType } from '../models/seriesType/config'; -import { getColorScale, getOrdinalColorScale } from '../internals/colorScale'; -import { ZAxisConfig, ZAxisDefaultized } from '../models/z-axis'; - -export type ZAxisContextProviderProps = { - /** - * The configuration of the z-axes. - */ - zAxis?: MakeOptional[]; - /** - * An array of objects that can be used to populate series and axes data using their `dataKey` property. - */ - dataset?: DatasetType; - children: React.ReactNode; -}; - -type DefaultizedZAxisConfig = { - [axisId: string]: ZAxisDefaultized; -}; - -export const ZAxisContext = React.createContext<{ - /** - * Mapping from z-axis key to scaling configuration. - */ - zAxis: DefaultizedZAxisConfig; - /** - * The z-axes IDs sorted by order they got provided. - */ - zAxisIds: string[]; -}>({ zAxis: {}, zAxisIds: [] }); - -if (process.env.NODE_ENV !== 'production') { - ZAxisContext.displayName = 'ZAxisContext'; -} - -function ZAxisContextProvider(props: ZAxisContextProviderProps) { - const { zAxis: inZAxis, dataset, children } = props; - - const zAxis = React.useMemo( - () => - inZAxis?.map((axisConfig) => { - const dataKey = axisConfig.dataKey; - if (dataKey === undefined || axisConfig.data !== undefined) { - return axisConfig; - } - if (dataset === undefined) { - throw new Error('MUI X: z-axis uses `dataKey` but no `dataset` is provided.'); - } - return { - ...axisConfig, - data: dataset.map((d) => d[dataKey]), - }; - }), - [inZAxis, dataset], - ); - - const value = React.useMemo(() => { - const allZAxis: ZAxisConfig[] = - zAxis?.map((axis, index) => ({ id: `defaultized-z-axis-${index}`, ...axis })) ?? []; - - const completedZAxis: DefaultizedZAxisConfig = {}; - allZAxis.forEach((axis) => { - completedZAxis[axis.id] = { - ...axis, - colorScale: - axis.colorMap && - (axis.colorMap.type === 'ordinal' && axis.data - ? getOrdinalColorScale({ values: axis.data, ...axis.colorMap }) - : getColorScale( - axis.colorMap.type === 'continuous' - ? { min: axis.min, max: axis.max, ...axis.colorMap } - : axis.colorMap, - )), - }; - }); - - return { - zAxis: completedZAxis, - zAxisIds: allZAxis.map(({ id }) => id), - }; - }, [zAxis]); - - return {children}; -} - -ZAxisContextProvider.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - children: PropTypes.node, - /** - * An array of objects that can be used to populate series and axes data using their `dataKey` property. - */ - dataset: PropTypes.arrayOf(PropTypes.object), - /** - * The configuration of the z-axes. - */ - zAxis: PropTypes.arrayOf( - PropTypes.shape({ - colorMap: PropTypes.oneOfType([ - PropTypes.shape({ - colors: PropTypes.arrayOf(PropTypes.string).isRequired, - type: PropTypes.oneOf(['ordinal']).isRequired, - unknownColor: PropTypes.string, - values: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) - .isRequired, - ), - }), - PropTypes.shape({ - color: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string.isRequired), - PropTypes.func, - ]).isRequired, - max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), - min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), - type: PropTypes.oneOf(['continuous']).isRequired, - }), - PropTypes.shape({ - colors: PropTypes.arrayOf(PropTypes.string).isRequired, - thresholds: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, - ).isRequired, - type: PropTypes.oneOf(['piecewise']).isRequired, - }), - ]), - data: PropTypes.array, - dataKey: PropTypes.string, - id: PropTypes.string, - max: PropTypes.number, - min: PropTypes.number, - }), - ), -} as any; - -export { ZAxisContextProvider }; diff --git a/packages/x-charts/src/context/index.ts b/packages/x-charts/src/context/index.ts index c95a0ef9fb675..c3aa10f8cdee3 100644 --- a/packages/x-charts/src/context/index.ts +++ b/packages/x-charts/src/context/index.ts @@ -1,5 +1,3 @@ export * from './HighlightedProvider'; -export { ZAxisContextProvider } from './ZAxisContextProvider'; -export type { ZAxisContextProviderProps } from './ZAxisContextProvider'; export { ChartDataProvider } from './ChartDataProvider'; export type { ChartDataProviderProps } from './ChartDataProvider'; diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index 23b19a9b0780e..9bbe0ac753cef 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -2,6 +2,7 @@ export * from './useDrawingArea'; export * from './useChartId'; export * from './useScale'; export * from './useAxis'; +export * from './useZAxis'; export * from './useColorScale'; export * from './useSvgRef'; export { diff --git a/packages/x-charts/src/hooks/useColorScale.ts b/packages/x-charts/src/hooks/useColorScale.ts index 6ba810e43795d..76406894f13ce 100644 --- a/packages/x-charts/src/hooks/useColorScale.ts +++ b/packages/x-charts/src/hooks/useColorScale.ts @@ -1,7 +1,7 @@ 'use client'; import { AxisScaleComputedConfig, ScaleName } from '../models/axis'; import { useXAxes, useYAxes } from './useAxis'; -import { useZAxis } from './useZAxis'; +import { useZAxes } from './useZAxis'; export function useXColorScale( identifier?: number | string, @@ -26,7 +26,7 @@ export function useYColorScale( export function useZColorScale( identifier?: number | string, ): AxisScaleComputedConfig[S]['colorScale'] | undefined { - const { zAxis, zAxisIds } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); const id = typeof identifier === 'string' ? identifier : zAxisIds[identifier ?? 0]; diff --git a/packages/x-charts/src/hooks/useZAxis.ts b/packages/x-charts/src/hooks/useZAxis.ts index 7f99fda885da5..1107f59f70c71 100644 --- a/packages/x-charts/src/hooks/useZAxis.ts +++ b/packages/x-charts/src/hooks/useZAxis.ts @@ -1,8 +1,25 @@ 'use client'; -import * as React from 'react'; -import { ZAxisContext } from '../context/ZAxisContextProvider'; +import { useStore } from '../internals/store/useStore'; +import { + selectorChartZAxis, + UseChartZAxisSignature, +} from '../internals/plugins/featurePlugins/useChartZAxis'; +import { useSelector } from '../internals/store/useSelector'; -export const useZAxis = () => { - const data = React.useContext(ZAxisContext); - return data; -}; +export function useZAxes() { + const store = useStore<[UseChartZAxisSignature]>(); + const { axis: zAxis, axisIds: zAxisIds } = useSelector(store, selectorChartZAxis) ?? { + axis: {}, + axisIds: [], + }; + + return { zAxis, zAxisIds }; +} + +export function useZAxis(identifier?: number | string) { + const { zAxis, zAxisIds } = useZAxes(); + + const id = typeof identifier === 'string' ? identifier : zAxisIds[identifier ?? 0]; + + return zAxis[id]; +} diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx index 972c8872f1007..c53087db8d28a 100644 --- a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx @@ -3,7 +3,7 @@ import { useDrawingArea, useXAxes, useYAxes } from '../../../hooks'; import ChartsPiecewiseGradient from './ChartsPiecewiseGradient'; import ChartsContinuousGradient from './ChartsContinuousGradient'; import ChartsContinuousGradientObjectBound from './ChartsContinuousGradientObjectBound'; -import { useZAxis } from '../../../hooks/useZAxis'; +import { useZAxes } from '../../../hooks/useZAxis'; import { useChartGradientIdBuilder, useChartGradientIdObjectBoundBuilder, @@ -20,7 +20,7 @@ export function ChartsAxesGradients() { const { xAxis, xAxisIds } = useXAxes(); const { yAxis, yAxisIds } = useYAxes(); - const { zAxisIds, zAxis } = useZAxis(); + const { zAxis, zAxisIds } = useZAxes(); const filteredYAxisIds = yAxisIds.filter((axisId) => yAxis[axisId].colorMap !== undefined); const filteredXAxisIds = xAxisIds.filter((axisId) => xAxis[axisId].colorMap !== undefined); diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index 8b08bc54f2691..f970e13c70c87 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -17,6 +17,7 @@ export * from '../context/ChartDataProvider/useChartDataProviderProps'; export * from './plugins/corePlugins/useChartId'; export * from './plugins/corePlugins/useChartSeries'; export * from './plugins/corePlugins/useChartDimensions'; +export * from './plugins/featurePlugins/useChartZAxis'; export * from './plugins/featurePlugins/useChartCartesianAxis'; export * from './plugins/featurePlugins/useChartInteraction'; export * from './plugins/utils/selectors'; @@ -36,7 +37,6 @@ export * from './getScale'; // contexts -export * from '../context/ZAxisContextProvider'; export * from '../context/AnimationProvider'; export type * from '../context/context.types'; export { getAxisExtremum } from './plugins/featurePlugins/useChartCartesianAxis/getAxisExtremum'; diff --git a/packages/x-charts/src/internals/plugins/allPlugins.ts b/packages/x-charts/src/internals/plugins/allPlugins.ts index 1fd0a05f57325..e5af78e20ddc1 100644 --- a/packages/x-charts/src/internals/plugins/allPlugins.ts +++ b/packages/x-charts/src/internals/plugins/allPlugins.ts @@ -1,4 +1,23 @@ // This file should be removed after creating all plugins in favor of a file per chart type. -import { useChartInteraction } from './featurePlugins/useChartInteraction'; +import { ChartSeriesType } from '../../models/seriesType/config'; +import { + useChartCartesianAxis, + UseChartCartesianAxisSignature, +} from './featurePlugins/useChartCartesianAxis'; +import { + useChartInteraction, + UseChartInteractionSignature, +} from './featurePlugins/useChartInteraction'; +import { useChartZAxis, UseChartZAxisSignature } from './featurePlugins/useChartZAxis'; +import { ConvertSignaturesIntoPlugins } from './models/helpers'; -export const ALL_PLUGINS = [useChartInteraction] as const; +export type AllPluginSignatures = [ + UseChartZAxisSignature, + UseChartCartesianAxisSignature, + UseChartInteractionSignature, +]; + +export type AllPluginsType = + ConvertSignaturesIntoPlugins>; + +export const ALL_PLUGINS = [useChartZAxis, useChartCartesianAxis, useChartInteraction]; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/index.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/index.ts new file mode 100644 index 0000000000000..f6cc1b444b1b6 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/index.ts @@ -0,0 +1,3 @@ +export { useChartZAxis } from './useChartZAxis'; +export type * from './useChartZAxis.types'; +export * from './useChartZAxis.selectors'; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.selectors.ts new file mode 100644 index 0000000000000..e670f79866478 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.selectors.ts @@ -0,0 +1,7 @@ +import { ChartState } from '../../models/chart'; +import { createSelector } from '../../utils/selectors'; +import { UseChartZAxisSignature } from './useChartZAxis.types'; + +const selectRootState = (state: ChartState<[UseChartZAxisSignature]>) => state; + +export const selectorChartZAxis = createSelector([selectRootState], (state) => state.zAxis); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.ts new file mode 100644 index 0000000000000..08c68cf0be353 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.ts @@ -0,0 +1,100 @@ +'use client'; +import * as React from 'react'; +import { MakeOptional } from '@mui/x-internals/types'; +import { ChartPlugin } from '../../models'; +import { DatasetType } from '../../../../models/seriesType/config'; +import { UseChartZAxisSignature } from './useChartZAxis.types'; +import { ZAxisConfig, ZAxisDefaultized } from '../../../../models/z-axis'; +import { getColorScale, getOrdinalColorScale } from '../../../colorScale'; + +function addDefaultId(axisConfig: MakeOptional, defaultId: string): ZAxisConfig { + if (axisConfig.id !== undefined) { + return axisConfig as ZAxisConfig; + } + return { + id: defaultId, + ...axisConfig, + }; +} + +function processColorMap(axisConfig: ZAxisConfig) { + if (!axisConfig.colorMap) { + return axisConfig; + } + + return { + ...axisConfig, + colorScale: + axisConfig.colorMap.type === 'ordinal' && axisConfig.data + ? getOrdinalColorScale({ values: axisConfig.data, ...axisConfig.colorMap }) + : getColorScale( + axisConfig.colorMap.type === 'continuous' + ? { min: axisConfig.min, max: axisConfig.max, ...axisConfig.colorMap } + : axisConfig.colorMap, + ), + }; +} + +function getZAxisState(zAxis?: MakeOptional[], dataset?: DatasetType) { + if (!zAxis || zAxis.length === 0) { + return { axis: {}, axisIds: [] }; + } + + const zAxisLookup: Record = {}; + const axisIds: string[] = []; + + zAxis.forEach((axisConfig, index) => { + const dataKey = axisConfig.dataKey; + const defaultizedId = axisConfig.id ?? `defaultized-z-axis-${index}`; + if (dataKey === undefined || axisConfig.data !== undefined) { + zAxisLookup[defaultizedId] = processColorMap(addDefaultId(axisConfig, defaultizedId)); + axisIds.push(defaultizedId); + return; + } + if (dataset === undefined) { + throw new Error('MUI X: z-axis uses `dataKey` but no `dataset` is provided.'); + } + zAxisLookup[defaultizedId] = processColorMap( + addDefaultId( + { + ...axisConfig, + data: dataset.map((d) => d[dataKey]), + }, + defaultizedId, + ), + ); + axisIds.push(defaultizedId); + }); + + return { axis: zAxisLookup, axisIds }; +} + +export const useChartZAxis: ChartPlugin = ({ params, store }) => { + const { zAxis, dataset } = params; + + // The effect do not track any value defined synchronously during the 1st render by hooks called after `useChartZAxis` + // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + + store.update((prev) => ({ + ...prev, + zAxis: getZAxisState(zAxis, dataset), + })); + }, [zAxis, dataset, store]); + + return {}; +}; + +useChartZAxis.params = { + zAxis: true, + dataset: true, +}; + +useChartZAxis.getInitialState = (params) => ({ + zAxis: getZAxisState(params.zAxis, params.dataset), +}); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.types.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.types.ts new file mode 100644 index 0000000000000..6c91aa9320caf --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/useChartZAxis.types.ts @@ -0,0 +1,43 @@ +import { MakeOptional } from '@mui/x-internals/types'; +import { ChartPluginSignature } from '../../models'; +import { DatasetType } from '../../../../models/seriesType/config'; +import { AxisId } from '../../../../models/axis'; +import { ZAxisConfig, ZAxisDefaultized } from '../../../../models/z-axis'; + +type DefaultizedZAxisConfig = { + [axisId: string]: ZAxisDefaultized; +}; + +export interface UseChartZAxisParameters { + /** + * The configuration of the z-axes. + */ + zAxis?: MakeOptional[]; + /** + * An array of objects that can be used to populate series and axes data using their `dataKey` property. + */ + dataset?: DatasetType; +} + +export type UseChartZAxisDefaultizedParameters = UseChartZAxisParameters; + +export interface UseChartZAxisState { + zAxis: { + /** + * Mapping from z-axis key to scaling configuration. + */ + axis: DefaultizedZAxisConfig; + /** + * The z-axes IDs sorted by order they got provided. + */ + axisIds: AxisId[]; + }; +} + +export interface UseChartZAxisInstance {} + +export type UseChartZAxisSignature = ChartPluginSignature<{ + params: UseChartZAxisParameters; + defaultizedParams: UseChartZAxisDefaultizedParameters; + state: UseChartZAxisState; +}>; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/utils.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartZAxis/utils.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/x-charts/src/internals/store/useCharts.ts b/packages/x-charts/src/internals/store/useCharts.ts index f78a365697f5c..8d25137a9217a 100644 --- a/packages/x-charts/src/internals/store/useCharts.ts +++ b/packages/x-charts/src/internals/store/useCharts.ts @@ -36,8 +36,8 @@ export function useChartApiInitialization( let globalId = 0; export function useCharts< - TSignatures extends readonly ChartAnyPluginSignature[], TSeriesType extends ChartSeriesType, + TSignatures extends readonly ChartAnyPluginSignature[], >( inPlugins: ConvertSignaturesIntoPlugins, props: Partial>, 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 a79441429611e..2ad4f2155adf4 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 @@ -35,14 +35,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(); }); @@ -51,7 +51,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); }); @@ -167,7 +167,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(); @@ -178,7 +178,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(); @@ -235,7 +235,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); @@ -277,7 +277,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); @@ -310,7 +310,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Change the end date fireEvent.click(getPickerDay('3')); @@ -337,7 +337,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')); @@ -364,7 +364,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')!; @@ -402,7 +402,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')); @@ -458,7 +458,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(); @@ -491,7 +491,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) @@ -529,7 +529,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)); @@ -555,7 +555,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)); @@ -582,10 +582,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); @@ -606,10 +606,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); @@ -620,7 +620,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'); @@ -632,7 +632,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'); @@ -644,7 +644,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'); @@ -656,7 +656,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 f9255bdd65af6..0ece845e39397 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 e8537c5cfc1d3..4dea6eb3e1865 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 83fee1c9814b9..83e2457bd23bd 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 8304680152111..1b8a91aec129f 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 90a5dc4057ec1..fe4f71d11525d 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 cb1441aca093c..6d58b8a4bb59b 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 { @@ -163,8 +160,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); const TextField = slots?.textField ?? diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx index 26557e1ed3fb5..d74e569613ef4 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 { @@ -161,8 +158,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); const TextField = slots?.textField ?? diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx index d2d9bb8e6bf92..f71baf65aac82 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 { @@ -164,8 +161,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); const TextField = slots?.textField ?? diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx index 26edd8cc6ad0c..5ff8e081f95a2 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx @@ -1,16 +1,10 @@ '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 { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { DateRangeIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; import { SingleInputDateRangeFieldProps } from './SingleInputDateRangeField.types'; import { useSingleInputDateRangeField } from './useSingleInputDateRangeField'; import { FieldType } from '../models'; @@ -41,41 +35,29 @@ const SingleInputDateRangeField = React.forwardRef(function SingleInputDateRange name: 'MuiSingleInputDateRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputDateRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputDateRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputDateRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateRangeField.fieldType = 'single-input'; @@ -96,6 +78,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 027ffbfbdb1de..7828b1643c3c0 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, + PickerFieldUISlots, + 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 PickerFieldUISlots {} -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 3148b6e40b37b..af4f32912bb52 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts @@ -33,5 +33,6 @@ export const useSingleInputDateRangeField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx index 74a4ea87c9550..f004f28093d36 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx @@ -1,16 +1,10 @@ 'use client'; 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 { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { DateRangeIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; 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'; @@ -41,41 +35,29 @@ const SingleInputDateTimeRangeField = React.forwardRef(function SingleInputDateT name: 'MuiSingleInputDateTimeRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputDateTimeRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputDateTimeRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputDateTimeRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateTimeRangeField.fieldType = 'single-input'; @@ -101,6 +83,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 9624ffa1c73e0..d7f51a40420b5 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, + PickerFieldUISlots, + 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 PickerFieldUISlots {} -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 12835555e9a57..c65e16c92ab8f 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts @@ -33,5 +33,6 @@ export const useSingleInputDateTimeRangeField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx index 509cf32d2daf6..a2c1fbe79cf76 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx @@ -1,15 +1,9 @@ 'use client'; 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 { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { ClockIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { SingleInputTimeRangeFieldProps } from './SingleInputTimeRangeField.types'; import { useSingleInputTimeRangeField } from './useSingleInputTimeRangeField'; @@ -41,41 +35,29 @@ const SingleInputTimeRangeField = React.forwardRef(function SingleInputTimeRange name: 'MuiSingleInputTimeRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputTimeRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputTimeRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputTimeRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputTimeRangeField.fieldType = 'single-input'; @@ -101,6 +83,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 46b019e84e955..56fcb66f9b5f9 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, + PickerFieldUISlots, + 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 PickerFieldUISlots {} -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 bfa482d68eedf..2416c57b1bd69 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts @@ -33,5 +33,6 @@ export const useSingleInputTimeRangeField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; 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 d0b5285994826..1b6a0c70a0802 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 @@ -11,6 +11,7 @@ import { PickerProvider, PickerValue, PickerRangeValue, + PickerFieldUIContextProvider, } from '@mui/x-date-pickers/internals'; import { FieldRef, InferError } from '@mui/x-date-pickers/models'; import { @@ -58,7 +59,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); @@ -93,6 +93,9 @@ export const useDesktopRangePicker = < localeText, }); + // 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 (providerProps.contextValue.view) { initialView.current = providerProps.contextValue.view; @@ -156,7 +159,7 @@ export const useDesktopRangePicker = < pickerSlotProps: slotProps, pickerSlots: slots, fieldProps, - anchorRef, + anchorRef: providerProps.contextValue.triggerRef, startFieldRef, endFieldRef, singleInputFieldRef, @@ -179,24 +182,26 @@ export const useDesktopRangePicker = < ...enrichedFieldResponse.fieldPrivateContextValue, }} > - - - - - {renderCurrentView()} - - - + + + + + + {renderCurrentView()} + + + + ); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts index 3c48c357e8bd0..196673da40d24 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts @@ -110,7 +110,7 @@ export interface UseEnrichedRangePickerFieldPropsParams< TEnableAccessibleFieldDOMStructure, TError >; - anchorRef?: React.Ref; + anchorRef?: React.Ref; currentView?: TView | null; initialView?: TView; startFieldRef: React.RefObject | null>; @@ -310,10 +310,7 @@ const useSingleInputFieldSlotProps = < rangePosition, onRangePositionChange, singleInputFieldRef, - pickerSlots, - pickerSlotProps, fieldProps, - anchorRef, currentView, }: UseEnrichedRangePickerFieldPropsParams< true, @@ -380,32 +377,12 @@ const useSingleInputFieldSlotProps = < } }; - const slots = { - ...fieldProps.slots, - textField: pickerSlots?.textField, - clearButton: pickerSlots?.clearButton, - clearIcon: pickerSlots?.clearIcon, - }; - - const slotProps = { - ...fieldProps.slotProps, - textField: pickerSlotProps?.textField, - clearButton: pickerSlotProps?.clearButton, - clearIcon: pickerSlotProps?.clearIcon, - }; - const enrichedFieldProps: ReturnType = { ...fieldProps, - slots, - slotProps, label, unstableFieldRef: handleFieldRef, onKeyDown: onSpaceOrEnter(openPicker, fieldProps.onKeyDown), onBlur, - InputProps: { - ref: anchorRef, - ...fieldProps?.InputProps, - }, focused: contextValue.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 d6d2382e90148..459323308e98d 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 @@ -10,6 +10,7 @@ import { PickerProvider, PickerRangeValue, PickerValue, + PickerFieldUIContextProvider, } from '@mui/x-date-pickers/internals'; import { usePickerTranslations } from '@mui/x-date-pickers/hooks'; import { FieldRef, InferError } from '@mui/x-date-pickers/models'; @@ -90,8 +91,10 @@ export const useMobileRangePicker = < localeText, }); - 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, @@ -184,14 +187,16 @@ export const useMobileRangePicker = < ...enrichedFieldResponse.fieldPrivateContextValue, }} > - - - - - {renderCurrentView()} - - - + + + + + + {renderCurrentView()} + + + + ); 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 f3568f99ad13d..0000000000000 --- 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 d255c2e719c75..e2b0b8483f184 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 @@ -16,7 +16,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'; import { useDateRangeManager } from '../../../managers'; @@ -143,9 +142,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 f4a13c557de6d..840ef757f2e9e 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 @@ -16,7 +16,6 @@ import { validateDateTimeRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; import { DateTimeRangeValidationError } from '../../../models'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; import { useDateTimeRangeManager } from '../../../managers'; @@ -144,9 +143,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 ea881fb4349d3..33c54bae45ae1 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 @@ -16,7 +16,6 @@ import { validateTimeRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; import { TimeRangeValidationError } from '../../../models'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; import { useTimeRangeManager } from '../../../managers'; @@ -143,9 +142,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/managers/useDateRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts index ed4785f5e9ead..4e08aeaee8301 100644 --- a/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts +++ b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts @@ -34,6 +34,8 @@ export function useDateRangeManager '', }), [enableAccessibleFieldDOMStructure, dateSeparator], ); diff --git a/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts index 8e779c3230b8b..dd99267e4f869 100644 --- a/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts +++ b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts @@ -35,6 +35,8 @@ export function useDateTimeRangeManager '', }), [enableAccessibleFieldDOMStructure, dateSeparator], ); diff --git a/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts index a17555bb6b5e9..4563793170fec 100644 --- a/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts +++ b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts @@ -35,6 +35,8 @@ export function useTimeRangeManager '', }), [enableAccessibleFieldDOMStructure, dateSeparator], ); diff --git a/packages/x-date-pickers-pro/src/models/fields.ts b/packages/x-date-pickers-pro/src/models/fields.ts index 648dc4c4f277a..0fe3c8c4415ad 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 a7a085a71570e..fca2497ad6219 100644 --- a/packages/x-date-pickers/src/DateField/DateField.tsx +++ b/packages/x-date-pickers/src/DateField/DateField.tsx @@ -1,16 +1,12 @@ '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, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateFieldComponent = (( props: DateFieldProps & React.RefAttributes, @@ -34,40 +30,28 @@ const DateField = React.forwardRef(function DateField< name: 'MuiDateField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - additionalProps: { + const textFieldProps = useFieldTextFieldProps>( + { + slotProps, ref: inRef, + externalForwardedProps: other, }, - ownerState, - }) as DateFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + ); const fieldResponse = useDateField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - return ; + return ( + + ); }) as DateFieldComponent; DateField.propTypes = { @@ -86,6 +70,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 @@ -229,6 +219,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 7226c4ac65791..8a4d1c048b14b 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, + PickerFieldUISlots, +} 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 PickerFieldUISlots {} -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 49b1e6d8b57df..fcbd4f3c4261d 100644 --- a/packages/x-date-pickers/src/DateField/useDateField.ts +++ b/packages/x-date-pickers/src/DateField/useDateField.ts @@ -30,5 +30,6 @@ export const useDateField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; 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 3ad845d85883b..902c7f656cfbb 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,11 @@ import * as React from 'react'; import { expect } from 'chai'; 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 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx index 187f0ffe62568..1ee01deb4b4c7 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx @@ -1,16 +1,12 @@ '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 { 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, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateTimeFieldComponent = (( props: DateTimeFieldProps & @@ -38,40 +34,28 @@ const DateTimeField = React.forwardRef(function DateTimeField< name: 'MuiDateTimeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + DateTimeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as DateTimeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useDateTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - - return ; + return ( + + ); }) as DateTimeFieldComponent; DateTimeField.propTypes = { @@ -95,6 +79,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 @@ -266,6 +256,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 2c3964472c396..ec523e87383ab 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, + PickerFieldUISlots, +} 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 PickerFieldUISlots {} -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 5943673b20c9b..7884f56705e76 100644 --- a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts +++ b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts @@ -30,5 +30,6 @@ export const useDateTimeField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; 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 933f61c48f166..bd47d4b3f04db 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,11 @@ import * as React from 'react'; import { expect } from 'chai'; 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 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx index 874017a90b7b8..4ee42b330bc98 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'; import { PickerLayoutOwnerState } from '../PickersLayout'; import { PickersActionBarAction } from '../PickersActionBar'; @@ -42,7 +39,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 @@ -65,7 +61,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< format: resolveDateFormat(utils, defaultizedProps, false), yearsPerRow: defaultizedProps.yearsPerRow ?? 4, slots: { - openPickerIcon: CalendarIcon, field: DateField, ...defaultizedProps.slots, }, @@ -95,12 +90,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 1c55b5a4bbf50..9d63482b4442d 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(); @@ -104,7 +104,7 @@ describe('', () => { testSkipIf(isJSDOM)('should move the focus to the newly opened views', () => { render(); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(document.activeElement).to.have.text('2019'); fireEvent.click(screen.getByText('2020')); @@ -120,7 +120,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); @@ -128,7 +128,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(); @@ -236,7 +236,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // Select year fireEvent.click(screen.getByRole('radio', { name: '2025' })); @@ -263,7 +263,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).to.have.attribute('disabled'); }); @@ -276,7 +276,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).not.to.have.attribute('disabled'); }); @@ -289,7 +289,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).to.have.attribute('disabled'); }); @@ -302,7 +302,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).not.to.have.attribute('disabled'); }); @@ -347,7 +347,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 1c79140c48074..1bb38f834e57b 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 fef966c911ec2..d4bf5da32c03c 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx @@ -9,11 +9,9 @@ import { DateTimeField } from '../DateTimeField'; import { DesktopDateTimePickerProps } from './DesktopDateTimePicker.types'; import { useDateTimePickerDefaultizedProps } 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 { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; import { resolveDateTimeFormat, @@ -38,7 +36,6 @@ import { } 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'; const rendererInterceptor = function RendererInterceptor( props: PickerRendererInterceptorProps, @@ -112,7 +109,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 @@ -164,7 +160,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< slots: { field: DateTimeField, layout: DesktopDateTimePickerLayout, - openPickerIcon: CalendarIcon, ...defaultizedProps.slots, }, slotProps: { @@ -194,12 +189,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 fda2d1f68cf0f..f3b787bfe1473 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' })); @@ -81,7 +81,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 1370af67311b2..551a73fee6e39 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 01bcb39d8eccc..777a2806aeea7 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, @@ -20,7 +18,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 & @@ -43,7 +40,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 @@ -90,7 +86,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< views: shouldRenderTimeInASingleColumn ? ['hours' as TimeViewWithMeridiem] : views, slots: { field: TimeField, - openPickerIcon: ClockIcon, ...defaultizedProps.slots, }, slotProps: { @@ -116,12 +111,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 096830e2565b8..e1fca7f54c978 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); @@ -104,7 +104,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '2 hours' })); expect(onChange.callCount).to.equal(1); @@ -142,7 +142,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '15 minutes' })); expect(onChange.callCount).to.equal(1); @@ -185,7 +185,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 700a92a4156ff..77caadcfb15c8 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 c4f5013abc991..4ec445f96f690 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 3f40a6ad1f1f4..a9f01953e7dba 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 586d963955a2d..1c6255189b9e3 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 2a75c950b93df..ed5371703071e 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 c0410f5d2499a..93cc5ca8ed812 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx @@ -7,7 +7,6 @@ import { singleItemValueManager } from '../internals/utils/valueManagers'; import { DateTimeField } from '../DateTimeField'; import { MobileDateTimePickerProps } from './MobileDateTimePicker.types'; import { 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'; @@ -15,7 +14,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'; import { PickerViewRendererLookup } from '../internals/hooks/usePicker/usePickerViews'; import { PickerValue } from '../internals/models'; @@ -40,7 +38,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 @@ -97,12 +94,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 16a032c454a06..34f64d19cfe3e 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'; import { hasTouchSupport, testSkipIf } from 'test/utils/skipIf'; @@ -84,17 +83,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(); - }); - testSkipIf(!hasTouchSupport)('should call onChange when selecting each view', () => { const onChange = spy(); const onAccept = spy(); @@ -111,7 +99,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 30ae63e192e42..88975bfea2bd1 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 e36e6cf1cd416..f73e78b7562fe 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 b902e6808b17b..a673288bd284e 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx @@ -8,7 +8,6 @@ import { adapterToUse, openPicker, getClockTouchEvent, - getFieldSectionsContainer, } from 'test/utils/pickers'; import { testSkipIf, hasTouchSupport } from 'test/utils/skipIf'; @@ -16,17 +15,6 @@ 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( @@ -61,7 +49,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 5cff45ba0c9ce..cb211de30b1dc 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 3ab127d6bafa6..992c33dc6decd 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 e99c0e4166965..c92a4ac60e896 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.tsx +++ b/packages/x-date-pickers/src/TimeField/TimeField.tsx @@ -1,16 +1,12 @@ '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, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { ClockIcon } from '../icons'; type TimeFieldComponent = (( props: TimeFieldProps & React.RefAttributes, @@ -36,38 +32,26 @@ const TimeField = React.forwardRef(function TimeField< const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const textFieldProps = useSlotProps({ - elementType: PickersTextField, - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState, - additionalProps: { + const textFieldProps = useFieldTextFieldProps>( + { + slotProps, ref: inRef, + externalForwardedProps: other, }, - }) as TimeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + ); const fieldResponse = useTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - const TextField = - slots?.textField ?? - (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - return ; + return ( + + ); }) as TimeFieldComponent; TimeField.propTypes = { @@ -91,6 +75,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 @@ -244,6 +234,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 819535baa8fb5..ce58f46cd7e27 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, + PickerFieldUISlots, +} 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 PickerFieldUISlots {} -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 5b6e155433d54..78630fb8e8e79 100644 --- a/packages/x-date-pickers/src/TimeField/useTimeField.ts +++ b/packages/x-date-pickers/src/TimeField/useTimeField.ts @@ -30,5 +30,6 @@ export const useTimeField = < fieldValueManager: manager.internal_fieldValueManager, validator: manager.validator, valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; 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 aaa91fd391fa5..d8c17b9ef0bc3 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,11 @@ 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 { 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 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose time/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose time/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); 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 0000000000000..3f78963434412 --- /dev/null +++ b/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx @@ -0,0 +1,500 @@ +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import resolveComponentProps from '@mui/utils/resolveComponentProps'; +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 { SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { FieldOwnerState } from '../../models'; +import { useFieldOwnerState, UseFieldOwnerStateParameters } 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 }, + }, + }; +}; + +const PickerFieldUIContext = React.createContext({ + slots: {}, + slotProps: {}, +}); + +/** + * 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, defaultOpenPickerIcon } = props; + + const translations = usePickerTranslations(); + const pickerContext = useNullablePickerContext(); + const pickerFieldUIContext = React.useContext(PickerFieldUIContext); + const { + textFieldProps, + onClear, + clearable, + openPickerAriaLabel, + clearButtonPosition: clearButtonPositionProp = 'end', + openPickerButtonPosition: openPickerButtonPositionProp = 'end', + } = cleanFieldResponse(fieldResponse); + const ownerState = useFieldOwnerState(textFieldProps); + + const handleClickOpeningButton = useEventCallback((event: React.MouseEvent) => { + event.preventDefault(); + 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 ?? + pickerFieldUIContext.slots.textField ?? + (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + + const InputAdornment = + slots?.inputAdornment ?? pickerFieldUIContext.slots.inputAdornment ?? MuiInputAdornment; + const { ownerState: startInputAdornmentOwnerState, ...startInputAdornmentProps } = useSlotProps({ + elementType: InputAdornment, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.inputAdornment, + 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 = pickerFieldUIContext.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: pickerFieldUIContext.slotProps.openPickerButton, + additionalProps: { + disabled: triggerStatus === 'disabled', + onClick: handleClickOpeningButton, + 'aria-label': openPickerAriaLabel, + edge: + clearButtonPosition === 'start' && openPickerButtonPosition === 'start' + ? undefined + : openPickerButtonPosition, + }, + ownerState, + }); + + const OpenPickerIcon = pickerFieldUIContext.slots.openPickerIcon ?? defaultOpenPickerIcon; + const openPickerIconProps = useSlotProps({ + elementType: OpenPickerIcon, + externalSlotProps: pickerFieldUIContext.slotProps.openPickerIcon, + ownerState, + }); + + const ClearButton = slots?.clearButton ?? pickerFieldUIContext.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: mergeSlotProps( + pickerFieldUIContext.slotProps.clearButton, + slotProps?.clearButton, + ), + className: 'clearButton', + additionalProps: { + title: translations.fieldClearLabel, + tabIndex: -1, + onClick: onClear, + disabled: fieldResponse.disabled || fieldResponse.readOnly, + edge: + clearButtonPosition === 'end' && openPickerButtonPosition === 'end' + ? undefined + : clearButtonPosition, + }, + ownerState, + }); + + const ClearIcon = slots?.clearIcon ?? pickerFieldUIContext.slots.clearIcon ?? MuiClearIcon; + const clearIconProps = useSlotProps({ + elementType: ClearIcon, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.clearIcon, + 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' && ( + + + + )} + + ); + } + + if (clearButtonPosition != null) { + textFieldProps.sx = [ + { + '& .clearButton': { + opacity: 1, + }, + '@media (pointer: fine)': { + '& .clearButton': { + opacity: 0, + }, + '&:hover, &:focus-within': { + '.clearButton': { + opacity: 1, + }, + }, + }, + }, + ...(Array.isArray(textFieldProps.sx) ? textFieldProps.sx : [textFieldProps.sx]), + ]; + } + + 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; + /** + * The component to use to render the picker opening icon if none is provided in the picker's slots. + */ + defaultOpenPickerIcon: React.ElementType; +} + +export 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; + /** + * Icon to display inside the clear button. + * @default ClearIcon + */ + clearIcon?: React.ElementType; + /** + * Button to clear the value. + * @default IconButton + */ + clearButton?: React.ElementType; +} + +export interface PickerFieldUISlotsFromContext extends PickerFieldUISlots { + /** + * 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 PickerFieldUISlotProps { + textField?: SlotComponentPropsFromProps< + PickersTextFieldProps | TextFieldProps, + {}, + FieldOwnerState + >; + inputAdornment?: SlotComponentPropsFromProps< + InputAdornmentProps, + {}, + FieldInputAdornmentOwnerState + >; + clearIcon?: SlotComponentPropsFromProps; + clearButton?: SlotComponentPropsFromProps; +} + +export interface PickerFieldUISlotPropsFromContext extends PickerFieldUISlotProps { + openPickerButton?: SlotComponentPropsFromProps; + openPickerIcon?: SlotComponentPropsFromProps; +} + +interface FieldInputAdornmentOwnerState extends FieldOwnerState { + position: 'start' | 'end'; +} + +interface PickerFieldUIContextValue { + slots: PickerFieldUISlotsFromContext; + slotProps: PickerFieldUISlotPropsFromContext; +} + +function mergeSlotProps( + slotPropsA: SlotComponentPropsFromProps | undefined, + slotPropsB: SlotComponentPropsFromProps | undefined, +) { + if (!slotPropsA) { + return slotPropsB; + } + + if (!slotPropsB) { + return slotPropsA; + } + + return (ownerState: TOwnerState) => { + return { + ...resolveComponentProps(slotPropsB, ownerState), + ...resolveComponentProps(slotPropsA, ownerState), + }; + }; +} + +/** + * The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. + * Once the non-accessible DOM structure will be removed, we will be able to remove the `textField` slot and clean this logic. + */ +export function useFieldTextFieldProps< + TProps extends UseFieldOwnerStateParameters & { inputProps?: {}; InputProps?: {} }, +>(parameters: UseFieldTextFieldPropsParameters) { + const { ref, externalForwardedProps, slotProps } = parameters; + const pickerFieldUIContext = React.useContext(PickerFieldUIContext); + const ownerState = useFieldOwnerState(externalForwardedProps); + + const { InputProps, inputProps, ...otherExternalForwardedProps } = externalForwardedProps; + + const textFieldProps = useSlotProps({ + elementType: PickersTextField, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.textField as any, + slotProps?.textField as any, + ), + externalForwardedProps: otherExternalForwardedProps, + additionalProps: { + ref, + }, + ownerState, + }) as any as TProps; + + // TODO: Remove when mui/material-ui#35088 will be merged + textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; + textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + + return textFieldProps; +} + +interface UseFieldTextFieldPropsParameters { + slotProps: + | { + textField?: SlotComponentPropsFromProps< + PickersTextFieldProps | TextFieldProps, + {}, + FieldOwnerState + >; + } + | undefined; + ref: React.Ref; + externalForwardedProps: any; +} + +export function PickerFieldUIContextProvider(props: PickerFieldUIContextProviderProps) { + const { slots = {}, slotProps = {}, children } = props; + + const contextValue = React.useMemo( + () => ({ + slots: { + openPickerButton: slots.openPickerButton, + openPickerIcon: slots.openPickerIcon, + textField: slots.textField, + inputAdornment: slots.inputAdornment, + clearIcon: slots.clearIcon, + clearButton: slots.clearButton, + }, + slotProps: { + openPickerButton: slotProps.openPickerButton, + openPickerIcon: slotProps.openPickerIcon, + textField: slotProps.textField, + inputAdornment: slotProps.inputAdornment, + clearIcon: slotProps.clearIcon, + clearButton: slotProps.clearButton, + }, + }), + [ + slots.openPickerButton, + slots.openPickerIcon, + slots.textField, + slots.inputAdornment, + slots.clearIcon, + slots.clearButton, + slotProps.openPickerButton, + slotProps.openPickerIcon, + slotProps.textField, + slotProps.inputAdornment, + slotProps.clearIcon, + slotProps.clearButton, + ], + ); + + return ( + {children} + ); +} + +interface PickerFieldUIContextProviderProps { + children: React.ReactNode; + slots: PickerFieldUISlotsFromContext | undefined; + slotProps: PickerFieldUISlotPropsFromContext | undefined; +} diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index 16997021db476..30d4aa9be6735 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -118,6 +118,18 @@ export interface PickerContextValue< * Is always equal to "portrait" if the component you are accessing the context 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 a disabled UI to open the Picker. + * If it is "enabled", the field should render an interactive UI to open the Picker. + */ + triggerStatus: 'hidden' | 'disabled' | 'enabled'; /** * Format that should be used to render the value in the field. * Is equal to `props.format` on the picker component if defined. 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 eed6bbba05946..8749e675362ee 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'; @@ -11,6 +9,7 @@ import { PickersLayout } from '../../../PickersLayout'; import { FieldRef, InferError } from '../../../models'; import { DateOrTimeViewWithMeridiem, BaseSingleInputFieldProps, PickerValue } from '../../models'; import { PickerProvider } from '../../components/PickerProvider'; +import { PickerFieldUIContextProvider } from '../../components/PickerFieldUI'; /** * Hook managing all the single-date desktop pickers: @@ -29,7 +28,6 @@ export const useDesktopPicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseDesktopPickerParams) => { const { @@ -41,19 +39,17 @@ export const useDesktopPicker = < label, inputRef, readOnly, - disabled, autoFocus, localeText, reduceAnimations, } = props; - const containerRef = React.useRef(null); const fieldRef = React.useRef>(null); const labelId = useId(); const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false; - const { hasUIView, providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker< + const { providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker< PickerValue, TView, TExternalProps @@ -66,40 +62,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, - // This direct access to `providerProps` will go away in https://github.com/mui/mui-x/pull/15671 - onClick: (event: React.UIEvent) => { - event.preventDefault(); - providerProps.contextValue.setOpen((prevOpen) => !prevOpen); - }, - 'aria-label': getOpenDialogAriaText(providerProps.contextValue.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, @@ -125,30 +87,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; @@ -175,25 +113,22 @@ export const useDesktopPicker = < const renderPicker = () => ( - - - - {renderCurrentView()} - - + + + + + {renderCurrentView()} + + + ); diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts index b8a7f5d0d8e07..58d97811de6cc 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts @@ -1,7 +1,4 @@ import * as React from 'react'; -import type { IconButtonProps } from '@mui/material/IconButton'; -import type { InputAdornmentProps } from '@mui/material/InputAdornment'; -import type { TextFieldProps } from '@mui/material/TextField'; import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { BasePickerProps, @@ -9,12 +6,7 @@ import { } from '../../models/props/basePickerProps'; import { PickersPopperSlots, PickersPopperSlotProps } from '../../components/PickersPopper'; import { UsePickerParams } from '../usePicker'; -import { - FieldOwnerState, - PickerFieldSlotProps, - PickerOwnerState, - PickerValidDate, -} from '../../../models'; +import { PickerFieldSlotProps, PickerOwnerState } from '../../../models'; import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, @@ -24,10 +16,9 @@ import { UsePickerValueNonStaticProps } from '../usePicker/usePickerValue.types' import { UsePickerViewsProps } from '../usePicker/usePickerViews'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; import { - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../../../hooks/useClearableField'; -import { PickersTextFieldProps } from '../../../PickersTextField'; + PickerFieldUISlotsFromContext, + PickerFieldUISlotPropsFromContext, +} from '../../components/PickerFieldUI'; import { UsePickerProviderNonStaticProps } from '../usePicker/usePickerProvider'; export interface UseDesktopPickerSlots @@ -36,7 +27,7 @@ export interface UseDesktopPickerSlots 'desktopPaper' | 'desktopTransition' | 'desktopTrapFocus' | 'popper' >, ExportedPickersLayoutSlots, - UseClearableFieldSlots { + PickerFieldUISlotsFromContext { /** * Component used to enter the date with the keyboard. */ @@ -46,46 +37,24 @@ 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 - extends ExportedUseDesktopPickerSlotProps, - Pick, 'toolbar'> {} - export interface ExportedUseDesktopPickerSlotProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersPopperSlotProps, ExportedPickersLayoutSlotProps, - UseClearableFieldSlotProps { + PickerFieldUISlotPropsFromContext { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; - inputAdornment?: SlotComponentPropsFromProps; - openPickerButton?: SlotComponentPropsFromProps; - openPickerIcon?: SlotComponentPropsFromProps, {}, PickerOwnerState>; } +export interface UseDesktopPickerSlotProps + extends ExportedUseDesktopPickerSlotProps, + Pick, 'toolbar'> {} + export interface DesktopOnlyPickerProps extends BaseNonRangeNonStaticPickerProps, UsePickerValueNonStaticProps, @@ -130,5 +99,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 8bec0c4e9afa6..3f92bef606d7b 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -3,7 +3,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import useEventCallback from '@mui/utils/useEventCallback'; import { useRtl } from '@mui/system/RtlProvider'; import { useValidation } from '../../../validation'; -import { useUtils } from '../useUtils'; +import { useLocalizationContext, useUtils } from '../useUtils'; import { UseFieldParams, UseFieldResponse, @@ -52,6 +52,7 @@ export const useField = < fieldValueManager, valueManager, validator, + getOpenPickerButtonAriaLabel: getOpenDialogAriaText, } = params; const isRtl = useRtl(); @@ -279,9 +280,16 @@ export const useField = < clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled), }; + const localizationContext = useLocalizationContext(); + const openPickerAriaLabel = React.useMemo( + () => getOpenDialogAriaText({ ...localizationContext, value: state.value }), + [getOpenDialogAriaText, state.value, localizationContext], + ); + 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 e286389178177..2b9dbd7d3598c 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,9 @@ 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'; +import { UseLocalizationContextReturnValue } from '../useUtils'; export interface UseFieldParams< TValue extends PickerValidValue, @@ -34,6 +35,9 @@ export interface UseFieldParams< fieldValueManager: FieldValueManager; validator: Validator, TInternalProps>; valueType: PickerValueType; + getOpenPickerButtonAriaLabel: ( + parameters: UseLocalizationContextReturnValue & { value: TValue }, + ) => string; } export interface UseFieldInternalProps< @@ -122,9 +126,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/useFieldOwnerState.ts b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts index 9ec96ac340d73..16f9c3ce49905 100644 --- a/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts @@ -20,6 +20,6 @@ export function useFieldOwnerState(parameters: UseFieldOwnerStateParameters) { ); } -interface UseFieldOwnerStateParameters extends FormProps { +export interface UseFieldOwnerStateParameters extends FormProps { required?: boolean; } 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 495276ff0aa50..f484f461a0075 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -5,11 +5,11 @@ 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'; import { PickerProvider } from '../../components/PickerProvider'; +import { PickerFieldUIContextProvider } from '../../components/PickerFieldUI'; /** * Hook managing all the single-date mobile pickers: @@ -28,7 +28,6 @@ export const useMobilePicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseMobilePickerParams) => { const { @@ -40,7 +39,7 @@ export const useMobilePicker = < label, inputRef, readOnly, - disabled, + autoFocus, localeText, } = props; @@ -72,38 +71,21 @@ export const useMobilePicker = < externalSlotProps: innerSlotProps?.field, additionalProps: { // Internal props - readOnly: readOnly ?? true, + readOnly, + autoFocus: autoFocus && !props.open, // Forwarded props className, sx, label, name, + focused: providerProps.contextValue.open ? true : undefined, ...(isToolbarHidden && { id: labelId }), - ...(!(disabled || readOnly) && { - // These direct access to `providerProps` will go away in https://github.com/mui/mui-x/pull/15671 - onClick: (event: React.UIEvent) => { - event.preventDefault(); - providerProps.contextValue.setOpen(true); - }, - onKeyDown: onSpaceOrEnter(() => providerProps.contextValue.setOpen(true)), - }), ...(!!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(providerProps.contextValue.value), - } as typeof fieldProps.inputProps; - - const slotsForField = { - textField: slots.textField, - ...fieldProps.slots, - }; - const Layout = slots.layout ?? PickersLayout; let labelledById = labelId; @@ -130,17 +112,14 @@ export const useMobilePicker = < const renderPicker = () => ( - - - - {renderCurrentView()} - - + + + + + {renderCurrentView()} + + + ); 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 c169f6713f5be..3398e2d1dfdb2 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 { BasePickerProps, @@ -10,12 +9,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, @@ -24,37 +18,32 @@ 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 { + PickerFieldUISlotsFromContext, + PickerFieldUISlotPropsFromContext, +} from '../../components/PickerFieldUI'; export interface UseMobilePickerSlots extends PickersModalDialogSlots, - ExportedPickersLayoutSlots { + ExportedPickersLayoutSlots, + PickerFieldUISlotsFromContext { /** * 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< TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersModalDialogSlotProps, - ExportedPickersLayoutSlotProps { + ExportedPickersLayoutSlotProps, + PickerFieldUISlotPropsFromContext { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; } export interface UseMobilePickerSlotProps @@ -99,5 +88,4 @@ export interface UseMobilePickerParams< 'valueManager' | 'valueType' | 'validator' > { props: TExternalProps; - getOpenDialogAriaText: (date: PickerValidDate | null) => string; } 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 06f6408ab5c1e..a27c1306f22a2 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -56,7 +56,6 @@ export const usePicker = < return { // Picker views renderCurrentView: pickerViewsResponse.renderCurrentView, - hasUIView: pickerViewsResponse.provider.hasUIView, shouldRestoreFocus: pickerViewsResponse.shouldRestoreFocus, // Picker provider 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 96bf2f4673a65..4bf6424a9de93 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 @@ -63,6 +63,4 @@ export interface UsePickerResponse< > extends Pick, 'shouldRestoreFocus' | 'renderCurrentView'> { ownerState: PickerOwnerState; providerProps: UsePickerProviderReturnValue; - // 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 97be050ea82f9..efbbae1fb221b 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -79,6 +79,7 @@ export function usePickerProvider< const utils = useUtils(); const orientation = usePickerOrientation(paramsFromUsePickerViews.views, props.orientation); + const triggerRef = React.useRef(null); const ownerState = React.useMemo( () => ({ @@ -105,6 +106,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, @@ -113,6 +126,8 @@ export function usePickerProvider< readOnly: props.readOnly ?? false, variant, orientation, + triggerRef, + triggerStatus, fieldFormat: props.format ?? '', }), [ @@ -122,6 +137,8 @@ export function usePickerProvider< orientation, props.disabled, props.readOnly, + triggerRef, + triggerStatus, props.format, ], ); diff --git a/packages/x-date-pickers/src/internals/hooks/useUtils.ts b/packages/x-date-pickers/src/internals/hooks/useUtils.ts index 814918372fcc6..d7e4aeb94179e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useUtils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useUtils.ts @@ -38,9 +38,7 @@ export const useLocalizationContext = () => { ({ ...localization, localeText, - }) as Omit & { - localeText: PickersLocaleText; - }, + }) as UseLocalizationContextReturnValue, [localization, localeText], ); }; @@ -59,3 +57,8 @@ export const useNow = (timezone: PickersTimezone): PickerValidDate => { return now.current!; }; + +export interface UseLocalizationContextReturnValue + extends Omit { + localeText: PickersLocaleText; +} diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 2bbb31ef4487a..85394bf31fdd6 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -4,6 +4,17 @@ export type { PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps, } from './components/PickersArrowSwitcher'; +export { + PickerFieldUI, + PickerFieldUIContextProvider, + cleanFieldResponse, + useFieldTextFieldProps, +} from './components/PickerFieldUI'; +export type { + ExportedPickerFieldUIProps, + PickerFieldUISlots, + PickerFieldUISlotProps, +} from './components/PickerFieldUI'; export { PickerProvider } from './components/PickerProvider'; export type { PickerContextValue } from './components/PickerProvider'; export { PickersModalDialog } from './components/PickersModalDialog'; @@ -132,7 +143,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 016c94b661d5f..44f0baa740eb0 100644 --- a/packages/x-date-pickers/src/internals/models/fields.ts +++ b/packages/x-date-pickers/src/internals/models/fields.ts @@ -1,19 +1,16 @@ import { SxProps } from '@mui/material/styles'; -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 } 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; @@ -24,18 +21,6 @@ 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 & { - 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 7124c455941ab..0000000000000 --- 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 40ab3fe2714ae..a6cf8e68da86c 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,18 +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 = utils.isValid(value) ? utils.format(value, formatKey) : null; - const translation = propsTranslation ?? contextTranslation; - return translation(formattedValue); - }; -}; diff --git a/packages/x-date-pickers/src/managers/useDateManager.ts b/packages/x-date-pickers/src/managers/useDateManager.ts index aec21fd0379bf..b99686fbd4d85 100644 --- a/packages/x-date-pickers/src/managers/useDateManager.ts +++ b/packages/x-date-pickers/src/managers/useDateManager.ts @@ -34,6 +34,10 @@ export function useDateManager { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullDate') : null; + return localeText.openDatePickerDialogue(formattedValue); + }, }), [enableAccessibleFieldDOMStructure], ); diff --git a/packages/x-date-pickers/src/managers/useDateTimeManager.ts b/packages/x-date-pickers/src/managers/useDateTimeManager.ts index 965333a10b704..dc8b37c307152 100644 --- a/packages/x-date-pickers/src/managers/useDateTimeManager.ts +++ b/packages/x-date-pickers/src/managers/useDateTimeManager.ts @@ -35,6 +35,10 @@ export function useDateTimeManager { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullDate') : null; + return localeText.openDatePickerDialogue(formattedValue); + }, }), [enableAccessibleFieldDOMStructure], ); diff --git a/packages/x-date-pickers/src/managers/useTimeManager.ts b/packages/x-date-pickers/src/managers/useTimeManager.ts index cfbccbabbfe33..5ce9df999af2c 100644 --- a/packages/x-date-pickers/src/managers/useTimeManager.ts +++ b/packages/x-date-pickers/src/managers/useTimeManager.ts @@ -34,6 +34,10 @@ export function useTimeManager { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullTime') : null; + return localeText.openTimePickerDialogue(formattedValue); + }, }), [enableAccessibleFieldDOMStructure], ); diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index eac8aedd90db1..2002619cb4e25 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 type { 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 = @@ -160,7 +157,7 @@ export interface FieldOwnerState extends PickerOwnerState { export type PickerFieldSlotProps< TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, -> = ExportedUseClearableFieldProps & +> = ExportedPickerFieldUIProps & Pick< UseFieldInternalProps, 'shouldRespectLeadingZeros' | 'readOnly' @@ -170,13 +167,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/packages/x-date-pickers/src/models/manager.ts b/packages/x-date-pickers/src/models/manager.ts index dd2346f71e953..c7a0c42db75ec 100644 --- a/packages/x-date-pickers/src/models/manager.ts +++ b/packages/x-date-pickers/src/models/manager.ts @@ -1,7 +1,7 @@ import type { FieldValueManager, UseFieldInternalProps } from '../internals/hooks/useField'; import type { PickerValueManager } from '../internals/hooks/usePicker'; +import type { UseLocalizationContextReturnValue } from '../internals/hooks/useUtils'; import type { PickerValidValue } from '../internals/models'; -import type { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; import type { Validator } from '../validation'; import type { PickerValueType } from './common'; @@ -78,15 +78,28 @@ export interface PickerManager< * - a default format to display the value in the field * - some default validation props that are needed to validate the value (e.g: minDate, maxDate) * This property is not part of the public API and should not be used directly. - * @param {ApplyDefaultsToFieldInternalPropsParameters} parameters The parameters to apply the defaults. + * @param {ApplyDefaultsToFieldInternalPropsParameters} parameters The parameters to apply the defaults. * @returns {TFieldInternalPropsWithDefaults} The field internal props with the defaults applied. */ internal_applyDefaultsToFieldInternalProps: ( parameters: ApplyDefaultsToFieldInternalPropsParameters, ) => TFieldInternalPropsWithDefaults; + /** + * Returns the aria-label to apply on the button that opens the picker. + * @param {GetOpenPickerButtonAriaLabelParameters} params The parameters to get the aria-label. + * @returns {string} The aria-label to apply on the button that opens the picker. + */ + internal_getOpenPickerButtonAriaLabel: ( + params: GetOpenPickerButtonAriaLabelParameters, + ) => string; } interface ApplyDefaultsToFieldInternalPropsParameters - extends MuiPickersAdapterContextValue { + extends UseLocalizationContextReturnValue { internalProps: TFieldInternalProps; } + +interface GetOpenPickerButtonAriaLabelParameters + extends UseLocalizationContextReturnValue { + value: TValue; +} diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 922530b85e437..b457362dd31ec 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -326,7 +326,7 @@ { "name": "useYAxis", "kind": "Function" }, { "name": "useYColorScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" }, - { "name": "useZColorScale", "kind": "Function" }, - { "name": "ZAxisContextProvider", "kind": "Function" }, - { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" } + { "name": "useZAxes", "kind": "Function" }, + { "name": "useZAxis", "kind": "Function" }, + { "name": "useZColorScale", "kind": "Function" } ] diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 28e649df3dd20..d23e021e2ab54 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -319,7 +319,7 @@ { "name": "useYAxis", "kind": "Function" }, { "name": "useYColorScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" }, - { "name": "useZColorScale", "kind": "Function" }, - { "name": "ZAxisContextProvider", "kind": "Function" }, - { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" } + { "name": "useZAxes", "kind": "Function" }, + { "name": "useZAxis", "kind": "Function" }, + { "name": "useZColorScale", "kind": "Function" } ] diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index a60c3ecfe0cb4..1616748cefe1e 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 5362abab6eaa5..53a3b203855bf 100644 --- a/test/utils/pickers/describePicker/describePicker.tsx +++ b/test/utils/pickers/describePicker/describePicker.tsx @@ -56,11 +56,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 c823c418143a2..93b8857a59da0 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 272360c609c0c..77be94a9414aa 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 76e9ba1d50689..8fe2f45957bbb 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 b34715ce5f8b6..1a4285de7b396 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)