From 16c8962124508cb18cd4ab2b770a0613fe37a9af Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Thu, 14 Nov 2024 10:01:06 +0100 Subject: [PATCH] [pickers] Strictly type the props a picker passes to its field, and migrate all the custom field demos accordingly (#15197) --- .../custom-field/BrowserV7Field.tsx | 29 +-- .../BrowserV7MultiInputRangeField.js | 7 - .../BrowserV7MultiInputRangeField.tsx | 33 +-- .../BrowserV7SingleInputRangeField.js | 33 +-- .../BrowserV7SingleInputRangeField.tsx | 68 ++---- .../date-pickers/custom-field/JoyV6Field.tsx | 27 +-- .../custom-field/JoyV6MultiInputRangeField.js | 9 +- .../JoyV6MultiInputRangeField.tsx | 29 +-- .../JoyV6SingleInputRangeField.js | 37 +-- .../JoyV6SingleInputRangeField.tsx | 65 ++---- .../MaterialDatePicker.js | 6 +- .../MaterialDatePicker.tsx | 6 +- .../MaskedMaterialTextField.js | 48 ++-- .../MaskedMaterialTextField.tsx | 48 ++-- .../date-pickers/custom-field/custom-field.md | 48 ---- .../migration-pickers-v7.md | 211 ++++++++++++++++++ .../overview/mainDemo/PickerButton.tsx | 75 +++---- .../DateRangePicker/DateRangePicker.types.ts | 21 +- .../DateTimeRangePicker.types.ts | 24 +- .../useDesktopRangePicker.tsx | 31 ++- .../hooks/useEnrichedRangePickerFieldProps.ts | 24 +- .../useMobileRangePicker.tsx | 30 ++- .../src/internals/models/fields.ts | 61 +++++ .../x-date-pickers-pro/src/models/fields.ts | 54 +---- .../src/DatePicker/DatePicker.types.ts | 26 +-- .../DateTimePicker/DateTimePicker.types.ts | 29 +-- .../src/TimePicker/TimePicker.types.ts | 28 +-- .../useDesktopPicker/useDesktopPicker.tsx | 51 ++--- .../useDesktopPicker.types.ts | 13 +- .../hooks/useMobilePicker/useMobilePicker.tsx | 57 ++--- .../useMobilePicker/useMobilePicker.types.ts | 13 +- .../x-date-pickers/src/internals/index.ts | 2 +- .../src/internals/models/fields.ts | 72 ++++-- .../src/internals/models/validation.ts | 2 +- packages/x-date-pickers/src/models/fields.ts | 68 ++---- scripts/x-date-pickers-pro.exports.json | 5 +- scripts/x-date-pickers.exports.json | 2 +- 37 files changed, 654 insertions(+), 738 deletions(-) create mode 100644 packages/x-date-pickers-pro/src/internals/models/fields.ts diff --git a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx index 00e3b3388d57..a2cc66b242e7 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx @@ -3,19 +3,14 @@ import useForkRef from '@mui/utils/useForkRef'; import { styled } from '@mui/material/styles'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker'; import { - unstable_useDateField as useDateField, - UseDateFieldProps, -} from '@mui/x-date-pickers/DateField'; + DatePicker, + DatePickerFieldProps, + DatePickerProps, +} from '@mui/x-date-pickers/DatePicker'; +import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - BaseSingleInputPickersTextFieldProps, - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, - PickerValidDate, -} from '@mui/x-date-pickers/models'; +import { BaseSingleInputPickersTextFieldProps } from '@mui/x-date-pickers/models'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ @@ -104,18 +99,8 @@ const BrowserTextField = React.forwardRef( }, ); -interface BrowserDateFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - PickerValidDate | null, - FieldSection, - true, - DateValidationError - > {} - const BrowserDateField = React.forwardRef( - (props: BrowserDateFieldProps, ref: React.Ref) => { + (props: DatePickerFieldProps, ref: React.Ref) => { const { slots, slotProps, ...textFieldProps } = props; const fieldResponse = useDateField(textFieldProps); diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js index 0023e32e477f..5f7d2d494688 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.js @@ -5,7 +5,6 @@ import { styled } from '@mui/material/styles'; import Stack from '@mui/material/Stack'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; @@ -25,8 +24,6 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content }, ); -// This demo uses `BasePickersTextFieldProps` instead of `BaseMultiInputPickersTextFieldProps`, -// That way you can reuse the same `BrowserTextField` for all your pickers, range or not. const BrowserTextField = React.forwardRef((props, ref) => { const { // Should be ignored @@ -84,12 +81,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { const { slotProps, value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -117,12 +112,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { const fieldResponse = useMultiInputDateRangeField({ sharedProps: { value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx index 725a629ce03f..4c60152c21d0 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx @@ -5,21 +5,17 @@ import { styled } from '@mui/material/styles'; import Stack from '@mui/material/Stack'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { PickerValidDate } from '@mui/x-date-pickers/models'; import { DateRangePicker, + DateRangePickerFieldProps, DateRangePickerProps, } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; import { - RangeFieldSection, - BaseMultiInputFieldProps, - BasePickersTextFieldProps, MultiInputFieldSlotTextFieldProps, - DateRangeValidationError, - DateRange, - UseDateRangeFieldProps, + MultiInputFieldRefs, + BaseMultiInputPickersTextFieldProps, } from '@mui/x-date-pickers-pro/models'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ @@ -38,14 +34,12 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content ); interface BrowserTextFieldProps - extends BasePickersTextFieldProps, + extends BaseMultiInputPickersTextFieldProps, Omit< React.HTMLAttributes, - keyof BasePickersTextFieldProps + keyof BaseMultiInputPickersTextFieldProps > {} -// This demo uses `BasePickersTextFieldProps` instead of `BaseMultiInputPickersTextFieldProps`, -// That way you can reuse the same `BrowserTextField` for all your pickers, range or not. const BrowserTextField = React.forwardRef( (props: BrowserTextFieldProps, ref: React.Ref) => { const { @@ -108,14 +102,11 @@ const BrowserTextField = React.forwardRef( ); interface BrowserMultiInputDateRangeFieldProps - extends UseDateRangeFieldProps, - BaseMultiInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - DateRange, - RangeFieldSection, - true, - DateRangeValidationError - > {} + extends Omit< + DateRangePickerFieldProps, + 'unstableFieldRef' | 'clearable' | 'onClear' + >, + MultiInputFieldRefs {} type BrowserMultiInputDateRangeFieldComponent = (( props: BrowserMultiInputDateRangeFieldProps & React.RefAttributes, @@ -126,12 +117,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef( const { slotProps, value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -162,12 +151,10 @@ const BrowserMultiInputDateRangeField = React.forwardRef( >({ sharedProps: { value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js index a04931569d64..a86335cd7dd7 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js @@ -9,7 +9,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 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'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; +import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ @@ -84,7 +84,16 @@ const BrowserTextField = React.forwardRef((props, ref) => { }); const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; + const { slots, slotProps, ...other } = props; + + const pickerContext = usePickerContext(); + const handleTogglePicker = (event) => { + if (pickerContext.open) { + pickerContext.onClose(event); + } else { + pickerContext.onOpen(event); + } + }; const textFieldProps = useSlotProps({ elementType: 'input', @@ -97,7 +106,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { ...textFieldProps.InputProps, endAdornment: ( - + @@ -127,29 +136,11 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { BrowserSingleInputDateRangeField.fieldType = 'single-input'; const BrowserSingleInputDateRangePicker = React.forwardRef((props, ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - return ( ); }); diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx index adfe26146591..2c1c935104e7 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx @@ -9,25 +9,14 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DateRangePicker, + DateRangePickerFieldProps, DateRangePickerProps, } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { - unstable_useSingleInputDateRangeField as useSingleInputDateRangeField, - UseSingleInputDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; +import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; +import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks'; import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-pickers/PickersSectionList'; -import { - BasePickersTextFieldProps, - DateRangeValidationError, - RangeFieldSection, - DateRange, - FieldType, -} from '@mui/x-date-pickers-pro/models'; -import { - BaseSingleInputFieldProps, - PickerValidDate, -} from '@mui/x-date-pickers/models'; +import { FieldType } from '@mui/x-date-pickers-pro/models'; +import { BaseSingleInputPickersTextFieldProps } from '@mui/x-date-pickers/models'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', @@ -48,10 +37,10 @@ const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content ); interface BrowserTextFieldProps - extends BasePickersTextFieldProps, + extends BaseSingleInputPickersTextFieldProps, Omit< React.HTMLAttributes, - keyof BasePickersTextFieldProps + keyof BaseSingleInputPickersTextFieldProps > {} const BrowserTextField = React.forwardRef( @@ -115,17 +104,7 @@ const BrowserTextField = React.forwardRef( }, ); -interface BrowserSingleInputDateRangeFieldProps - extends UseSingleInputDateRangeFieldProps, - BaseSingleInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - DateRange, - RangeFieldSection, - true, - DateRangeValidationError - > { - onAdornmentClick?: () => void; -} +interface BrowserSingleInputDateRangeFieldProps extends DateRangePickerFieldProps {} type BrowserSingleInputDateRangeFieldComponent = (( props: BrowserSingleInputDateRangeFieldProps & React.RefAttributes, @@ -133,7 +112,16 @@ type BrowserSingleInputDateRangeFieldComponent = (( const BrowserSingleInputDateRangeField = React.forwardRef( (props: BrowserSingleInputDateRangeFieldProps, ref: React.Ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; + const { slots, slotProps, ...other } = props; + + const pickerContext = usePickerContext(); + const handleTogglePicker = (event: React.UIEvent) => { + if (pickerContext.open) { + pickerContext.onClose(event); + } else { + pickerContext.onOpen(event); + } + }; const textFieldProps: typeof props = useSlotProps({ elementType: 'input', @@ -146,7 +134,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef( ...textFieldProps.InputProps, endAdornment: ( - + @@ -180,29 +168,11 @@ BrowserSingleInputDateRangeField.fieldType = 'single-input'; const BrowserSingleInputDateRangePicker = React.forwardRef( (props: DateRangePickerProps, ref: React.Ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - return ( ); }, diff --git a/docs/data/date-pickers/custom-field/JoyV6Field.tsx b/docs/data/date-pickers/custom-field/JoyV6Field.tsx index a2dfdebe9255..50b0fabe3c09 100644 --- a/docs/data/date-pickers/custom-field/JoyV6Field.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6Field.tsx @@ -15,18 +15,13 @@ import FormControl from '@mui/joy/FormControl'; import FormLabel from '@mui/joy/FormLabel'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker'; import { - unstable_useDateField as useDateField, - UseDateFieldProps, -} from '@mui/x-date-pickers/DateField'; + DatePicker, + DatePickerFieldProps, + DatePickerProps, +} from '@mui/x-date-pickers/DatePicker'; +import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, - PickerValidDate, -} from '@mui/x-date-pickers/models'; const joyTheme = extendJoyTheme(); @@ -99,18 +94,8 @@ const JoyField = React.forwardRef( }, ) as JoyFieldComponent; -interface JoyDateFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - PickerValidDate | null, - FieldSection, - false, - DateValidationError - > {} - const JoyDateField = React.forwardRef( - (props: JoyDateFieldProps, ref: React.Ref) => { + (props: DatePickerFieldProps, ref: React.Ref) => { const { slots, slotProps, ...textFieldProps } = props; const fieldResponse = useDateField({ diff --git a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js index 74b17040c152..61a8a5d231f7 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.js @@ -19,7 +19,6 @@ import FormLabel from '@mui/joy/FormLabel'; import Typography from '@mui/joy/Typography'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; @@ -106,12 +105,10 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { const { slotProps, value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -120,6 +117,8 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { selectedSections, onSelectedSectionsChange, className, + unstableStartFieldRef, + unstableEndFieldRef, } = props; const startTextFieldProps = useSlotProps({ @@ -137,12 +136,10 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { const fieldResponse = useMultiInputDateRangeField({ sharedProps: { value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -154,6 +151,8 @@ const JoyMultiInputDateRangeField = React.forwardRef((props, ref) => { }, startTextFieldProps, endTextFieldProps, + unstableStartFieldRef, + unstableEndFieldRef, }); return ( diff --git a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx index ce976dd69843..f4c28e574437 100644 --- a/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6MultiInputRangeField.tsx @@ -19,19 +19,15 @@ import FormLabel from '@mui/joy/FormLabel'; import Typography, { TypographyProps } from '@mui/joy/Typography'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { PickerValidDate } from '@mui/x-date-pickers/models'; import { DateRangePicker, + DateRangePickerFieldProps, DateRangePickerProps, } from '@mui/x-date-pickers-pro/DateRangePicker'; import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; import { - BaseMultiInputFieldProps, - DateRange, - DateRangeValidationError, + MultiInputFieldRefs, MultiInputFieldSlotTextFieldProps, - RangeFieldSection, - UseDateRangeFieldProps, } from '@mui/x-date-pickers-pro/models'; const joyTheme = extendJoyTheme(); @@ -132,14 +128,11 @@ const MultiInputJoyDateRangeFieldSeparator = styled( )({ marginTop: '25px' }); interface JoyMultiInputDateRangeFieldProps - extends UseDateRangeFieldProps, - BaseMultiInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - DateRange, - RangeFieldSection, - false, - DateRangeValidationError - > {} + extends Omit< + DateRangePickerFieldProps, + 'unstableFieldRef' | 'clearable' | 'onClear' + >, + MultiInputFieldRefs {} type JoyMultiInputDateRangeFieldComponent = (( props: JoyMultiInputDateRangeFieldProps & React.RefAttributes, @@ -150,12 +143,10 @@ const JoyMultiInputDateRangeField = React.forwardRef( const { slotProps, value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -164,6 +155,8 @@ const JoyMultiInputDateRangeField = React.forwardRef( selectedSections, onSelectedSectionsChange, className, + unstableStartFieldRef, + unstableEndFieldRef, } = props; const startTextFieldProps = useSlotProps({ @@ -184,12 +177,10 @@ const JoyMultiInputDateRangeField = React.forwardRef( >({ sharedProps: { value, - defaultValue, format, onChange, readOnly, disabled, - onError, shouldDisableDate, minDate, maxDate, @@ -201,6 +192,8 @@ const JoyMultiInputDateRangeField = React.forwardRef( }, startTextFieldProps, endTextFieldProps, + unstableStartFieldRef, + unstableEndFieldRef, }); return ( diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js index 0ede431a9e5b..dd5ee96d6f52 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js @@ -20,7 +20,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 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'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; +import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks'; const joyTheme = extendJoyTheme(); @@ -69,7 +69,16 @@ const JoyField = React.forwardRef((props, ref) => { }); const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; + const { slots, slotProps, ...other } = props; + + const pickerContext = usePickerContext(); + const handleTogglePicker = (event) => { + if (pickerContext.open) { + pickerContext.onClose(event); + } else { + pickerContext.onOpen(event); + } + }; const textFieldProps = useSlotProps({ elementType: FormControl, @@ -96,7 +105,7 @@ const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { ref={ref} endDecorator={ { JoySingleInputDateRangeField.fieldType = 'single-input'; const JoySingleInputDateRangePicker = React.forwardRef((props, ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = (event) => { - // allows toggle behavior - event.stopPropagation(); - setIsOpen((currentOpen) => !currentOpen); - }; - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - return ( ); }); diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx index e2815f70d823..36d33af92f98 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx @@ -20,23 +20,12 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DateRangePicker, + DateRangePickerFieldProps, DateRangePickerProps, } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { - unstable_useSingleInputDateRangeField as useSingleInputDateRangeField, - UseSingleInputDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - BaseSingleInputFieldProps, - PickerValidDate, -} from '@mui/x-date-pickers/models'; -import { - RangeFieldSection, - DateRange, - DateRangeValidationError, - FieldType, -} from '@mui/x-date-pickers-pro/models'; +import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; +import { useClearableField, usePickerContext } from '@mui/x-date-pickers/hooks'; +import { FieldType } from '@mui/x-date-pickers-pro/models'; const joyTheme = extendJoyTheme(); @@ -103,16 +92,7 @@ const JoyField = React.forwardRef( ) as JoyFieldComponent; interface JoySingleInputDateRangeFieldProps - extends UseSingleInputDateRangeFieldProps, - BaseSingleInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - DateRange, - RangeFieldSection, - false, - DateRangeValidationError - > { - onAdornmentClick?: () => void; -} + extends DateRangePickerFieldProps {} type JoySingleInputDateRangeFieldComponent = (( props: JoySingleInputDateRangeFieldProps & React.RefAttributes, @@ -120,7 +100,16 @@ type JoySingleInputDateRangeFieldComponent = (( const JoySingleInputDateRangeField = React.forwardRef( (props: JoySingleInputDateRangeFieldProps, ref: React.Ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; + const { slots, slotProps, ...other } = props; + + const pickerContext = usePickerContext(); + const handleTogglePicker = (event: React.UIEvent) => { + if (pickerContext.open) { + pickerContext.onClose(event); + } else { + pickerContext.onOpen(event); + } + }; const textFieldProps: JoySingleInputDateRangeFieldProps = useSlotProps({ elementType: FormControl, @@ -147,7 +136,7 @@ const JoySingleInputDateRangeField = React.forwardRef( ref={ref} endDecorator={ , ref: React.Ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = (event: React.PointerEvent) => { - // allows toggle behavior - event.stopPropagation(); - setIsOpen((currentOpen) => !currentOpen); - }; - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - return ( ); }, 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 0e849ab1228c..2856b3af1a2d 100644 --- a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.js @@ -25,7 +25,7 @@ function AutocompleteField(props) { ...other } = forwardedProps; - const { hasValidationError } = useValidation({ + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, timezone, @@ -85,7 +85,9 @@ function AutocompleteField(props) { }} value={value} onChange={(_, newValue) => { - onChange?.(newValue, { validationError: null }); + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); }} isOptionEqualToValue={(option, valueToCheck) => option.toISOString() === valueToCheck.toISOString() 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 10228ff08be1..7633f42d7199 100644 --- a/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-autocomplete/MaterialDatePicker.tsx @@ -36,7 +36,7 @@ function AutocompleteField(props: AutocompleteFieldProps) { ...other } = forwardedProps; - const { hasValidationError } = useValidation({ + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ validator: validateDate, value, timezone, @@ -96,7 +96,9 @@ function AutocompleteField(props: AutocompleteFieldProps) { }} value={value} onChange={(_, newValue) => { - onChange?.(newValue, { validationError: null }); + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); }} isOptionEqualToValue={(option, valueToCheck) => option.toISOString() === valueToCheck.toISOString() 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 0723d4565c51..107fb273f496 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,7 +2,6 @@ import * as React from 'react'; import dayjs from 'dayjs'; import { useRifm } from 'rifm'; import TextField from '@mui/material/TextField'; -import useControlled from '@mui/utils/useControlled'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; @@ -15,7 +14,7 @@ const ACCEPT_REGEX = /[\d]/gi; const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); -function getValueStrFromValue(value, format) { +function getInputValueFromValue(value, format) { if (value == null) { return ''; } @@ -23,35 +22,21 @@ function getValueStrFromValue(value, format) { return value.isValid() ? value.format(format) : ''; } -function MaskedField(props) { +function MaskedDateField(props) { const { slots, slotProps, ...other } = props; const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); - const { - format, - value: valueProp, - defaultValue, - onChange, - timezone, - onError, - } = internalProps; - - const [value, setValue] = useControlled({ - controlled: valueProp, - default: defaultValue ?? null, - name: 'MaskedField', - state: 'value', - }); + const { format, value, onChange, timezone } = internalProps; // Control the input text const [inputValue, setInputValue] = React.useState(() => - getValueStrFromValue(value, format), + getInputValueFromValue(value, format), ); React.useEffect(() => { if (value && value.isValid()) { - const newDisplayDate = getValueStrFromValue(value, format); + const newDisplayDate = getInputValueFromValue(value, format); setInputValue(newDisplayDate); } }, [format, value]); @@ -61,22 +46,17 @@ function MaskedField(props) { const { hasValidationError, getValidationErrorForNewValue } = useValidation({ value, timezone, - onError, props: internalProps, validator: validateDate, }); - const handleValueStrChange = (newValueStr) => { - setInputValue(newValueStr); - - const newValue = dayjs(newValueStr, format); - setValue(newValue); + const handleInputValueChange = (newInputValue) => { + setInputValue(newInputValue); - if (onChange) { - onChange(newValue, { - validationError: getValidationErrorForNewValue(newValue), - }); - } + const newValue = dayjs(newInputValue, format); + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); }; const rifmFormat = React.useMemo(() => { @@ -137,7 +117,7 @@ function MaskedField(props) { const rifmProps = useRifm({ value: inputValue, - onChange: handleValueStrChange, + onChange: handleInputValueChange, format: rifmFormat, }); @@ -152,7 +132,9 @@ function MaskedField(props) { } function MaskedFieldDatePicker(props) { - return ; + return ( + + ); } export default function MaskedMaterialTextField() { 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 13c0a71b5456..aa7b8023fcaf 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,7 +2,6 @@ import * as React from 'react'; import dayjs, { Dayjs } from 'dayjs'; import { useRifm } from 'rifm'; import TextField from '@mui/material/TextField'; -import useControlled from '@mui/utils/useControlled'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { @@ -19,7 +18,7 @@ const ACCEPT_REGEX = /[\d]/gi; const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); -function getValueStrFromValue(value: Dayjs | null, format: string) { +function getInputValueFromValue(value: Dayjs | null, format: string) { if (value == null) { return ''; } @@ -27,35 +26,21 @@ function getValueStrFromValue(value: Dayjs | null, format: string) { return value.isValid() ? value.format(format) : ''; } -function MaskedField(props: DatePickerFieldProps) { +function MaskedDateField(props: DatePickerFieldProps) { const { slots, slotProps, ...other } = props; const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); - const { - format, - value: valueProp, - defaultValue, - onChange, - timezone, - onError, - } = internalProps; - - const [value, setValue] = useControlled({ - controlled: valueProp, - default: defaultValue ?? null, - name: 'MaskedField', - state: 'value', - }); + const { format, value, onChange, timezone } = internalProps; // Control the input text const [inputValue, setInputValue] = React.useState(() => - getValueStrFromValue(value, format), + getInputValueFromValue(value, format), ); React.useEffect(() => { if (value && value.isValid()) { - const newDisplayDate = getValueStrFromValue(value, format); + const newDisplayDate = getInputValueFromValue(value, format); setInputValue(newDisplayDate); } }, [format, value]); @@ -65,22 +50,17 @@ function MaskedField(props: DatePickerFieldProps) { const { hasValidationError, getValidationErrorForNewValue } = useValidation({ value, timezone, - onError, props: internalProps, validator: validateDate, }); - const handleValueStrChange = (newValueStr: string) => { - setInputValue(newValueStr); - - const newValue = dayjs(newValueStr, format); - setValue(newValue); + const handleInputValueChange = (newInputValue: string) => { + setInputValue(newInputValue); - if (onChange) { - onChange(newValue, { - validationError: getValidationErrorForNewValue(newValue), - }); - } + const newValue = dayjs(newInputValue, format); + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); }; const rifmFormat = React.useMemo(() => { @@ -141,7 +121,7 @@ function MaskedField(props: DatePickerFieldProps) { const rifmProps = useRifm({ value: inputValue, - onChange: handleValueStrChange, + onChange: handleInputValueChange, format: rifmFormat, }); @@ -156,7 +136,9 @@ function MaskedField(props: DatePickerFieldProps) { } function MaskedFieldDatePicker(props: DatePickerProps) { - return ; + return ( + + ); } export default function MaskedMaterialTextField() { diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index 3b9459feb141..e30850de8502 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -154,51 +154,3 @@ 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}} - -## How to build a custom field - -The main challenge when building a custom field, is to make sure that all the relevant props passed by the pickers are correctly handled. - -On the examples below, you can see that the typing of the props received by a custom field always have the same shape: - -```tsx -interface JoyDateFieldProps - extends UseDateFieldProps, // The headless field props - BaseSingleInputFieldProps< - Dayjs | null, - FieldSection, - true, // `false` for `enableAccessibleFieldDOMStructure={false}` - DateValidationError - > {} // The DOM field props - -interface JoyDateTimeFieldProps - extends UseDateTimeFieldProps, // The headless field props - BaseSingleInputFieldProps< - Dayjs | null, - FieldSection, - true, // `false` for `enableAccessibleFieldDOMStructure={false}` - DateTimeValidationError - > {} // The DOM field props -``` - -### The headless field props - -This interface depends on which type of field you are building (`UseDateField` for date field, `UseTimeField` for a time field, `UseDateRangeFieldProps` for a date range field, etc.). - -It contains: - -- the basic props common to all the fields (`value`, `onChange`, `format`, `readOnly`, etc.) -- the validation props for this type of field (`minDate`, `maxDate`, `shouldDisableDate`, etc.) - -:::info -If you are building a custom field that doesn't have any input editing (e.g: the _Button field_), you can ignore most of those props. -::: - -### The DOM field props - -This interface contains the props the pickers pass to its field in order to customize the rendering. - -These props are shaped to be received by the built-in fields which are using the `TextField` from `@mui/material`. -When used with another type of input (or no input at all), you will have to manually pass them to the relevant component. - -You can have a look at the `BaseSingleInputFieldProps` and `BaseMultiInputFieldProps` interfaces to know exactly what those interfaces contain. 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 c78ed186f4d5..07765489a33b 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -365,6 +365,217 @@ If you were using them, you need to replace them with the following code: UseDateTimeFieldProps; ``` +- `BaseSingleInputFieldProps` + + - If you are building a custom field for a Date Picker: + + ```diff + -import { + - BaseSingleInputFieldProps, + - DateValidationError, + - FieldSection, + -} from '@mui/x-date-pickers/models'; + -import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; + +import { DatePickerFieldProps } from '@mui/x-date-pickers/DatePicker'; + + -interface CustomDateFieldProps + - extends UseDateFieldProps, + - BaseSingleInputFieldProps< + - Dayjs | null, + - Dayjs, + - FieldSection, + - true, + - DateValidationError + - > {} + +interface CustomDateFieldProps extends DatePickerFieldProps {} + ``` + + - If you are building a custom field for a Time Picker: + + ```diff + -import { + - BaseSingleInputFieldProps, + - TimeValidationError, + - FieldSection, + -} from '@mui/x-date-pickers/models'; + -import { UseTimeFieldProps } from '@mui/x-date-pickers/TimeField'; + +import { TimePickerFieldProps } from '@mui/x-date-pickers/TimePicker'; + + -interface CustomTimeFieldProps + - extends UseTimeFieldProps, + - BaseSingleInputFieldProps< + - Dayjs | null, + - Dayjs, + - FieldSection, + - true, + - TimeValidationError + - > {} + +interface CustomTimeFieldProps extends TimePickerFieldProps {} + ``` + + - If you are building a custom field for a Date Time Picker: + + ```diff + -import { + - BaseSingleInputFieldProps, + - DateTimeValidationError, + - FieldSection, + -} from '@mui/x-date-pickers/models'; + -import { UseDateTimeFieldProps } from '@mui/x-date-pickers/DateTimeField'; + +import { DateTimePickerFieldProps } from '@mui/x-date-pickers/DateTimePicker'; + + -interface CustomDateTimeFieldProps + - extends UseDateTimeFieldProps, + - BaseSingleInputFieldProps< + - Dayjs | null, + - Dayjs, + - FieldSection, + - true, + - DateTimeValidationError + - > {} + +interface CustomDateTimeFieldProps extends DateTimePickerFieldProps {} + ``` + + - If you are building a custom single input field for a Date Range Picker: + + ```diff + -import { + - DateRangeValidationError, + - RangeFieldSection, + - DateRange, + -} from '@mui/x-date-pickers-pro/models'; + -import { + - UseSingleInputDateRangeFieldProps + -} from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; + +import { DateRangePickerFieldProps } from '@mui/x-date-pickers-pro/DateRangePicker'; + + -interface CustomDateRangeFieldProps + - extends UseSingleInputDateRangeFieldProps, + - BaseSingleInputFieldProps< + - DateRange, + - Dayjs, + - RangeFieldSection, + - true, + - DateRangeValidationError + - > + +interface CustomDateRangeFieldProps extends DateRangePickerFieldProps {} + ``` + + - If you are building a custom single input field for a Date Time Range Picker: + + ```diff + -import { + - DateTimeRangeValidationError, + - RangeFieldSection, + - DateRange, + -} from '@mui/x-date-pickers-pro/models'; + -import { + - UseSingleInputDateTimeRangeFieldProps + -} from '@mui/x-date-pickers-pro/SingleInputDateTimeRangeField'; + +import { + + DateTimeRangePickerFieldProps + +} from '@mui/x-date-pickers-pro/DateTimeRangePicker'; + + -interface CustomDateTimeRangeFieldProps + - extends UseSingleInputDateTimeRangeFieldProps, + - BaseSingleInputFieldProps< + - DateRange, + - Dayjs, + - RangeFieldSection, + - true, + - DateTimeRangeValidationError + - > + +interface CustomDateTimeRangeFieldProps extends DateTimeRangePickerFieldProps {} + ``` + +- `BaseMultiInputFieldProps` + + - If you are building a custom multi input field for a Date Range Picker: + + ```diff + -import { + - DateRangeValidationError, + - RangeFieldSection, + - DateRange, + -} from '@mui/x-date-pickers-pro/models'; + -import { + - UseMultiInputDateRangeFieldProps + -} from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; + +import { DateRangePickerFieldProps } from '@mui/x-date-pickers-pro/DateRangePicker'; + + -interface CustomDateRangeFieldProps + - extends UseMultiInputDateRangeFieldProps, + - BaseMultiInputFieldProps< + - DateRange, + - Dayjs, + - RangeFieldSection, + - true, + - DateRangeValidationError + - > {} + +interface CustomDateRangeFieldProps + + extends Omit< + + DateRangePickerFieldProps, + + 'unstableFieldRef' | 'clearable' | 'onClear' + + >, + + MultiInputFieldRefs {} + ``` + + - If you are building a custom multi input field for a Date Time Range Picker: + + ```diff + -import { + - DateTimeRangeValidationError, + - RangeFieldSection, + - DateRange, + -} from '@mui/x-date-pickers-pro/models'; + -import { + - UseMultiInputDateTimeRangeFieldProps + -} from '@mui/x-date-pickers-pro/MultiInputDateTimeRangeField'; + +import { + + DateTimeRangePickerFieldProps + +} from '@mui/x-date-pickers-pro/DateTimeRangePicker'; + + -interface CustomDateTimeRangeFieldProps + - extends UseMultiInputDateTimeRangeFieldProps, + - BaseMultiInputFieldProps< + - DateRange, + - Dayjs, + - RangeFieldSection, + - false, + - DateTimeRangeValidationError + - > {} + +interface JoyMultiInputDateRangeFieldProps + + extends Omit< + + DateTimeRangePickerFieldProps, + + 'unstableFieldRef' | 'clearable' | 'onClear' + + >, + + MultiInputFieldRefs {} + ``` + +- `BasePickersTextFieldProps` + + - If your Text Field is used inside a non-range picker or in a range-picker with a single input field: + + ```diff + -import { BasePickersTextFieldProps } from '@mui/x-date-pickers-pro/models'; + +import { BaseSingleInputPickersTextFieldProps } from '@mui/x-date-pickers/models'; + + interface CustomTextFieldProps + - extends BasePickersTextFieldProps {} + + extends BaseSingleInputPickersTextFieldProps {} + ``` + + - If your Text Field is used inside a range-picker with a multi input field: + + ```diff + -import { BasePickersTextFieldProps } from '@mui/x-date-pickers-pro/models'; + +import { BaseMultiInputPickersTextFieldProps } from '@mui/x-date-pickers-pro/models'; + + interface CustomTextFieldProps + - extends BasePickersTextFieldProps {} + + extends BaseMultiInputPickersTextFieldProps {} + ``` + ## Stop using `LicenseInfo` from `@mui/x-date-pickers-pro` The `LicenseInfo` object is not exported from the `@mui/x-date-pickers-pro` package anymore. diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index 7dee0bad4fed..d47d045fb1c6 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -3,74 +3,67 @@ import dayjs, { Dayjs } from 'dayjs'; import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; -import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, - PickerValidDate, -} from '@mui/x-date-pickers/models'; +import { DatePicker, DatePickerFieldProps } from '@mui/x-date-pickers/DatePicker'; +import { useParsedFormat, usePickerContext, useSplitFieldProps } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; -interface ButtonFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - // This usage of PickerValidDate will go away with TIsRange - PickerValidDate | null, - FieldSection, - true, - DateValidationError - > { - setOpen?: React.Dispatch>; -} +function ButtonDateField(props: DatePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { InputProps, slotProps, slots, ownerState, label, focused, name, ...other } = + forwardedProps; + + const pickerContext = usePickerContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event: React.UIEvent) => { + if (pickerContext.open) { + pickerContext.onClose(event); + } else { + pickerContext.onOpen(event); + } + }; -function ButtonField(props: ButtonFieldProps) { - const { - setOpen, - label, - id, - disabled, - InputProps: { ref } = {}, - inputProps: { 'aria-label': ariaLabel } = {}, - } = props; + const valueStr = value == null ? parsedFormat : value.format(format); return ( ); } export default function PickerButton() { const [value, setValue] = React.useState(dayjs('2023-04-17')); - const [open, setOpen] = React.useState(false); return ( setValue(newValue)} - slots={{ field: ButtonField }} + slots={{ field: ButtonDateField }} slotProps={{ - field: { setOpen } as any, nextIconButton: { size: 'small' }, previousIconButton: { size: 'small' }, }} - open={open} - onClose={() => setOpen(false)} - onOpen={() => setOpen(true)} views={['day', 'month', 'year']} /> diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts index 326c9b650e99..8afa43015506 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts @@ -1,6 +1,4 @@ -import { MakeRequired } from '@mui/x-internals/types'; -import { BaseDateValidationProps, PickerRangeValue } from '@mui/x-date-pickers/internals'; -import { BaseSingleInputFieldProps } from '@mui/x-date-pickers/models'; +import { BaseSingleInputFieldProps, PickerRangeValue } from '@mui/x-date-pickers/internals'; import { DesktopDateRangePickerProps, DesktopDateRangePickerSlots, @@ -11,7 +9,8 @@ import { MobileDateRangePickerSlots, MobileDateRangePickerSlotProps, } from '../MobileDateRangePicker'; -import { DateRangeValidationError, RangeFieldSection, UseDateRangeFieldProps } from '../models'; +import { DateRangeValidationError, RangeFieldSection } from '../models'; +import { ValidateDateRangeProps } from '../validation'; export interface DateRangePickerSlots extends DesktopDateRangePickerSlots, @@ -43,11 +42,13 @@ export interface DateRangePickerProps, or component). */ export type DateRangePickerFieldProps = - MakeRequired< - UseDateRangeFieldProps, - 'format' | 'timezone' | 'value' | keyof BaseDateValidationProps - > & - BaseSingleInputFieldProps; + ValidateDateRangeProps & + BaseSingleInputFieldProps< + PickerRangeValue, + RangeFieldSection, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError + >; diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts index 43998205dc64..bfb0237f8f03 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts @@ -1,10 +1,4 @@ -import { MakeRequired } from '@mui/x-internals/types'; -import { - BaseDateValidationProps, - BaseTimeValidationProps, - PickerRangeValue, -} from '@mui/x-date-pickers/internals'; -import { BaseSingleInputFieldProps } from '@mui/x-date-pickers/models'; +import { BaseSingleInputFieldProps, PickerRangeValue } from '@mui/x-date-pickers/internals'; import { DesktopDateTimeRangePickerProps, DesktopDateTimeRangePickerSlots, @@ -15,8 +9,8 @@ import { MobileDateTimeRangePickerSlots, MobileDateTimeRangePickerSlotProps, } from '../MobileDateTimeRangePicker'; -import { UseDateTimeRangeFieldProps } from '../internals/models'; import { DateTimeRangeValidationError, RangeFieldSection } from '../models'; +import type { ValidateDateTimeRangeProps } from '../validation'; export interface DateTimeRangePickerSlots extends DesktopDateTimeRangePickerSlots, @@ -48,22 +42,14 @@ export interface DateTimeRangePickerProps, or component). */ export type DateTimeRangePickerFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, -> = MakeRequired< - UseDateTimeRangeFieldProps, - | 'format' - | 'timezone' - | 'value' - | 'ampm' - | keyof BaseDateValidationProps - | keyof BaseTimeValidationProps -> & +> = ValidateDateTimeRangeProps & BaseSingleInputFieldProps< PickerRangeValue, RangeFieldSection, - false, + TEnableAccessibleFieldDOMStructure, DateTimeRangeValidationError >; 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 67ddaddd225e..c34caa11572c 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 @@ -13,12 +13,11 @@ import { PickerProvider, PickerRangeValue, } from '@mui/x-date-pickers/internals'; -import { FieldRef, InferError, PickerOwnerState } from '@mui/x-date-pickers/models'; +import { FieldRef, InferError } from '@mui/x-date-pickers/models'; import { DesktopRangePickerAdditionalViewProps, UseDesktopRangePickerParams, UseDesktopRangePickerProps, - UseDesktopRangePickerSlotProps, } from './useDesktopRangePicker.types'; import { RangePickerPropsForFieldSlot, @@ -129,24 +128,18 @@ export const useDesktopRangePicker = < }; const Field = slots.field; - const fieldProps = useSlotProps< - typeof Field, - UseDesktopRangePickerSlotProps['field'], - RangePickerPropsForFieldSlot< - boolean, - TEnableAccessibleFieldDOMStructure, - InferError - >, - PickerOwnerState - >({ + + const fieldProps: RangePickerPropsForFieldSlot< + boolean, + TEnableAccessibleFieldDOMStructure, + InferError + > = useSlotProps({ elementType: Field, externalSlotProps: slotProps?.field, additionalProps: { - ...pickerFieldProps, + // Internal props readOnly, disabled, - className, - sx, format, formatDensity, enableAccessibleFieldDOMStructure, @@ -154,8 +147,14 @@ export const useDesktopRangePicker = < onSelectedSectionsChange, timezone, autoFocus: autoFocus && !props.open, + ...pickerFieldProps, // onChange and value + + // Forwarded props + className, + sx, ref: fieldContainerRef, - ...(fieldType === 'single-input' ? { inputRef, name } : {}), + ...(fieldType === 'single-input' && !!inputRef && { inputRef }), + ...(fieldType === 'single-input' && { name }), }, ownerState, }); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts index 80b5dc2b99ee..2811bf6cda00 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts @@ -1,18 +1,12 @@ import * as React from 'react'; import Stack, { StackProps } from '@mui/material/Stack'; import Typography, { TypographyProps } from '@mui/material/Typography'; -import TextField from '@mui/material/TextField'; import { SlotComponentProps } from '@mui/utils'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import useEventCallback from '@mui/utils/useEventCallback'; import useForkRef from '@mui/utils/useForkRef'; import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { - BaseSingleInputFieldProps, - FieldSelectedSections, - FieldRef, - PickerOwnerState, -} from '@mui/x-date-pickers/models'; +import { FieldSelectedSections, FieldRef, PickerOwnerState } from '@mui/x-date-pickers/models'; import { UseClearableFieldSlots, UseClearableFieldSlotProps, @@ -24,18 +18,21 @@ import { UsePickerResponse, WrapperVariant, DateOrTimeViewWithMeridiem, + BaseSingleInputFieldProps, PickerRangeValue, } from '@mui/x-date-pickers/internals'; +import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { - BaseMultiInputFieldProps, MultiInputFieldSlotRootProps, MultiInputFieldSlotTextFieldProps, RangeFieldSection, RangePosition, FieldType, UseDateRangeFieldProps, + PickerRangeFieldSlotProps, } from '../../models'; import { UseRangePositionResponse } from './useRangePosition'; +import { BaseMultiInputFieldProps } from '../models/fields'; export interface RangePickerFieldSlots extends UseClearableFieldSlots { field: React.ElementType; @@ -60,11 +57,10 @@ export interface RangePickerFieldSlots extends UseClearableFieldSlots { export interface RangePickerFieldSlotProps extends UseClearableFieldSlotProps { field?: SlotComponentPropsFromProps< - BaseMultiInputFieldProps< + PickerRangeFieldSlotProps< PickerRangeValue, RangeFieldSection, - TEnableAccessibleFieldDOMStructure, - unknown + TEnableAccessibleFieldDOMStructure >, {}, PickerOwnerState @@ -72,7 +68,7 @@ export interface RangePickerFieldSlotProps>; fieldSeparator?: SlotComponentProps>; textField?: SlotComponentProps< - typeof TextField, + typeof PickersTextField, {}, UseDateRangeFieldProps & { position?: RangePosition } >; @@ -408,8 +404,8 @@ const useSingleInputFieldSlotProps = < const slotProps = { ...fieldProps.slotProps, textField: pickerSlotProps?.textField, - clearButton: pickerSlots?.clearButton, - clearIcon: pickerSlots?.clearIcon, + clearButton: pickerSlotProps?.clearButton, + clearIcon: pickerSlotProps?.clearIcon, }; const enrichedFieldProps: ReturnType = { 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 e7679af817a9..4dba5c2eea8b 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 @@ -12,13 +12,12 @@ import { PickerRangeValue, } from '@mui/x-date-pickers/internals'; import { usePickerTranslations } from '@mui/x-date-pickers/hooks'; -import { FieldRef, InferError, PickerOwnerState } from '@mui/x-date-pickers/models'; +import { FieldRef, InferError } from '@mui/x-date-pickers/models'; import useId from '@mui/utils/useId'; import { MobileRangePickerAdditionalViewProps, UseMobileRangePickerParams, UseMobileRangePickerProps, - UseMobileRangePickerSlotProps, } from './useMobileRangePicker.types'; import { RangePickerPropsForFieldSlot, @@ -105,31 +104,30 @@ export const useMobileRangePicker = < const Field = slots.field; - const fieldProps = useSlotProps< - typeof Field, - UseMobileRangePickerSlotProps['field'], - RangePickerPropsForFieldSlot< - boolean, - TEnableAccessibleFieldDOMStructure, - InferError - >, - PickerOwnerState - >({ + const fieldProps: RangePickerPropsForFieldSlot< + boolean, + TEnableAccessibleFieldDOMStructure, + InferError + > = useSlotProps({ elementType: Field, externalSlotProps: innerSlotProps?.field, additionalProps: { - ...pickerFieldProps, + // Internal props readOnly: readOnly ?? true, disabled, - className, - sx, format, formatDensity, enableAccessibleFieldDOMStructure, selectedSections, onSelectedSectionsChange, timezone, - ...(fieldType === 'single-input' ? { inputRef, name } : {}), + ...pickerFieldProps, // onChange and value + + // Forwarded props + className, + sx, + ...(fieldType === 'single-input' && !!inputRef && { inputRef }), + ...(fieldType === 'single-input' && { name }), }, ownerState, }); diff --git a/packages/x-date-pickers-pro/src/internals/models/fields.ts b/packages/x-date-pickers-pro/src/internals/models/fields.ts new file mode 100644 index 000000000000..79a498efbd54 --- /dev/null +++ b/packages/x-date-pickers-pro/src/internals/models/fields.ts @@ -0,0 +1,61 @@ +import { SxProps } from '@mui/material/styles'; +import { SlotComponentProps } from '@mui/utils'; +import { MakeRequired } from '@mui/x-internals/types'; +import { UseFieldInternalProps } from '@mui/x-date-pickers/internals'; +import { FieldSection } from '@mui/x-date-pickers/models'; +import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import type { + MultiInputFieldRefs, + MultiInputFieldSlotRootProps, + RangeFieldSeparatorProps, + RangePosition, +} from '../../models'; + +/** + * Props the multi input field can receive when used inside a picker. + * Only contains what the MUI components are passing to the field, not what users can pass using the `props.slotProps.field`. + */ +export interface BaseMultiInputFieldProps< + TValue, + TSection extends FieldSection, + TEnableAccessibleFieldDOMStructure extends boolean, + TError, +> extends MakeRequired< + Pick< + UseFieldInternalProps, + | 'readOnly' + | 'disabled' + | 'format' + | 'formatDensity' + | 'enableAccessibleFieldDOMStructure' + | 'selectedSections' + | 'onSelectedSectionsChange' + | 'timezone' + | 'autoFocus' + | 'value' + | 'onChange' + >, + 'format' | 'value' | 'onChange' | 'timezone' + >, + RangeFieldSeparatorProps, + MultiInputFieldRefs { + className: string | undefined; + sx: SxProps | undefined; + slots?: { + root?: React.ElementType; + separator?: React.ElementType; + textField?: React.ElementType; + }; + slotProps?: { + root?: SlotComponentProps< + React.ElementType, + {}, + Record + >; + textField?: SlotComponentProps< + typeof PickersTextField, + {}, + { position?: RangePosition } & Record + >; + }; +} diff --git a/packages/x-date-pickers-pro/src/models/fields.ts b/packages/x-date-pickers-pro/src/models/fields.ts index 5c09a4aacd45..09c12ac54307 100644 --- a/packages/x-date-pickers-pro/src/models/fields.ts +++ b/packages/x-date-pickers-pro/src/models/fields.ts @@ -1,14 +1,7 @@ import * as React from 'react'; -import { SlotComponentProps } from '@mui/utils'; -import { BaseFieldProps, UseFieldResponse, FormProps } from '@mui/x-date-pickers/internals'; -import { - BaseSingleInputPickersTextFieldProps, - FieldRef, - FieldSection, -} from '@mui/x-date-pickers/models'; +import { UseFieldResponse, FormProps } from '@mui/x-date-pickers/internals'; +import { FieldRef, FieldSection, PickerFieldSlotProps } from '@mui/x-date-pickers/models'; import { UseClearableFieldResponse } from '@mui/x-date-pickers/hooks'; -import { SxProps } from '@mui/material/styles'; -import TextField from '@mui/material/TextField'; import { RangePosition } from './range'; export interface RangeFieldSection extends FieldSection { @@ -55,41 +48,14 @@ export interface RangeFieldSeparatorProps { } /** - * Props the multi input field can receive when used inside a picker. - * Only contains what the MUI components are passing to the field, - * not what users can pass using the `props.slotProps.field`. + * Props the `slotProps.field` of a range picker component can receive. */ -export interface BaseMultiInputFieldProps< +export type PickerRangeFieldSlotProps< TValue, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, - TError, -> extends Omit< - BaseFieldProps, - 'unstableFieldRef' - >, - RangeFieldSeparatorProps { - sx?: SxProps; - unstableStartFieldRef?: React.Ref>; - unstableEndFieldRef?: React.Ref>; - slots?: { - root?: React.ElementType; - separator?: React.ElementType; - textField?: React.ElementType; - }; - slotProps?: { - root?: SlotComponentProps< - React.ElementType, - {}, - Record - >; - textField?: SlotComponentProps< - typeof TextField, - {}, - { position?: RangePosition } & Record - >; - }; -} +> = PickerFieldSlotProps & + RangeFieldSeparatorProps; /** * Props the text field receives when used with a multi input picker. @@ -100,11 +66,3 @@ export type BaseMultiInputPickersTextFieldProps< > = UseClearableFieldResponse< UseFieldResponse >; - -/** - * Props the text field receives when used with a single or multi input picker. - * Only contains what the MUI components are passing to the text field, not what users can pass using the `props.slotProps.field` or `props.slotProps.textField`. - */ -export type BasePickersTextFieldProps = - BaseSingleInputPickersTextFieldProps & - BaseMultiInputPickersTextFieldProps; diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts index 3b80ff310dad..6ea627c4a5eb 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts @@ -1,22 +1,16 @@ -import { MakeRequired } from '@mui/x-internals/types'; -import { UseDateFieldProps } from '../DateField'; import { DesktopDatePickerProps, DesktopDatePickerSlots, DesktopDatePickerSlotProps, } from '../DesktopDatePicker'; -import { BaseDateValidationProps } from '../internals/models/validation'; +import { BaseSingleInputFieldProps } from '../internals/models'; import { MobileDatePickerProps, MobileDatePickerSlots, MobileDatePickerSlotProps, } from '../MobileDatePicker'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, - PickerValidDate, -} from '../models'; +import { DateValidationError, FieldSection, PickerValidDate } from '../models'; +import { ValidateDateProps } from '../validation/validateDate'; export interface DatePickerSlots extends DesktopDatePickerSlots, MobileDatePickerSlots {} @@ -51,11 +45,13 @@ export interface DatePickerProps, or component). */ export type DatePickerFieldProps = - MakeRequired< - UseDateFieldProps, - 'format' | 'timezone' | 'value' | keyof BaseDateValidationProps - > & - BaseSingleInputFieldProps; + ValidateDateProps & + BaseSingleInputFieldProps< + PickerValidDate | null, + FieldSection, + TEnableAccessibleFieldDOMStructure, + DateValidationError + >; diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts index fd81624ee3bd..a461ac290f65 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts @@ -1,23 +1,16 @@ -import { MakeRequired } from '@mui/x-internals/types'; -import { UseDateTimeFieldProps } from '../DateTimeField'; import { DesktopDateTimePickerProps, DesktopDateTimePickerSlots, DesktopDateTimePickerSlotProps, } from '../DesktopDateTimePicker'; -import { DateOrTimeViewWithMeridiem } from '../internals/models'; -import { BaseDateValidationProps, BaseTimeValidationProps } from '../internals/models/validation'; +import { BaseSingleInputFieldProps, DateOrTimeViewWithMeridiem } from '../internals/models'; import { MobileDateTimePickerProps, MobileDateTimePickerSlots, MobileDateTimePickerSlotProps, } from '../MobileDateTimePicker'; -import { - BaseSingleInputFieldProps, - DateTimeValidationError, - FieldSection, - PickerValidDate, -} from '../models'; +import { DateTimeValidationError, FieldSection, PickerValidDate } from '../models'; +import { ValidateDateTimeProps } from '../validation'; import { ExportedYearCalendarProps } from '../YearCalendar/YearCalendar.types'; export interface DateTimePickerSlots @@ -59,17 +52,7 @@ export interface DateTimePickerProps, or component). */ -export type DateTimePickerFieldProps = - MakeRequired< - UseDateTimeFieldProps, - | 'format' - | 'timezone' - | 'value' - | 'ampm' - | keyof BaseDateValidationProps - | keyof BaseTimeValidationProps - > & - BaseSingleInputFieldProps; +export type DateTimePickerFieldProps = ValidateDateTimeProps & + BaseSingleInputFieldProps; diff --git a/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts b/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts index 4a22286b2b10..787f4c68fba4 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts +++ b/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts @@ -1,23 +1,16 @@ -import { MakeRequired } from '@mui/x-internals/types'; import { DesktopTimePickerProps, DesktopTimePickerSlots, DesktopTimePickerSlotProps, } from '../DesktopTimePicker'; -import { TimeViewWithMeridiem } from '../internals/models'; -import { BaseTimeValidationProps } from '../internals/models/validation'; +import { BaseSingleInputFieldProps, TimeViewWithMeridiem } from '../internals/models'; import { MobileTimePickerProps, MobileTimePickerSlots, MobileTimePickerSlotProps, } from '../MobileTimePicker'; -import { - BaseSingleInputFieldProps, - FieldSection, - PickerValidDate, - TimeValidationError, -} from '../models'; -import { UseTimeFieldProps } from '../TimeField'; +import { FieldSection, PickerValidDate, TimeValidationError } from '../models'; +import { ValidateTimeProps } from '../validation/validateTime'; export interface TimePickerSlots extends DesktopTimePickerSlots, @@ -49,12 +42,13 @@ export interface TimePickerProps, or component). */ export type TimePickerFieldProps = - MakeRequired< - UseTimeFieldProps, - 'format' | 'timezone' | 'value' | 'ampm' | keyof BaseTimeValidationProps - > & - BaseSingleInputFieldProps; + ValidateTimeProps & + BaseSingleInputFieldProps< + PickerValidDate | null, + FieldSection, + TEnableAccessibleFieldDOMStructure, + TimeValidationError + >; 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 b9e5f1edb0be..0650af229e2a 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -5,22 +5,11 @@ import IconButton from '@mui/material/IconButton'; import useForkRef from '@mui/utils/useForkRef'; import useId from '@mui/utils/useId'; import { PickersPopper } from '../../components/PickersPopper'; -import { - UseDesktopPickerParams, - UseDesktopPickerProps, - UseDesktopPickerSlotProps, -} from './useDesktopPicker.types'; +import { UseDesktopPickerParams, UseDesktopPickerProps } from './useDesktopPicker.types'; import { usePicker } from '../usePicker'; import { PickersLayout } from '../../../PickersLayout'; -import { - FieldSection, - PickerValidDate, - FieldRef, - BaseSingleInputFieldProps, - InferError, - PickerOwnerState, -} from '../../../models'; -import { DateOrTimeViewWithMeridiem } from '../../models'; +import { FieldSection, PickerValidDate, FieldRef, InferError } from '../../../models'; +import { BaseSingleInputFieldProps, DateOrTimeViewWithMeridiem } from '../../models'; import { PickerProvider } from '../../components/PickerProvider'; /** @@ -121,39 +110,35 @@ export const useDesktopPicker = < }); const Field = slots.field; - const fieldProps = useSlotProps< - typeof Field, - UseDesktopPickerSlotProps['field'], - Partial< - BaseSingleInputFieldProps< - PickerValidDate | null, - FieldSection, - TEnableAccessibleFieldDOMStructure, - InferError - > - >, - PickerOwnerState - >({ + const fieldProps: BaseSingleInputFieldProps< + PickerValidDate | null, + FieldSection, + TEnableAccessibleFieldDOMStructure, + InferError + > = useSlotProps({ elementType: Field, externalSlotProps: innerSlotProps?.field, additionalProps: { - ...pickerFieldProps, - ...(isToolbarHidden && { id: labelId }), + // Internal props readOnly, disabled, - className, - sx, format, formatDensity, enableAccessibleFieldDOMStructure, selectedSections, onSelectedSectionsChange, timezone, + autoFocus: autoFocus && !props.open, + ...pickerFieldProps, // onChange and value + + // Forwarded props + className, + sx, label, name, - autoFocus: autoFocus && !props.open, focused: open ? true : undefined, - ...(inputRef ? { inputRef } : {}), + ...(isToolbarHidden && { id: labelId }), + ...(!!inputRef && { inputRef }), }, ownerState, }); 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 3e207b42029a..8aead242e00d 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 @@ -3,7 +3,7 @@ import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import { InputAdornmentProps } from '@mui/material/InputAdornment'; import TextField from '@mui/material/TextField'; import { SlotComponentProps } from '@mui/utils'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { BaseNonStaticPickerProps, BasePickerProps, @@ -12,8 +12,8 @@ import { import { PickersPopperSlots, PickersPopperSlotProps } from '../../components/PickersPopper'; import { UsePickerParams } from '../usePicker'; import { - BaseSingleInputFieldProps, FieldSection, + PickerFieldSlotProps, PickerOwnerState, PickerValidDate, } from '../../../models'; @@ -75,12 +75,7 @@ export interface ExportedUseDesktopPickerSlotProps< ExportedPickersLayoutSlotProps, UseClearableFieldSlotProps { field?: SlotComponentPropsFromProps< - BaseSingleInputFieldProps< - PickerValidDate | null, - FieldSection, - TEnableAccessibleFieldDOMStructure, - unknown - >, + PickerFieldSlotProps, {}, PickerOwnerState >; @@ -108,7 +103,7 @@ export interface UseDesktopPickerProps< TError, TExternalProps extends UsePickerViewsProps, > extends BasePickerProps, - DesktopOnlyPickerProps { + MakeRequired { /** * Overridable component slots. * @default {} 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 088160ff9b5e..4b6e52ff244a 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -3,23 +3,12 @@ import useSlotProps from '@mui/utils/useSlotProps'; import useForkRef from '@mui/utils/useForkRef'; import useId from '@mui/utils/useId'; import { PickersModalDialog } from '../../components/PickersModalDialog'; -import { - UseMobilePickerParams, - UseMobilePickerProps, - UseMobilePickerSlotProps, -} from './useMobilePicker.types'; +import { UseMobilePickerParams, UseMobilePickerProps } from './useMobilePicker.types'; import { usePicker } from '../usePicker'; import { onSpaceOrEnter } from '../../utils/utils'; import { PickersLayout } from '../../../PickersLayout'; -import { - FieldSection, - BaseSingleInputFieldProps, - PickerValidDate, - FieldRef, - InferError, - PickerOwnerState, -} from '../../../models'; -import { DateOrTimeViewWithMeridiem } from '../../models'; +import { FieldSection, PickerValidDate, FieldRef, InferError } from '../../../models'; +import { BaseSingleInputFieldProps, DateOrTimeViewWithMeridiem } from '../../models'; import { PickerProvider } from '../../components/PickerProvider'; /** @@ -85,41 +74,37 @@ export const useMobilePicker = < }); const Field = slots.field; - const fieldProps = useSlotProps< - typeof Field, - UseMobilePickerSlotProps['field'], - Partial< - BaseSingleInputFieldProps< - PickerValidDate | null, - FieldSection, - TEnableAccessibleFieldDOMStructure, - InferError - > - >, - PickerOwnerState - >({ + const fieldProps: BaseSingleInputFieldProps< + PickerValidDate | null, + FieldSection, + TEnableAccessibleFieldDOMStructure, + InferError + > = useSlotProps({ elementType: Field, externalSlotProps: innerSlotProps?.field, additionalProps: { - ...pickerFieldProps, - ...(isToolbarHidden && { id: labelId }), - ...(!(disabled || readOnly) && { - onClick: actions.onOpen, - onKeyDown: onSpaceOrEnter(actions.onOpen), - }), + // Internal props readOnly: readOnly ?? true, disabled, - className, - sx, format, formatDensity, enableAccessibleFieldDOMStructure, selectedSections, onSelectedSectionsChange, timezone, + ...pickerFieldProps, // onChange and value + + // Forwarded props + className, + sx, label, name, - ...(inputRef ? { inputRef } : {}), + ...(isToolbarHidden && { id: labelId }), + ...(!(disabled || readOnly) && { + onClick: actions.onOpen, + onKeyDown: onSpaceOrEnter(actions.onOpen), + }), + ...(!!inputRef && { inputRef }), }, ownerState, }); 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 27824fd3df82..a811500a7a67 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,7 +1,7 @@ import * as React from 'react'; import TextField from '@mui/material/TextField'; import { SlotComponentProps } from '@mui/utils'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { BaseNonStaticPickerProps, BasePickerProps, @@ -13,8 +13,8 @@ import { } from '../../components/PickersModalDialog'; import { UsePickerParams } from '../usePicker'; import { - BaseSingleInputFieldProps, FieldSection, + PickerFieldSlotProps, PickerOwnerState, PickerValidDate, } from '../../../models'; @@ -47,12 +47,7 @@ export interface ExportedUseMobilePickerSlotProps< > extends PickersModalDialogSlotProps, ExportedPickersLayoutSlotProps { field?: SlotComponentPropsFromProps< - BaseSingleInputFieldProps< - PickerValidDate | null, - FieldSection, - TEnableAccessibleFieldDOMStructure, - unknown - >, + PickerFieldSlotProps, {}, PickerOwnerState >; @@ -77,7 +72,7 @@ export interface UseMobilePickerProps< TError, TExternalProps extends UsePickerViewsProps, > extends BasePickerProps, - MobileOnlyPickerProps { + MakeRequired { /** * Overridable component slots. * @default {} diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 84d70729c57a..56be2c11a7ac 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -96,7 +96,7 @@ export type { ExportedUseViewsOptions, UseViewsOptions } from './hooks/useViews' export { useViews } from './hooks/useViews'; export { usePreviousMonthDisabled, useNextMonthDisabled } from './hooks/date-helpers-hooks'; -export type { BaseFieldProps } from './models/fields'; +export type { BaseSingleInputFieldProps } from './models/fields'; export type { BasePickerProps, BasePickerInputProps, diff --git a/packages/x-date-pickers/src/internals/models/fields.ts b/packages/x-date-pickers/src/internals/models/fields.ts index 81063dcd344a..5e8e5102cf3e 100644 --- a/packages/x-date-pickers/src/internals/models/fields.ts +++ b/packages/x-date-pickers/src/internals/models/fields.ts @@ -1,20 +1,64 @@ -import * as React from 'react'; +import { SxProps } from '@mui/material/styles'; +import { MakeRequired } from '@mui/x-internals/types'; +import type { + ExportedUseClearableFieldProps, + UseClearableFieldSlotProps, + UseClearableFieldSlots, +} from '../../hooks/useClearableField'; +import type { FieldSection, PickerOwnerState } from '../../models'; import type { UseFieldInternalProps } from '../hooks/useField'; -import { FieldSection, PickerOwnerState } from '../../models'; -import type { ExportedUseClearableFieldProps } from '../../hooks/useClearableField'; -export interface BaseFieldProps< +export interface BaseForwardedSingleInputFieldProps extends ExportedUseClearableFieldProps { + className: string | undefined; + sx: SxProps | undefined; + label: React.ReactNode | undefined; + name: string | undefined; + id?: string; + focused?: boolean; + onKeyDown?: React.KeyboardEventHandler; + 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; +} + +/** + * Props the single input field can receive when used inside a picker. + * Only contains what the MUI components are passing to the field, not what users can pass using the `props.slotProps.field`. + */ +export type BaseSingleInputFieldProps< TValue, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, TError, -> extends Omit< - UseFieldInternalProps, - 'format' - >, - ExportedUseClearableFieldProps { - className?: string; - format?: string; - ref?: React.Ref; - ownerState?: PickerOwnerState; -} +> = MakeRequired< + Pick< + UseFieldInternalProps, + | 'readOnly' + | 'disabled' + | 'format' + | 'formatDensity' + | 'enableAccessibleFieldDOMStructure' + | 'selectedSections' + | 'onSelectedSectionsChange' + | 'timezone' + | 'value' + | 'onChange' + | 'unstableFieldRef' + | 'autoFocus' + >, + 'format' | 'value' | 'onChange' | 'timezone' +> & + BaseForwardedSingleInputFieldProps; diff --git a/packages/x-date-pickers/src/internals/models/validation.ts b/packages/x-date-pickers/src/internals/models/validation.ts index bd4f6aee2fc2..aa67e43f251f 100644 --- a/packages/x-date-pickers/src/internals/models/validation.ts +++ b/packages/x-date-pickers/src/internals/models/validation.ts @@ -1,4 +1,4 @@ -import { PickerValidDate, TimeView } from '../../models'; +import type { PickerValidDate, TimeView } from '../../models'; interface FutureAndPastValidationProps { /** diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index 9d853e8d2ed8..000e1d7d2ce1 100644 --- a/packages/x-date-pickers/src/models/fields.ts +++ b/packages/x-date-pickers/src/models/fields.ts @@ -1,16 +1,13 @@ import * as React from 'react'; import { TextFieldProps } from '@mui/material/TextField'; -import { SxProps } from '@mui/material/styles'; -import type { BaseFieldProps } from '../internals/models/fields'; import type { ExportedUseClearableFieldProps, UseClearableFieldResponse, - UseClearableFieldSlotProps, - UseClearableFieldSlots, } from '../hooks/useClearableField'; -import { ExportedPickersSectionListProps, PickersSectionListRef } from '../PickersSectionList'; -import type { UseFieldResponse } from '../internals/hooks/useField'; +import { ExportedPickersSectionListProps } from '../PickersSectionList'; +import type { UseFieldInternalProps, UseFieldResponse } from '../internals/hooks/useField'; import type { PickersTextFieldProps } from '../PickersTextField'; +import { BaseForwardedSingleInputFieldProps } from '../internals/models'; // Update PickersComponentAgnosticLocaleText -> viewNames when adding new entries export type FieldSectionType = @@ -122,55 +119,21 @@ export interface FieldRef { export type FieldSelectedSections = number | FieldSectionType | null | 'all'; -interface BaseForwardedCommonSingleInputFieldProps extends ExportedUseClearableFieldProps { - ref?: React.Ref; - sx?: SxProps; - label?: React.ReactNode; - id?: string; - name?: string; - onKeyDown?: React.KeyboardEventHandler; - onBlur?: React.FocusEventHandler; - focused?: boolean; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - inputProps?: { - 'aria-label'?: string; - }; - slots?: UseClearableFieldSlots; - slotProps?: UseClearableFieldSlotProps & { - textField?: {}; - }; -} - -interface BaseForwardedV6SingleInputFieldProps { - inputRef?: React.Ref; -} - -interface BaseForwardedV7SingleInputFieldProps { - sectionListRef?: React.Ref; -} - -type BaseForwardedSingleInputFieldProps = - BaseForwardedCommonSingleInputFieldProps & - (TEnableAccessibleFieldDOMStructure extends false - ? BaseForwardedV6SingleInputFieldProps - : BaseForwardedV7SingleInputFieldProps); - /** - * Props the single input field can receive when used inside a picker. - * Only contains what the MUI components are passing to the field, - * not what users can pass using the `props.slotProps.field`. + * Props the prop `slotProps.field` of a picker can receive. */ -export type BaseSingleInputFieldProps< +export type PickerFieldSlotProps< TValue, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, - TError, -> = BaseFieldProps & - BaseForwardedSingleInputFieldProps; +> = ExportedUseClearableFieldProps & + Pick< + UseFieldInternalProps, + 'shouldRespectLeadingZeros' | 'readOnly' + > & + React.HTMLAttributes & { + ref?: React.Ref; + }; /** * Props the text field receives when used with a single input picker. @@ -179,10 +142,7 @@ export type BaseSingleInputFieldProps< export type BaseSingleInputPickersTextFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > = UseClearableFieldResponse< - UseFieldResponse< - TEnableAccessibleFieldDOMStructure, - BaseForwardedSingleInputFieldProps - > + UseFieldResponse >; /** diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index b51f98218b0a..ab354bb99dac 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -4,10 +4,7 @@ { "name": "ArrowDropDownIcon", "kind": "Variable" }, { "name": "ArrowLeftIcon", "kind": "Variable" }, { "name": "ArrowRightIcon", "kind": "Variable" }, - { "name": "BaseMultiInputFieldProps", "kind": "Interface" }, { "name": "BaseMultiInputPickersTextFieldProps", "kind": "TypeAlias" }, - { "name": "BasePickersTextFieldProps", "kind": "TypeAlias" }, - { "name": "BaseSingleInputFieldProps", "kind": "TypeAlias" }, { "name": "BaseSingleInputPickersTextFieldProps", "kind": "TypeAlias" }, { "name": "BuiltInFieldTextFieldProps", "kind": "TypeAlias" }, { "name": "CalendarIcon", "kind": "Variable" }, @@ -250,8 +247,10 @@ { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, { "name": "PickerDayOwnerState", "kind": "Interface" }, + { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, + { "name": "PickerRangeFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickersActionBar", "kind": "Function" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" }, { "name": "PickersActionBarProps", "kind": "Interface" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index 42fb0b3cc786..f50db66778ff 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -4,7 +4,6 @@ { "name": "ArrowDropDownIcon", "kind": "Variable" }, { "name": "ArrowLeftIcon", "kind": "Variable" }, { "name": "ArrowRightIcon", "kind": "Variable" }, - { "name": "BaseSingleInputFieldProps", "kind": "TypeAlias" }, { "name": "BaseSingleInputPickersTextFieldProps", "kind": "TypeAlias" }, { "name": "BuiltInFieldTextFieldProps", "kind": "TypeAlias" }, { "name": "CalendarIcon", "kind": "Variable" }, @@ -164,6 +163,7 @@ { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, { "name": "PickerDayOwnerState", "kind": "Interface" }, + { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickersActionBar", "kind": "Function" },