diff --git a/docs/data/date-pickers/date-range-field/BasicDateRangeField.js b/docs/data/date-pickers/date-range-field/BasicDateRangeField.js
index 06e7c389b07f8..6cc983bc9b2ee 100644
--- a/docs/data/date-pickers/date-range-field/BasicDateRangeField.js
+++ b/docs/data/date-pickers/date-range-field/BasicDateRangeField.js
@@ -6,11 +6,18 @@ import { Unstable_MultiInputDateRangeField as MultiInputDateRangeField } from '@
import { Unstable_SingleInputDateRangeField as SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
export default function BasicDateRangeField() {
+ const [value, setValue] = React.useState([null, null]);
return (
-
-
+ setValue(newValue)}
+ />
+ setValue(newValue)}
+ />
);
diff --git a/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx b/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx
index 06e7c389b07f8..92cdc1f0f77b9 100644
--- a/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx
+++ b/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx
@@ -6,11 +6,18 @@ import { Unstable_MultiInputDateRangeField as MultiInputDateRangeField } from '@
import { Unstable_SingleInputDateRangeField as SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
export default function BasicDateRangeField() {
+ const [value, setValue] = React.useState([null, null]);
return (
-
-
+ setValue(newValue)}
+ />
+ setValue(newValue)}
+ />
);
diff --git a/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx.preview b/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx.preview
index 13214cf368c44..ed200657dde99 100644
--- a/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx.preview
+++ b/docs/data/date-pickers/date-range-field/BasicDateRangeField.tsx.preview
@@ -1,6 +1,12 @@
-
-
+ setValue(newValue)}
+ />
+ setValue(newValue)}
+ />
\ No newline at end of file
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerInput.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerInput.tsx
index 0e7cdbfbe753a..a892527288f07 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerInput.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerInput.tsx
@@ -11,7 +11,7 @@ import {
onSpaceOrEnter,
useLocaleText,
} from '@mui/x-date-pickers/internals';
-import { CurrentlySelectingRangeEndProps, DateRange } from '../internal/models/dateRange';
+import { CurrentlySelectingRangeEndProps, DateRange } from '../internal/models/range';
import { DateRangeValidationError } from '../internal/hooks/validation/useDateRangeValidation';
import {
DateRangePickerInputClasses,
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerView.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerView.tsx
index c6219a6f2d5d4..e80d96da0865a 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerView.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerView.tsx
@@ -13,11 +13,8 @@ import {
BaseDateValidationProps,
DayValidationProps,
} from '@mui/x-date-pickers/internals';
-import {
- DateRange,
- CurrentlySelectingRangeEndProps,
- DayRangeValidationProps,
-} from '../internal/models/dateRange';
+import { DateRange, CurrentlySelectingRangeEndProps } from '../internal/models/range';
+import { DayRangeValidationProps } from '../internal/models/dateRange';
import { isRangeValid } from '../internal/utils/date-utils';
import { calculateRangeChange } from './date-range-manager';
import { DateRangePickerToolbar } from './DateRangePickerToolbar';
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewDesktop.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewDesktop.tsx
index 7087cb4b21dd8..1b9cfec7c9ef6 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewDesktop.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewDesktop.tsx
@@ -21,7 +21,7 @@ import {
DayCalendarSlotsComponentsProps,
} from '@mui/x-date-pickers/internals';
import { calculateRangePreview } from './date-range-manager';
-import { DateRange } from '../internal/models';
+import { DateRange } from '../internal/models/range';
import { DateRangePickerDay, DateRangePickerDayProps } from '../DateRangePickerDay';
import { isWithinRange, isStartOfRange, isEndOfRange } from '../internal/utils/date-utils';
import { doNothing } from '../internal/utils/utils';
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewMobile.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewMobile.tsx
index c785d31c9fa20..7f20f0fa8416d 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewMobile.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerViewMobile.tsx
@@ -14,8 +14,9 @@ import {
DayCalendarSlotsComponentsProps,
} from '@mui/x-date-pickers/internals';
import { doNothing } from '../internal/utils/utils';
-import { DateRange } from '../internal/models/dateRange';
+import { DateRange } from '../internal/models/range';
import { DateRangePickerDay, DateRangePickerDayProps } from '../DateRangePickerDay';
+
import { isWithinRange, isStartOfRange, isEndOfRange } from '../internal/utils/date-utils';
export interface DateRangePickerViewMobileSlotsComponent
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/date-range-manager.test.ts b/packages/x-date-pickers-pro/src/DateRangePicker/date-range-manager.test.ts
index 6847065b5a34d..f2a12ae188c90 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/date-range-manager.test.ts
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/date-range-manager.test.ts
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { adapterToUse } from 'test/utils/pickers-utils';
import { calculateRangeChange, calculateRangePreview } from './date-range-manager';
-import { DateRange } from '../internal/models/dateRange';
+import { DateRange } from '../internal/models/range';
const start2018 = adapterToUse.date(new Date(2018, 0, 1));
const mid2018 = adapterToUse.date(new Date(2018, 6, 1));
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts
index ece2743486c1e..1d51914778197 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts
@@ -67,6 +67,7 @@ export function useDateRangePickerDefaultizedProps<
};
}
+// What about renaming it `rangePickerValueManager` such that it's clear this manager is common to date, time and dateTime?
export const dateRangePickerValueManager: PickerStateValueManager<[any, any], any> = {
emptyValue: [null, null],
getTodayValue: (utils) => [utils.date()!, utils.date()!],
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.interfaces.ts b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.interfaces.ts
deleted file mode 100644
index 02793d82c4d19..0000000000000
--- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.interfaces.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { UseSingleInputDateRangeFieldProps } from '../SingleInputDateRangeField';
-
-export interface UseMultiInputDateRangeFieldProps
- extends UseSingleInputDateRangeFieldProps {}
-
-export interface MultiInputDateRangeFieldProps
- extends UseMultiInputDateRangeFieldProps {}
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx
index e7f91fa5b4448..90aa0617bf30b 100644
--- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx
+++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx
@@ -5,7 +5,7 @@ import Typography, { TypographyProps } from '@mui/material/Typography';
import { styled, useThemeProps } from '@mui/material/styles';
import { useSlotProps } from '@mui/base/utils';
import { MultiInputDateRangeFieldProps } from './MultiInputDateRangeField.types';
-import { useMultiInputDateRangeField } from './useMultiInputDateRangeField';
+import { useMultiInputDateRangeField } from '../internal/hooks/useMultiInputRangeField/useMultiInputDateRangeField';
const MultiInputDateRangeFieldRoot = styled(
React.forwardRef((props: StackProps, ref: React.Ref) => (
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.types.ts b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.types.ts
index f10af189973a1..f4ebdb976c351 100644
--- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.types.ts
+++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.types.ts
@@ -3,18 +3,18 @@ import { SlotComponentProps } from '@mui/base/utils';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
-import { UseSingleInputDateRangeFieldProps } from '../SingleInputDateRangeField';
+import { UseDateRangeFieldProps } from '../internal/models/dateRange';
export interface UseMultiInputDateRangeFieldParams {
- sharedProps: UseMultiInputDateRangeFieldComponentProps;
+ sharedProps: Omit> &
+ UseMultiInputDateRangeFieldProps;
startInputProps: TChildProps;
endInputProps: TChildProps;
startInputRef?: React.Ref;
endInputRef?: React.Ref;
}
-export interface UseMultiInputDateRangeFieldProps
- extends UseSingleInputDateRangeFieldProps {}
+export interface UseMultiInputDateRangeFieldProps extends UseDateRangeFieldProps {}
export type UseMultiInputDateRangeFieldComponentProps = Omit<
TChildProps,
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx
new file mode 100644
index 0000000000000..c6031f018f45c
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx
@@ -0,0 +1,147 @@
+import * as React from 'react';
+import Stack, { StackProps } from '@mui/material/Stack';
+import TextField, { TextFieldProps } from '@mui/material/TextField';
+import Typography, { TypographyProps } from '@mui/material/Typography';
+import { styled, useThemeProps } from '@mui/material/styles';
+import { useSlotProps } from '@mui/base/utils';
+import { MultiInputDateTimeRangeFieldProps } from './MultiInputDateTimeRangeField.types';
+import { useMultiInputDateTimeRangeField } from '../internal/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField';
+
+const MultiInputDateTimeRangeFieldRoot = styled(
+ React.forwardRef((props: StackProps, ref: React.Ref) => (
+
+ )),
+ {
+ name: 'MuiMultiInputDateTimeRangeField',
+ slot: 'Root',
+ overridesResolver: (props, styles) => styles.root,
+ },
+)({});
+
+const MultiInputDateTimeRangeFieldSeparator = styled(
+ (props: TypographyProps) => {props.children ?? ' — '},
+ {
+ name: 'MuiMultiInputDateTimeRangeField',
+ slot: 'Separator',
+ overridesResolver: (props, styles) => styles.separator,
+ },
+)({});
+
+type MultiInputDateTimeRangeFieldComponent = ((
+ props: MultiInputDateTimeRangeFieldProps & React.RefAttributes,
+) => JSX.Element) & { propTypes?: any };
+
+export const MultiInputDateTimeRangeField = React.forwardRef(function MultiInputDateTimeRangeField<
+ TDate,
+>(inProps: MultiInputDateTimeRangeFieldProps, ref: React.Ref) {
+ const themeProps = useThemeProps({
+ props: inProps,
+ name: 'MuiMultiInputDateTimeRangeField',
+ });
+
+ const {
+ components,
+ componentsProps,
+ value,
+ defaultValue,
+ format,
+ onChange,
+ readOnly,
+ onError,
+ shouldDisableDate,
+ minDate,
+ maxDate,
+ minTime,
+ maxTime,
+ minDateTime,
+ maxDateTime,
+ minutesStep,
+ shouldDisableTime,
+ disableFuture,
+ disablePast,
+ ...other
+ } = themeProps;
+
+ const ownerState = themeProps;
+
+ const Root = components?.Root ?? MultiInputDateTimeRangeFieldRoot;
+ const rootProps = useSlotProps({
+ elementType: Root,
+ externalSlotProps: componentsProps?.root,
+ externalForwardedProps: other,
+ additionalProps: {
+ ref,
+ },
+ ownerState,
+ });
+
+ const Input = components?.Input ?? TextField;
+ const startInputProps: TextFieldProps = useSlotProps({
+ elementType: Input,
+ externalSlotProps: componentsProps?.input,
+ ownerState: { ...ownerState, position: 'start' },
+ });
+ const endInputProps: TextFieldProps = useSlotProps({
+ elementType: Input,
+ externalSlotProps: componentsProps?.input,
+ ownerState: { ...ownerState, position: 'end' },
+ });
+
+ const Separator = components?.Separator ?? MultiInputDateTimeRangeFieldSeparator;
+ const separatorProps = useSlotProps({
+ elementType: Separator,
+ externalSlotProps: componentsProps?.separator,
+ ownerState,
+ });
+
+ const {
+ startDate: { onKeyDown: onStartInputKeyDown, ref: startInputRef, ...startDateProps },
+ endDate: { onKeyDown: onEndInputKeyDown, ref: endInputRef, ...endDateProps },
+ } = useMultiInputDateTimeRangeField({
+ sharedProps: {
+ value,
+ defaultValue,
+ format,
+ onChange,
+ readOnly,
+ onError,
+ shouldDisableDate,
+ minDate,
+ maxDate,
+ minTime,
+ maxTime,
+ minDateTime,
+ maxDateTime,
+ minutesStep,
+ shouldDisableTime,
+ disableFuture,
+ disablePast,
+ },
+ startInputProps,
+ endInputProps,
+ startInputRef: startInputProps.inputRef,
+ endInputRef: endInputProps.inputRef,
+ });
+
+ return (
+
+
+
+
+
+ );
+}) as MultiInputDateTimeRangeFieldComponent;
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types.ts
new file mode 100644
index 0000000000000..2750e4b4757fd
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types.ts
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import { SlotComponentProps } from '@mui/base/utils';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import TextField, { TextFieldProps } from '@mui/material/TextField';
+import {
+ UseDateTimeRangeFieldDefaultizedProps,
+ UseDateTimeRangeFieldProps,
+} from '../internal/models/dateTimeRange';
+
+export interface UseMultiInputDateTimeRangeFieldParams {
+ sharedProps: Omit> &
+ UseMultiInputDateTimeRangeFieldProps;
+ startInputProps: TChildProps;
+ endInputProps: TChildProps;
+ startInputRef?: React.Ref;
+ endInputRef?: React.Ref;
+}
+
+export interface UseMultiInputDateTimeRangeFieldProps
+ extends UseDateTimeRangeFieldProps {}
+
+export type UseMultiInputDateTimeRangeFieldComponentProps = Omit<
+ TChildProps,
+ keyof UseMultiInputDateTimeRangeFieldProps
+> &
+ UseMultiInputDateTimeRangeFieldProps;
+
+export interface MultiInputDateTimeRangeFieldProps
+ extends UseMultiInputDateTimeRangeFieldComponentProps {
+ /**
+ * Overrideable components.
+ * @default {}
+ */
+ components?: MultiInputDateTimeRangeFieldSlotsComponent;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ componentsProps?: MultiInputDateTimeRangeFieldSlotsComponentsProps;
+}
+
+export type MultiInputDateTimeRangeFieldOwnerState =
+ MultiInputDateTimeRangeFieldProps;
+
+export interface MultiInputDateTimeRangeFieldSlotsComponent {
+ /**
+ * Element rendered at the root.
+ * @default MultiInputDateTimeRangeFieldRoot
+ */
+ Root?: React.ElementType;
+ /**
+ * Input rendered for the start or end date.
+ * @default TextField
+ */
+ Input?: React.ElementType;
+ /**
+ * Element rendered between the two inputs.
+ * @default MultiInputDateTimeRangeFieldSeparator
+ */
+ Separator?: React.ElementType;
+}
+
+export interface MultiInputDateTimeRangeFieldSlotsComponentsProps {
+ root?: SlotComponentProps>;
+ input?: SlotComponentProps<
+ typeof TextField,
+ {},
+ MultiInputDateTimeRangeFieldOwnerState & { position: 'start' | 'end' }
+ >;
+ separator?: SlotComponentProps<
+ typeof Typography,
+ {},
+ MultiInputDateTimeRangeFieldOwnerState
+ >;
+}
+
+export type UseMultiInputDateTimeRangeFieldDefaultizedProps =
+ UseDateTimeRangeFieldDefaultizedProps;
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/index.ts b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/index.ts
new file mode 100644
index 0000000000000..741f9d78a60b0
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/index.ts
@@ -0,0 +1,2 @@
+export { MultiInputDateTimeRangeField as Unstable_MultiInputDateTimeRangeField } from './MultiInputDateTimeRangeField';
+export type { UseMultiInputDateTimeRangeFieldProps } from './MultiInputDateTimeRangeField.types';
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx
new file mode 100644
index 0000000000000..8a0a21d2ae7c3
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import { describeConformance } from '@mui/monorepo/test/utils';
+import { Unstable_MultiInputDateTimeRangeField as MultiInputDateTimeRangeField } from '@mui/x-date-pickers-pro/MultiInputDateTimeRangeField';
+import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers-utils';
+
+describe('', () => {
+ const { render } = createPickerRenderer();
+
+ describeConformance(, () => ({
+ classes: {},
+ inheritComponent: 'div',
+ render,
+ muiName: 'MuiMultiInputDateTimeRangeField',
+ wrapMount: wrapPickerMount,
+ refInstanceof: window.HTMLDivElement,
+ // cannot test reactTestRenderer because of required context
+ skip: ['reactTestRenderer', 'themeVariants'],
+ }));
+});
diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx
new file mode 100644
index 0000000000000..51507041bd83d
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx
@@ -0,0 +1,138 @@
+import * as React from 'react';
+import Stack, { StackProps } from '@mui/material/Stack';
+import TextField, { TextFieldProps } from '@mui/material/TextField';
+import Typography, { TypographyProps } from '@mui/material/Typography';
+import { styled, useThemeProps } from '@mui/material/styles';
+import { useSlotProps } from '@mui/base/utils';
+import { MultiInputTimeRangeFieldProps } from './MultiInputTimeRangeField.types';
+import { useMultiInputTimeRangeField } from '../internal/hooks/useMultiInputRangeField/useMultiInputTimeRangeField';
+
+const MultiInputTimeRangeFieldRoot = styled(
+ React.forwardRef((props: StackProps, ref: React.Ref) => (
+
+ )),
+ {
+ name: 'MuiMultiInputTimeRangeField',
+ slot: 'Root',
+ overridesResolver: (props, styles) => styles.root,
+ },
+)({});
+
+const MultiInputTimeRangeFieldSeparator = styled(
+ (props: TypographyProps) => {props.children ?? ' — '},
+ {
+ name: 'MuiMultiInputTimeRangeField',
+ slot: 'Separator',
+ overridesResolver: (props, styles) => styles.separator,
+ },
+)({});
+
+type MultiInputTimeRangeFieldComponent = ((
+ props: MultiInputTimeRangeFieldProps & React.RefAttributes,
+) => JSX.Element) & { propTypes?: any };
+
+export const MultiInputTimeRangeField = React.forwardRef(function MultiInputTimeRangeField(
+ inProps: MultiInputTimeRangeFieldProps,
+ ref: React.Ref,
+) {
+ const themeProps = useThemeProps({
+ props: inProps,
+ name: 'MuiMultiInputTimeRangeField',
+ });
+
+ const {
+ components,
+ componentsProps,
+ value,
+ defaultValue,
+ format,
+ onChange,
+ readOnly,
+ onError,
+ minTime,
+ maxTime,
+ minutesStep,
+ shouldDisableTime,
+ disableFuture,
+ disablePast,
+ ...other
+ } = themeProps;
+
+ const ownerState = themeProps;
+
+ const Root = components?.Root ?? MultiInputTimeRangeFieldRoot;
+ const rootProps = useSlotProps({
+ elementType: Root,
+ externalSlotProps: componentsProps?.root,
+ externalForwardedProps: other,
+ additionalProps: {
+ ref,
+ },
+ ownerState,
+ });
+
+ const Input = components?.Input ?? TextField;
+ const startInputProps: TextFieldProps = useSlotProps({
+ elementType: Input,
+ externalSlotProps: componentsProps?.input,
+ ownerState: { ...ownerState, position: 'start' },
+ });
+ const endInputProps: TextFieldProps = useSlotProps({
+ elementType: Input,
+ externalSlotProps: componentsProps?.input,
+ ownerState: { ...ownerState, position: 'end' },
+ });
+
+ const Separator = components?.Separator ?? MultiInputTimeRangeFieldSeparator;
+ const separatorProps = useSlotProps({
+ elementType: Separator,
+ externalSlotProps: componentsProps?.separator,
+ ownerState,
+ });
+
+ const {
+ startDate: { onKeyDown: onStartInputKeyDown, ref: startInputRef, ...startDateProps },
+ endDate: { onKeyDown: onEndInputKeyDown, ref: endInputRef, ...endDateProps },
+ } = useMultiInputTimeRangeField({
+ sharedProps: {
+ value,
+ defaultValue,
+ format,
+ onChange,
+ readOnly,
+ onError,
+ minTime,
+ maxTime,
+ minutesStep,
+ shouldDisableTime,
+ disableFuture,
+ disablePast,
+ },
+ startInputProps,
+ endInputProps,
+ startInputRef: startInputProps.inputRef,
+ endInputRef: endInputProps.inputRef,
+ });
+
+ return (
+
+
+
+
+
+ );
+}) as MultiInputTimeRangeFieldComponent;
diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.types.ts
new file mode 100644
index 0000000000000..936705d721956
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.types.ts
@@ -0,0 +1,73 @@
+import * as React from 'react';
+import { SlotComponentProps } from '@mui/base/utils';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import TextField from '@mui/material/TextField';
+import {
+ UseTimeRangeFieldDefaultizedProps,
+ UseTimeRangeFieldProps,
+} from '../internal/models/timeRange';
+
+export interface UseMultiInputTimeRangeFieldParams {
+ sharedProps: Omit> &
+ UseMultiInputTimeRangeFieldProps;
+ startInputProps: TChildProps;
+ endInputProps: TChildProps;
+ startInputRef?: React.Ref;
+ endInputRef?: React.Ref;
+}
+
+export interface UseMultiInputTimeRangeFieldProps extends UseTimeRangeFieldProps {}
+
+export type UseMultiInputTimeRangeFieldComponentProps = Omit<
+ TChildProps,
+ keyof UseMultiInputTimeRangeFieldProps
+> &
+ UseMultiInputTimeRangeFieldProps;
+
+export interface MultiInputTimeRangeFieldProps
+ extends UseMultiInputTimeRangeFieldComponentProps {
+ /**
+ * Overrideable components.
+ * @default {}
+ */
+ components?: MultiInputTimeRangeFieldSlotsComponent;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ componentsProps?: MultiInputTimeRangeFieldSlotsComponentsProps;
+}
+
+export type MultiInputTimeRangeFieldOwnerState = MultiInputTimeRangeFieldProps;
+
+export interface MultiInputTimeRangeFieldSlotsComponent {
+ /**
+ * Element rendered at the root.
+ * @default MultiInputTimeRangeFieldRoot
+ */
+ Root?: React.ElementType;
+ /**
+ * Input rendered for the start or end date.
+ * @default TextField
+ */
+ Input?: React.ElementType;
+ /**
+ * Element rendered between the two inputs.
+ * @default MultiInputTimeRangeFieldSeparator
+ */
+ Separator?: React.ElementType;
+}
+
+export interface MultiInputTimeRangeFieldSlotsComponentsProps {
+ root?: SlotComponentProps>;
+ input?: SlotComponentProps<
+ typeof TextField,
+ {},
+ MultiInputTimeRangeFieldOwnerState & { position: 'start' | 'end' }
+ >;
+ separator?: SlotComponentProps>;
+}
+
+export type UseMultiInputTimeRangeFieldDefaultizedProps =
+ UseTimeRangeFieldDefaultizedProps;
diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/index.ts b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/index.ts
new file mode 100644
index 0000000000000..4de66891b6eb8
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/index.ts
@@ -0,0 +1,2 @@
+export { MultiInputTimeRangeField as Unstable_MultiInputTimeRangeField } from './MultiInputTimeRangeField';
+export type { UseMultiInputTimeRangeFieldProps } from './MultiInputTimeRangeField.types';
diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx
new file mode 100644
index 0000000000000..f6ddfe2e14f79
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import { describeConformance } from '@mui/monorepo/test/utils';
+import { Unstable_MultiInputTimeRangeField as MultiInputTimeRangeField } from '@mui/x-date-pickers-pro/MultiInputTimeRangeField';
+import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers-utils';
+
+describe('', () => {
+ const { render } = createPickerRenderer();
+
+ describeConformance(, () => ({
+ classes: {},
+ inheritComponent: 'div',
+ render,
+ muiName: 'MuiMultiInputTimeRangeField',
+ wrapMount: wrapPickerMount,
+ refInstanceof: window.HTMLDivElement,
+ // cannot test reactTestRenderer because of required context
+ skip: ['reactTestRenderer', 'themeVariants'],
+ }));
+});
diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.interfaces.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.interfaces.ts
index 7c6054409bf10..aa9ca94ac22e3 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.interfaces.ts
+++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.interfaces.ts
@@ -1,18 +1,7 @@
import { TextFieldProps } from '@mui/material/TextField';
-import { BaseDateValidationProps, DefaultizedProps } from '@mui/x-date-pickers/internals';
-import { UseFieldInternalProps, FieldSection } from '@mui/x-date-pickers/internals-fields';
-import { DateRange, DayRangeValidationProps } from '../internal/models';
-import { DateRangeValidationError } from '../internal/hooks/validation/useDateRangeValidation';
+import { UseDateRangeFieldProps } from '../internal/models';
-export interface UseSingleInputDateRangeFieldProps
- extends UseFieldInternalProps, DateRangeValidationError>,
- DayRangeValidationProps,
- BaseDateValidationProps {}
-
-export type UseSingleInputDateRangeFieldDefaultizedProps = DefaultizedProps<
- UseSingleInputDateRangeFieldProps,
- 'minDate' | 'maxDate' | 'disableFuture' | 'disablePast'
->;
+export interface UseSingleInputDateRangeFieldProps extends UseDateRangeFieldProps {}
export type UseSingleInputDateRangeFieldComponentProps = Omit<
ChildProps,
@@ -24,7 +13,3 @@ export type SingleInputDateRangeFieldProps = UseSingleInputDateRangeField
TDate,
TextFieldProps
>;
-
-export interface DateRangeFieldSection extends FieldSection {
- dateName: 'start' | 'end';
-}
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 843df54680bde..68396ae0db9c2 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts
+++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts
@@ -1,29 +1,17 @@
import * as React from 'react';
import { SlotComponentProps } from '@mui/base/utils';
import TextField, { TextFieldProps } from '@mui/material/TextField';
-import {
- BaseDateValidationProps,
- DefaultizedProps,
- MakeOptional,
-} from '@mui/x-date-pickers/internals';
-import { UseFieldInternalProps, FieldSection } from '@mui/x-date-pickers/internals-fields';
-import { DateRange, DayRangeValidationProps } from '../internal/models';
-import { DateRangeValidationError } from '../internal/hooks/validation/useDateRangeValidation';
+import { UseDateRangeFieldDefaultizedProps, UseDateRangeFieldProps } from '../internal/models';
export interface UseSingleInputDateRangeFieldParams {
props: UseSingleInputDateRangeFieldComponentProps;
inputRef?: React.Ref;
}
-export interface UseSingleInputDateRangeFieldProps
- extends MakeOptional, DateRangeValidationError>, 'format'>,
- DayRangeValidationProps,
- BaseDateValidationProps {}
+export interface UseSingleInputDateRangeFieldProps extends UseDateRangeFieldProps {}
-export type UseSingleInputDateRangeFieldDefaultizedProps = DefaultizedProps<
- UseSingleInputDateRangeFieldProps,
- keyof BaseDateValidationProps | 'format'
->;
+export type UseSingleInputDateRangeFieldDefaultizedProps =
+ UseDateRangeFieldDefaultizedProps;
export type UseSingleInputDateRangeFieldComponentProps = Omit<
TChildProps,
@@ -58,7 +46,3 @@ export interface SingleInputDateRangeFieldSlotsComponent {
export interface SingleInputDateRangeFieldSlotsComponentsProps {
input?: SlotComponentProps>;
}
-
-export interface DateRangeFieldSection extends FieldSection {
- dateName: 'start' | 'end';
-}
diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.utils.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.utils.ts
index 176a3b18a0ae4..016638f6eb1cb 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.utils.ts
+++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.utils.ts
@@ -1,4 +1,4 @@
-import { DateRangeFieldSection } from './SingleInputDateRangeField.types';
+import { DateRangeFieldSection } from '../internal/models';
export const splitDateRangeSections = (sections: DateRangeFieldSection[]) => {
const startDateSections: DateRangeFieldSection[] = [];
diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts
index d5d7f0050508e..081d71cbc80ff 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts
+++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts
@@ -1,139 +1,13 @@
import { useUtils, useDefaultDates, applyDefaultDate } from '@mui/x-date-pickers/internals';
+import { useField } from '@mui/x-date-pickers/internals-fields';
import {
- useField,
- FieldValueManager,
- splitFormatIntoSections,
- addPositionPropertiesToSections,
- createDateStrFromSections,
-} from '@mui/x-date-pickers/internals-fields';
-import {
- DateRangeFieldSection,
UseSingleInputDateRangeFieldDefaultizedProps,
UseSingleInputDateRangeFieldParams,
UseSingleInputDateRangeFieldProps,
} from './SingleInputDateRangeField.types';
import { dateRangePickerValueManager } from '../DateRangePicker/shared';
-import { DateRange } from '../internal/models';
-import { splitDateRangeSections, removeLastSeparator } from './SingleInputDateRangeField.utils';
-import {
- DateRangeValidationError,
- isSameDateRangeError,
- validateDateRange,
-} from '../internal/hooks/validation/useDateRangeValidation';
-
-export const dateRangeFieldValueManager: FieldValueManager<
- DateRange,
- any,
- DateRangeFieldSection,
- DateRangeValidationError
-> = {
- updateReferenceValue: (utils, value, prevReferenceValue) => {
- const shouldKeepStartDate = value[0] != null && utils.isValid(value[0]);
- const shouldKeepEndDate = value[1] != null && utils.isValid(value[1]);
-
- if (!shouldKeepStartDate && !shouldKeepEndDate) {
- return prevReferenceValue;
- }
-
- if (shouldKeepStartDate && shouldKeepEndDate) {
- return value;
- }
-
- if (shouldKeepStartDate) {
- return [value[0], prevReferenceValue[0]];
- }
-
- return [prevReferenceValue[1], value[1]];
- },
- getSectionsFromValue: (utils, localeText, prevSections, [start, end], format) => {
- const prevDateRangeSections =
- prevSections == null
- ? { startDate: null, endDate: null }
- : splitDateRangeSections(prevSections);
-
- const getSections = (newDate: any | null, prevDateSections: DateRangeFieldSection[] | null) => {
- const shouldReUsePrevDateSections = !utils.isValid(newDate) && !!prevDateSections;
-
- if (shouldReUsePrevDateSections) {
- return prevDateSections;
- }
-
- return splitFormatIntoSections(utils, localeText, format, newDate);
- };
-
- const rawSectionsOfStartDate = getSections(start, prevDateRangeSections.startDate);
- const rawSectionsOfEndDate = getSections(end, prevDateRangeSections.endDate);
-
- const sectionsOfStartDate = rawSectionsOfStartDate.map((section, sectionIndex) => {
- if (sectionIndex === rawSectionsOfStartDate.length - 1) {
- return {
- ...section,
- dateName: 'start' as const,
- separator: ' – ',
- };
- }
-
- return {
- ...section,
- dateName: 'start' as const,
- };
- });
-
- const sectionsOfEndDate = rawSectionsOfEndDate.map((section) => ({
- ...section,
- dateName: 'end' as const,
- }));
-
- return addPositionPropertiesToSections([...sectionsOfStartDate, ...sectionsOfEndDate]);
- },
- getValueStrFromSections: (sections) => {
- const dateRangeSections = splitDateRangeSections(sections);
- const startDateStr = createDateStrFromSections(dateRangeSections.startDate, true);
- const endDateStr = createDateStrFromSections(dateRangeSections.endDate, true);
-
- return `${startDateStr}${endDateStr}`;
- },
- getActiveDateSections: (sections, activeSection) => {
- const index = activeSection.dateName === 'start' ? 0 : 1;
- const dateRangeSections = splitDateRangeSections(sections);
-
- return index === 0
- ? removeLastSeparator(dateRangeSections.startDate)
- : dateRangeSections.endDate;
- },
- getActiveDateManager: (utils, state, activeSection) => {
- const index = activeSection.dateName === 'start' ? 0 : 1;
-
- const updateDateInRange = (newDate: any, prevDateRange: DateRange) =>
- (index === 0 ? [newDate, prevDateRange[1]] : [prevDateRange[0], newDate]) as DateRange;
-
- return {
- activeDate: state.value[index],
- referenceActiveDate: state.referenceValue[index],
- getNewValueFromNewActiveDate: (newActiveDate) => ({
- value: updateDateInRange(newActiveDate, state.value),
- referenceValue:
- newActiveDate == null || !utils.isValid(newActiveDate)
- ? state.referenceValue
- : updateDateInRange(newActiveDate, state.referenceValue),
- }),
- };
- },
- parseValueStr: (valueStr, referenceValue, parseDate) => {
- // TODO: Improve because it would not work if the date format has `–` as a separator.
- const [startStr, endStr] = valueStr.split('–');
-
- return [startStr, endStr].map((dateStr, index) => {
- if (dateStr == null) {
- return null;
- }
-
- return parseDate(dateStr.trim(), referenceValue[index]);
- }) as DateRange;
- },
- hasError: (error) => error[0] != null || error[1] != null,
- isSameError: isSameDateRangeError,
-};
+import { validateDateRange } from '../internal/hooks/validation/useDateRangeValidation';
+import { dateRangeFieldValueManager } from '../internal/hooks/valueManager/dateRangeValueManager';
export const useDefaultizedDateRangeFieldProps = (
props: UseSingleInputDateRangeFieldProps,
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/useMultiInputDateRangeField.ts b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts
similarity index 77%
rename from packages/x-date-pickers-pro/src/MultiInputDateRangeField/useMultiInputDateRangeField.ts
rename to packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts
index 531d86b4315b3..67c2731995932 100644
--- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/useMultiInputDateRangeField.ts
+++ b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts
@@ -5,14 +5,16 @@ import {
UseDateFieldComponentProps,
} from '@mui/x-date-pickers/DateField';
import { useValidation } from '@mui/x-date-pickers/internals';
-import { DateRange } from '../internal/models';
-import { validateDateRange } from '../internal/hooks/validation/useDateRangeValidation';
-import { dateRangePickerValueManager } from '../DateRangePicker/shared';
+import { useDefaultizedDateRangeFieldProps } from '../../../SingleInputDateRangeField/useSingleInputDateRangeField';
+import { UseMultiInputDateRangeFieldParams } from '../../../MultiInputDateRangeField/MultiInputDateRangeField.types';
+import { DateRange } from '../../models/range';
import {
- dateRangeFieldValueManager,
- useDefaultizedDateRangeFieldProps,
-} from '../SingleInputDateRangeField/useSingleInputDateRangeField';
-import { UseMultiInputDateRangeFieldParams } from './MultiInputDateRangeField.types';
+ DateRangeComponentValidationProps,
+ DateRangeValidationError,
+ validateDateRange,
+} from '../validation/useDateRangeValidation';
+import { dateRangePickerValueManager } from '../../../DateRangePicker/shared';
+import { dateRangeFieldValueManager } from '../valueManager/dateRangeValueManager';
export const useMultiInputDateRangeField = ({
sharedProps: inSharedProps,
@@ -73,7 +75,12 @@ export const useMultiInputDateRangeField = ({
const value = valueProp ?? firstDefaultValue.current ?? dateRangePickerValueManager.emptyValue;
- const validationError = useValidation({ ...sharedProps, value }, validateDateRange, () => true);
+ const validationError = useValidation<
+ DateRange,
+ TDate,
+ DateRangeValidationError,
+ DateRangeComponentValidationProps
+ >({ ...sharedProps, value }, validateDateRange, () => true);
const inputError = React.useMemo(
() => dateRangeFieldValueManager.hasError(validationError),
[validationError],
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts
new file mode 100644
index 0000000000000..ab5c640c26498
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts
@@ -0,0 +1,132 @@
+import * as React from 'react';
+import useEventCallback from '@mui/utils/useEventCallback';
+import {
+ unstable_useDateTimeField as useDateTimeField,
+ UseDateTimeFieldComponentProps,
+} from '@mui/x-date-pickers/DateTimeField';
+import {
+ applyDefaultDate,
+ useDefaultDates,
+ useUtils,
+ useValidation,
+} from '@mui/x-date-pickers/internals';
+import { DateRange } from '../../models/range';
+import {
+ UseMultiInputDateTimeRangeFieldDefaultizedProps,
+ UseMultiInputDateTimeRangeFieldParams,
+ UseMultiInputDateTimeRangeFieldProps,
+} from '../../../MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types';
+import {
+ DateTimeRangeComponentValidationProps,
+ DateTimeRangeValidationError,
+ validateDateTimeRange,
+} from '../validation/useDateTimeRangeValidation';
+import { dateRangePickerValueManager } from '../../../DateRangePicker/shared';
+import { dateTimeRangeFieldValueManager } from '../valueManager/dateTimeRangeValueManager';
+
+export const useDefaultizedDateTimeRangeFieldProps = (
+ props: UseMultiInputDateTimeRangeFieldProps,
+): UseMultiInputDateTimeRangeFieldDefaultizedProps & AdditionalProps => {
+ const utils = useUtils();
+ const defaultDates = useDefaultDates();
+
+ const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale();
+ const defaultFormat = ampm
+ ? utils.formats.keyboardDateTime12h
+ : utils.formats.keyboardDateTime24h;
+
+ return {
+ ...props,
+ disablePast: props.disablePast ?? false,
+ disableFuture: props.disableFuture ?? false,
+ format: props.format ?? defaultFormat,
+ minDate: applyDefaultDate(utils, props.minDateTime ?? props.minDate, defaultDates.minDate),
+ maxDate: applyDefaultDate(utils, props.maxDateTime ?? props.maxDate, defaultDates.maxDate),
+ minTime: props.minDateTime ?? props.minTime,
+ maxTime: props.maxDateTime ?? props.maxTime,
+ disableIgnoringDatePartForTimeValidation: Boolean(props.minDateTime || props.maxDateTime),
+ } as any;
+};
+
+export const useMultiInputDateTimeRangeField = ({
+ sharedProps: inSharedProps,
+ startInputProps: inStartInputProps,
+ endInputProps: inEndInputProps,
+ startInputRef,
+ endInputRef,
+}: UseMultiInputDateTimeRangeFieldParams) => {
+ const sharedProps = useDefaultizedDateTimeRangeFieldProps(inSharedProps);
+
+ const { value: valueProp, defaultValue, format, onChange } = sharedProps;
+
+ const firstDefaultValue = React.useRef(defaultValue);
+
+ // TODO: Maybe export utility from `useField` instead of copy/pasting the logic
+ const buildChangeHandler = (index: 0 | 1) => {
+ if (!onChange) {
+ return () => {};
+ }
+
+ return (newDate: TDate | null) => {
+ const currentDateRange =
+ valueProp ?? firstDefaultValue.current ?? dateRangePickerValueManager.emptyValue;
+ const newDateRange: DateRange =
+ index === 0 ? [newDate, currentDateRange[1]] : [currentDateRange[0], newDate];
+
+ onChange(newDateRange);
+ };
+ };
+
+ const handleStartDateChange = useEventCallback(buildChangeHandler(0));
+ const handleEndDateChange = useEventCallback(buildChangeHandler(1));
+
+ const startInputProps: UseDateTimeFieldComponentProps = {
+ ...inStartInputProps,
+ format,
+ value: valueProp === undefined ? undefined : valueProp[0],
+ defaultValue: defaultValue === undefined ? undefined : defaultValue[0],
+ onChange: handleStartDateChange,
+ };
+
+ const endInputProps: UseDateTimeFieldComponentProps = {
+ ...inEndInputProps,
+ format,
+ value: valueProp === undefined ? undefined : valueProp[1],
+ defaultValue: defaultValue === undefined ? undefined : defaultValue[1],
+ onChange: handleEndDateChange,
+ };
+
+ const rawStartDateResponse = useDateTimeField({
+ props: startInputProps,
+ inputRef: startInputRef,
+ });
+ const rawEndDateResponse = useDateTimeField({
+ props: endInputProps,
+ inputRef: endInputRef,
+ });
+
+ const value = valueProp ?? firstDefaultValue.current ?? dateRangePickerValueManager.emptyValue;
+
+ const validationError = useValidation<
+ DateRange,
+ TDate,
+ DateTimeRangeValidationError,
+ DateTimeRangeComponentValidationProps
+ >({ ...sharedProps, value }, validateDateTimeRange, () => true);
+ const inputError = React.useMemo(
+ () => dateTimeRangeFieldValueManager.hasError(validationError),
+ [validationError],
+ );
+
+ const startDateResponse = {
+ ...rawStartDateResponse,
+ error: inputError,
+ };
+
+ const endDateResponse = {
+ ...rawEndDateResponse,
+ error: inputError,
+ };
+
+ return { startDate: startDateResponse, endDate: endDateResponse };
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts
new file mode 100644
index 0000000000000..70de596fe9d06
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts
@@ -0,0 +1,121 @@
+import * as React from 'react';
+import useEventCallback from '@mui/utils/useEventCallback';
+import {
+ unstable_useTimeField as useTimeField,
+ UseTimeFieldComponentProps,
+} from '@mui/x-date-pickers/TimeField';
+import { useUtils, useValidation } from '@mui/x-date-pickers/internals';
+import { DateRange } from '../../models/range';
+import {
+ TimeRangeValidationError,
+ TimeRangeComponentValidationProps,
+ validateTimeRange,
+} from '../validation/useTimeRangeValidation';
+import {
+ UseMultiInputTimeRangeFieldDefaultizedProps,
+ UseMultiInputTimeRangeFieldParams,
+ UseMultiInputTimeRangeFieldProps,
+} from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types';
+import { dateRangePickerValueManager } from '../../../DateRangePicker/shared';
+import { timeRangeFieldValueManager } from '../valueManager/timeRangeValueManager';
+
+export const useDefaultizedTimeRangeFieldProps = (
+ props: UseMultiInputTimeRangeFieldProps,
+): UseMultiInputTimeRangeFieldDefaultizedProps & AdditionalProps => {
+ const utils = useUtils();
+
+ const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale();
+ const defaultFormat = ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h;
+
+ return {
+ ...props,
+ disablePast: props.disablePast ?? false,
+ disableFuture: props.disableFuture ?? false,
+ format: props.format ?? defaultFormat,
+ minTime: props.minTime,
+ maxTime: props.maxTime,
+ } as any;
+};
+
+export const useMultiInputTimeRangeField = ({
+ sharedProps: inSharedProps,
+ startInputProps: inStartInputProps,
+ endInputProps: inEndInputProps,
+ startInputRef,
+ endInputRef,
+}: UseMultiInputTimeRangeFieldParams) => {
+ const sharedProps = useDefaultizedTimeRangeFieldProps(inSharedProps);
+
+ const { value: valueProp, defaultValue, format, onChange } = sharedProps;
+
+ const firstDefaultValue = React.useRef(defaultValue);
+
+ // TODO: Maybe export utility from `useField` instead of copy/pasting the logic
+ const buildChangeHandler = (index: 0 | 1) => {
+ if (!onChange) {
+ return () => {};
+ }
+
+ return (newDate: TDate | null) => {
+ const currentDateRange =
+ valueProp ?? firstDefaultValue.current ?? dateRangePickerValueManager.emptyValue;
+ const newDateRange: DateRange =
+ index === 0 ? [newDate, currentDateRange[1]] : [currentDateRange[0], newDate];
+
+ onChange(newDateRange);
+ };
+ };
+
+ const handleStartDateChange = useEventCallback(buildChangeHandler(0));
+ const handleEndDateChange = useEventCallback(buildChangeHandler(1));
+
+ const startInputProps: UseTimeFieldComponentProps = {
+ ...inStartInputProps,
+ format,
+ value: valueProp === undefined ? undefined : valueProp[0],
+ defaultValue: defaultValue === undefined ? undefined : defaultValue[0],
+ onChange: handleStartDateChange,
+ };
+
+ const endInputProps: UseTimeFieldComponentProps = {
+ ...inEndInputProps,
+ format,
+ value: valueProp === undefined ? undefined : valueProp[1],
+ defaultValue: defaultValue === undefined ? undefined : defaultValue[1],
+ onChange: handleEndDateChange,
+ };
+
+ const rawStartDateResponse = useTimeField({
+ props: startInputProps,
+ inputRef: startInputRef,
+ });
+ const rawEndDateResponse = useTimeField({
+ props: endInputProps,
+ inputRef: endInputRef,
+ });
+
+ const value = valueProp ?? firstDefaultValue.current ?? dateRangePickerValueManager.emptyValue;
+
+ const validationError = useValidation<
+ DateRange,
+ TDate,
+ TimeRangeValidationError,
+ TimeRangeComponentValidationProps
+ >({ ...sharedProps, value }, validateTimeRange, () => true);
+ const inputError = React.useMemo(
+ () => timeRangeFieldValueManager.hasError(validationError),
+ [validationError],
+ );
+
+ const startDateResponse = {
+ ...rawStartDateResponse,
+ error: inputError,
+ };
+
+ const endDateResponse = {
+ ...rawEndDateResponse,
+ error: inputError,
+ };
+
+ return { startDate: startDateResponse, endDate: endDateResponse };
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateRangeValidation.ts b/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateRangeValidation.ts
index f6e8033901004..cb7ce55c07c14 100644
--- a/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateRangeValidation.ts
+++ b/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateRangeValidation.ts
@@ -7,7 +7,7 @@ import {
ValidationProps,
} from '@mui/x-date-pickers/internals';
import { isRangeValid } from '../../utils/date-utils';
-import { DateRange, DayRangeValidationProps } from '../../models/dateRange';
+import { DateRange, DayRangeValidationProps } from '../../models';
export interface DateRangeComponentValidationProps
extends DayRangeValidationProps,
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateTimeRangeValidation.ts b/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateTimeRangeValidation.ts
new file mode 100644
index 0000000000000..1d342f9f0a9f9
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/validation/useDateTimeRangeValidation.ts
@@ -0,0 +1,87 @@
+import {
+ useValidation,
+ Validator,
+ DateTimeValidationError,
+ validateDateTime,
+ BaseDateValidationProps,
+ TimeValidationProps,
+ ValidationProps,
+} from '@mui/x-date-pickers/internals';
+import { isRangeValid } from '../../utils/date-utils';
+import { DayRangeValidationProps } from '../../models/dateRange';
+import { DateRange } from '../../models/range';
+
+export interface DateTimeRangeComponentValidationProps
+ extends DayRangeValidationProps,
+ TimeValidationProps,
+ Required> {}
+
+export const validateDateTimeRange: Validator<
+ DateRange,
+ any,
+ DateTimeRangeValidationError,
+ DateTimeRangeComponentValidationProps
+> = ({ props, value, adapter }) => {
+ const [start, end] = value;
+
+ // for partial input
+ if (start === null || end === null) {
+ return [null, null];
+ }
+
+ const { shouldDisableDate, ...otherProps } = props;
+
+ const dateTimeValidations: [
+ DateTimeRangeValidationErrorValue,
+ DateTimeRangeValidationErrorValue,
+ ] = [
+ validateDateTime({
+ adapter,
+ value: start,
+ props: {
+ ...otherProps,
+ shouldDisableDate: (day) => !!shouldDisableDate?.(day, 'start'),
+ },
+ }),
+ validateDateTime({
+ adapter,
+ value: end,
+ props: {
+ ...otherProps,
+ shouldDisableDate: (day) => !!shouldDisableDate?.(day, 'end'),
+ },
+ }),
+ ];
+
+ if (dateTimeValidations[0] || dateTimeValidations[1]) {
+ return dateTimeValidations;
+ }
+
+ if (!isRangeValid(adapter.utils, value)) {
+ return ['invalidRange', 'invalidRange'];
+ }
+
+ return [null, null];
+};
+
+type DateTimeRangeValidationErrorValue = DateTimeValidationError | 'invalidRange' | null;
+
+export type DateTimeRangeValidationError = [
+ DateTimeRangeValidationErrorValue,
+ DateTimeRangeValidationErrorValue,
+];
+
+export const isSameDateTimeRangeError = (
+ a: DateTimeRangeValidationError,
+ b: DateTimeRangeValidationError | null,
+) => b !== null && a[1] === b[1] && a[0] === b[0];
+
+export const useDateRangeValidation = (
+ props: ValidationProps<
+ DateTimeRangeValidationError,
+ DateRange,
+ DateTimeRangeComponentValidationProps
+ >,
+): DateTimeRangeValidationError => {
+ return useValidation(props, validateDateTimeRange, isSameDateTimeRangeError);
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/validation/useTimeRangeValidation.ts b/packages/x-date-pickers-pro/src/internal/hooks/validation/useTimeRangeValidation.ts
new file mode 100644
index 0000000000000..4a4e2aa9e2306
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/validation/useTimeRangeValidation.ts
@@ -0,0 +1,71 @@
+import {
+ useValidation,
+ Validator,
+ TimeValidationError,
+ validateTime,
+ BaseTimeValidationProps,
+ ValidationProps,
+} from '@mui/x-date-pickers/internals';
+import { isRangeValid } from '../../utils/date-utils';
+import { DateRange } from '../../models/range';
+
+export interface TimeRangeComponentValidationProps extends Required {}
+
+export const validateTimeRange: Validator<
+ DateRange,
+ any,
+ TimeRangeValidationError,
+ TimeRangeComponentValidationProps
+> = ({ props, value, adapter }) => {
+ const [start, end] = value;
+
+ // for partial input
+ if (start === null || end === null) {
+ return [null, null];
+ }
+
+ const dateTimeValidations: [TimeRangeValidationErrorValue, TimeRangeValidationErrorValue] = [
+ validateTime({
+ adapter,
+ value: start,
+ props,
+ }),
+ validateTime({
+ adapter,
+ value: end,
+ props,
+ }),
+ ];
+
+ if (dateTimeValidations[0] || dateTimeValidations[1]) {
+ return dateTimeValidations;
+ }
+
+ if (!isRangeValid(adapter.utils, value)) {
+ return ['invalidRange', 'invalidRange'];
+ }
+
+ return [null, null];
+};
+
+type TimeRangeValidationErrorValue = TimeValidationError | 'invalidRange' | null;
+
+export type TimeRangeValidationError = [
+ TimeRangeValidationErrorValue,
+ TimeRangeValidationErrorValue,
+];
+
+export const isSameTimeRangeError = (
+ a: TimeRangeValidationError,
+ b: TimeRangeValidationError | null,
+) => b !== null && a[1] === b[1] && a[0] === b[0];
+
+export const useDateRangeValidation = (
+ props: ValidationProps<
+ TimeRangeValidationError,
+ DateRange,
+ TimeRangeComponentValidationProps
+ >,
+): TimeRangeValidationError => {
+ return useValidation(props, validateTimeRange, isSameTimeRangeError);
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts
new file mode 100644
index 0000000000000..2f1297bed6d4b
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts
@@ -0,0 +1,119 @@
+import {
+ FieldValueManager,
+ splitFormatIntoSections,
+ addPositionPropertiesToSections,
+ createDateStrFromSections,
+} from '@mui/x-date-pickers/internals-fields';
+import { DateRange, DateRangeFieldSection } from '../../models/range';
+import { splitDateRangeSections, removeLastSeparator } from '../../utils/date-fields-utils';
+
+export const rangeFieldValueManager: Omit<
+ FieldValueManager, any, DateRangeFieldSection, any>,
+ 'isSameError'
+> = {
+ updateReferenceValue: (utils, value, prevReferenceValue) => {
+ const shouldKeepStartDate = value[0] != null && utils.isValid(value[0]);
+ const shouldKeepEndDate = value[1] != null && utils.isValid(value[1]);
+
+ if (!shouldKeepStartDate && !shouldKeepEndDate) {
+ return prevReferenceValue;
+ }
+
+ if (shouldKeepStartDate && shouldKeepEndDate) {
+ return value;
+ }
+
+ if (shouldKeepStartDate) {
+ return [value[0], prevReferenceValue[0]];
+ }
+
+ return [prevReferenceValue[1], value[1]];
+ },
+ getSectionsFromValue: (utils, localeText, prevSections, [start, end], format) => {
+ const prevDateRangeSections =
+ prevSections == null
+ ? { startDate: null, endDate: null }
+ : splitDateRangeSections(prevSections);
+
+ const getSections = (newDate: any | null, prevDateSections: DateRangeFieldSection[] | null) => {
+ const shouldReUsePrevDateSections = !utils.isValid(newDate) && !!prevDateSections;
+
+ if (shouldReUsePrevDateSections) {
+ return prevDateSections;
+ }
+
+ return splitFormatIntoSections(utils, localeText, format, newDate);
+ };
+
+ const rawSectionsOfStartDate = getSections(start, prevDateRangeSections.startDate);
+ const rawSectionsOfEndDate = getSections(end, prevDateRangeSections.endDate);
+
+ const sectionsOfStartDate = rawSectionsOfStartDate.map((section, sectionIndex) => {
+ if (sectionIndex === rawSectionsOfStartDate.length - 1) {
+ return {
+ ...section,
+ dateName: 'start' as const,
+ separator: ' – ',
+ };
+ }
+
+ return {
+ ...section,
+ dateName: 'start' as const,
+ };
+ });
+
+ const sectionsOfEndDate = rawSectionsOfEndDate.map((section) => ({
+ ...section,
+ dateName: 'end' as const,
+ }));
+
+ return addPositionPropertiesToSections([...sectionsOfStartDate, ...sectionsOfEndDate]);
+ },
+ getValueStrFromSections: (sections) => {
+ const dateRangeSections = splitDateRangeSections(sections);
+ const startDateStr = createDateStrFromSections(dateRangeSections.startDate, true);
+ const endDateStr = createDateStrFromSections(dateRangeSections.endDate, true);
+
+ return `${startDateStr}${endDateStr}`;
+ },
+ getActiveDateSections: (sections, activeSection) => {
+ const index = activeSection.dateName === 'start' ? 0 : 1;
+ const dateRangeSections = splitDateRangeSections(sections);
+
+ return index === 0
+ ? removeLastSeparator(dateRangeSections.startDate)
+ : dateRangeSections.endDate;
+ },
+ parseValueStr: (valueStr, referenceValue, parseDate) => {
+ // TODO: Improve because it would not work if the date format has `–` as a separator.
+ const [startStr, endStr] = valueStr.split('–');
+
+ return [startStr, endStr].map((dateStr, index) => {
+ if (dateStr == null) {
+ return null;
+ }
+
+ return parseDate(dateStr.trim(), referenceValue[index]);
+ }) as DateRange;
+ },
+ getActiveDateManager: (utils, state, activeSection) => {
+ const index = activeSection.dateName === 'start' ? 0 : 1;
+
+ const updateDateInRange = (newDate: any, prevDateRange: DateRange) =>
+ (index === 0 ? [newDate, prevDateRange[1]] : [prevDateRange[0], newDate]) as DateRange;
+
+ return {
+ activeDate: state.value[index],
+ referenceActiveDate: state.referenceValue[index],
+ getNewValueFromNewActiveDate: (newActiveDate) => ({
+ value: updateDateInRange(newActiveDate, state.value),
+ referenceValue:
+ newActiveDate == null || !utils.isValid(newActiveDate)
+ ? state.referenceValue
+ : updateDateInRange(newActiveDate, state.referenceValue),
+ }),
+ };
+ },
+ hasError: (error) => error[0] != null || error[1] != null,
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateRangeValueManager.ts b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateRangeValueManager.ts
new file mode 100644
index 0000000000000..d45181a450c70
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateRangeValueManager.ts
@@ -0,0 +1,17 @@
+import { FieldValueManager } from '@mui/x-date-pickers/internals-fields';
+import { DateRange, DateRangeFieldSection } from '../../models/range';
+import {
+ DateRangeValidationError,
+ isSameDateRangeError,
+} from '../validation/useDateRangeValidation';
+import { rangeFieldValueManager } from './common';
+
+export const dateRangeFieldValueManager: FieldValueManager<
+ DateRange,
+ any,
+ DateRangeFieldSection,
+ DateRangeValidationError
+> = {
+ ...rangeFieldValueManager,
+ isSameError: isSameDateRangeError,
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateTimeRangeValueManager.ts b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateTimeRangeValueManager.ts
new file mode 100644
index 0000000000000..19aed27d7c1ab
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/dateTimeRangeValueManager.ts
@@ -0,0 +1,17 @@
+import { FieldValueManager } from '@mui/x-date-pickers/internals-fields';
+import { DateRange, DateRangeFieldSection } from '../../models/range';
+import {
+ DateTimeRangeValidationError,
+ isSameDateTimeRangeError,
+} from '../validation/useDateTimeRangeValidation';
+import { rangeFieldValueManager } from './common';
+
+export const dateTimeRangeFieldValueManager: FieldValueManager<
+ DateRange,
+ any,
+ DateRangeFieldSection,
+ DateTimeRangeValidationError
+> = {
+ ...rangeFieldValueManager,
+ isSameError: isSameDateTimeRangeError,
+};
diff --git a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/timeRangeValueManager.ts b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/timeRangeValueManager.ts
new file mode 100644
index 0000000000000..76a1cd1827b9a
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/timeRangeValueManager.ts
@@ -0,0 +1,17 @@
+import { FieldValueManager } from '@mui/x-date-pickers/internals-fields';
+import { DateRange, DateRangeFieldSection } from '../../models/range';
+import {
+ TimeRangeValidationError,
+ isSameTimeRangeError,
+} from '../validation/useTimeRangeValidation';
+import { rangeFieldValueManager } from './common';
+
+export const timeRangeFieldValueManager: FieldValueManager<
+ DateRange,
+ any,
+ DateRangeFieldSection,
+ TimeRangeValidationError
+> = {
+ ...rangeFieldValueManager,
+ isSameError: isSameTimeRangeError,
+};
diff --git a/packages/x-date-pickers-pro/src/internal/models/dateRange.ts b/packages/x-date-pickers-pro/src/internal/models/dateRange.ts
index 1b5245c548746..dfe4406ee85bf 100644
--- a/packages/x-date-pickers-pro/src/internal/models/dateRange.ts
+++ b/packages/x-date-pickers-pro/src/internal/models/dateRange.ts
@@ -1,10 +1,11 @@
-export type DateRange = [TDate | null, TDate | null];
-export type NonEmptyDateRange = [TDate, TDate];
-
-export interface CurrentlySelectingRangeEndProps {
- currentlySelectingRangeEnd: 'start' | 'end';
- setCurrentlySelectingRangeEnd: (newSelectingEnd: 'start' | 'end') => void;
-}
+import {
+ BaseDateValidationProps,
+ DefaultizedProps,
+ MakeOptional,
+} from '@mui/x-date-pickers/internals';
+import { UseFieldInternalProps } from '@mui/x-date-pickers/internals-fields';
+import { DateRange } from './range';
+import type { DateRangeValidationError } from '../hooks/validation/useDateRangeValidation';
/**
* Props used to validate a day value in range pickers.
@@ -19,3 +20,13 @@ export interface DayRangeValidationProps {
*/
shouldDisableDate?: (day: TDate, position: 'start' | 'end') => boolean;
}
+
+export interface UseDateRangeFieldProps
+ extends MakeOptional, DateRangeValidationError>, 'format'>,
+ DayRangeValidationProps,
+ BaseDateValidationProps {}
+
+export type UseDateRangeFieldDefaultizedProps = DefaultizedProps<
+ UseDateRangeFieldProps,
+ keyof BaseDateValidationProps | 'format'
+>;
diff --git a/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts b/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts
new file mode 100644
index 0000000000000..b7b56f77ca056
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts
@@ -0,0 +1,38 @@
+import { UseFieldInternalProps } from '@mui/x-date-pickers/internals-fields';
+import {
+ BaseDateValidationProps,
+ TimeValidationProps,
+ DefaultizedProps,
+ MakeOptional,
+} from '@mui/x-date-pickers/internals';
+import { DayRangeValidationProps } from './dateRange';
+import { DateRange } from './range';
+import { DateTimeRangeValidationError } from '../hooks/validation/useDateTimeRangeValidation';
+
+export interface UseDateTimeRangeFieldProps
+ extends MakeOptional<
+ UseFieldInternalProps, DateTimeRangeValidationError>,
+ 'format'
+ >,
+ DayRangeValidationProps,
+ TimeValidationProps,
+ BaseDateValidationProps {
+ /**
+ * Minimal selectable moment of time with binding to date, to set min time in each day use `minTime`.
+ */
+ minDateTime?: TDate;
+ /**
+ * Maximal selectable moment of time with binding to date, to set max time in each day use `maxTime`.
+ */
+ maxDateTime?: TDate;
+ /**
+ * 12h/24h view for hour selection clock.
+ * @default `utils.is12HourCycleInCurrentLocale()`
+ */
+ ampm?: boolean;
+}
+
+export type UseDateTimeRangeFieldDefaultizedProps = DefaultizedProps<
+ UseDateTimeRangeFieldProps,
+ keyof BaseDateValidationProps | 'format' | 'disableIgnoringDatePartForTimeValidation'
+>;
diff --git a/packages/x-date-pickers-pro/src/internal/models/index.ts b/packages/x-date-pickers-pro/src/internal/models/index.ts
index 0f4b8e3531d7b..61657566f5b7b 100644
--- a/packages/x-date-pickers-pro/src/internal/models/index.ts
+++ b/packages/x-date-pickers-pro/src/internal/models/index.ts
@@ -1 +1,4 @@
export * from './dateRange';
+export * from './range';
+export * from './dateTimeRange';
+export * from './timeRange';
diff --git a/packages/x-date-pickers-pro/src/internal/models/range.ts b/packages/x-date-pickers-pro/src/internal/models/range.ts
new file mode 100644
index 0000000000000..935347e5d2f42
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/models/range.ts
@@ -0,0 +1,13 @@
+import { FieldSection } from '@mui/x-date-pickers/internals-fields';
+
+export type DateRange = [TDate | null, TDate | null];
+export type NonEmptyDateRange = [TDate, TDate];
+
+export interface CurrentlySelectingRangeEndProps {
+ currentlySelectingRangeEnd: 'start' | 'end';
+ setCurrentlySelectingRangeEnd: (newSelectingEnd: 'start' | 'end') => void;
+}
+
+export interface DateRangeFieldSection extends FieldSection {
+ dateName: 'start' | 'end';
+}
diff --git a/packages/x-date-pickers-pro/src/internal/models/timeRange.ts b/packages/x-date-pickers-pro/src/internal/models/timeRange.ts
new file mode 100644
index 0000000000000..afa4e759e391c
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/models/timeRange.ts
@@ -0,0 +1,25 @@
+import { UseFieldInternalProps } from '@mui/x-date-pickers/internals-fields';
+import {
+ BaseTimeValidationProps,
+ TimeValidationProps,
+ DefaultizedProps,
+ MakeOptional,
+} from '@mui/x-date-pickers/internals';
+import { DateRange } from './range';
+import { TimeRangeValidationError } from '../hooks/validation/useTimeRangeValidation';
+
+export interface UseTimeRangeFieldProps
+ extends MakeOptional, TimeRangeValidationError>, 'format'>,
+ TimeValidationProps,
+ BaseTimeValidationProps {
+ /**
+ * 12h/24h view for hour selection clock.
+ * @default `utils.is12HourCycleInCurrentLocale()`
+ */
+ ampm?: boolean;
+}
+
+export type UseTimeRangeFieldDefaultizedProps = DefaultizedProps<
+ UseTimeRangeFieldProps,
+ keyof BaseTimeValidationProps | 'format'
+>;
diff --git a/packages/x-date-pickers-pro/src/internal/utils/date-fields-utils.ts b/packages/x-date-pickers-pro/src/internal/utils/date-fields-utils.ts
new file mode 100644
index 0000000000000..cb4728715a7e2
--- /dev/null
+++ b/packages/x-date-pickers-pro/src/internal/utils/date-fields-utils.ts
@@ -0,0 +1,24 @@
+import { DateRangeFieldSection } from '../models/range';
+
+export const splitDateRangeSections = (sections: DateRangeFieldSection[]) => {
+ const startDateSections: DateRangeFieldSection[] = [];
+ const endDateSections: DateRangeFieldSection[] = [];
+ sections.forEach((section) => {
+ if (section.dateName === 'start') {
+ startDateSections.push(section);
+ } else {
+ endDateSections.push(section);
+ }
+ });
+
+ return { startDate: startDateSections, endDate: endDateSections };
+};
+
+export const removeLastSeparator = (dateSections: DateRangeFieldSection[]) =>
+ dateSections.map((section, sectionIndex) => {
+ if (sectionIndex === dateSections.length - 1) {
+ return { ...section, separator: null };
+ }
+
+ return section;
+ });
diff --git a/packages/x-date-pickers-pro/src/internal/utils/date-utils.ts b/packages/x-date-pickers-pro/src/internal/utils/date-utils.ts
index 366813f24c259..edb91ac14e8c4 100644
--- a/packages/x-date-pickers-pro/src/internal/utils/date-utils.ts
+++ b/packages/x-date-pickers-pro/src/internal/utils/date-utils.ts
@@ -1,5 +1,5 @@
import { MuiPickersAdapter, replaceInvalidDateByNull } from '@mui/x-date-pickers/internals';
-import { DateRange, NonEmptyDateRange } from '../models';
+import { DateRange, NonEmptyDateRange } from '../models/range';
export const replaceInvalidDatesByNull = (
utils: MuiPickersAdapter,
diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts
index 038a48a86e964..5b4d31950af4f 100644
--- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts
+++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts
@@ -26,6 +26,11 @@ export interface UseDateTimeFieldProps
BaseDateValidationProps,
TimeValidationProps,
BaseTimeValidationProps {
+ /**
+ * 12h/24h view for hour selection clock.
+ * @default `utils.is12HourCycleInCurrentLocale()`
+ */
+ ampm?: boolean;
/**
* Minimal selectable moment of time with binding to date, to set min time in each day use `minTime`.
*/
diff --git a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts
index 54efe71b996df..a3b0fe1f5dd59 100644
--- a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts
+++ b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts
@@ -31,11 +31,16 @@ const useDefaultizedDateTimeField = (
const utils = useUtils();
const defaultDates = useDefaultDates();
+ const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale();
+ const defaultFormat = ampm
+ ? utils.formats.keyboardDateTime12h
+ : utils.formats.keyboardDateTime24h;
+
return {
...props,
disablePast: props.disablePast ?? false,
disableFuture: props.disableFuture ?? false,
- format: props.format ?? utils.formats.keyboardDateTime,
+ format: props.format ?? defaultFormat,
disableIgnoringDatePartForTimeValidation: Boolean(props.minDateTime || props.maxDateTime),
minDate: applyDefaultDate(utils, props.minDateTime ?? props.minDate, defaultDates.minDate),
maxDate: applyDefaultDate(utils, props.maxDateTime ?? props.maxDate, defaultDates.maxDate),
@@ -71,6 +76,7 @@ export const useDateTimeField = ({
shouldDisableTime,
selectedSections,
onSelectedSectionsChange,
+ ampm,
...other
} = useDefaultizedDateTimeField(props);
@@ -99,6 +105,7 @@ export const useDateTimeField = ({
selectedSections,
onSelectedSectionsChange,
inputRef,
+ ampm,
},
valueManager: dateTimePickerValueManager,
fieldValueManager: dateTimeFieldValueManager,
diff --git a/packages/x-date-pickers/src/TimeField/TimeField.types.ts b/packages/x-date-pickers/src/TimeField/TimeField.types.ts
index 3ea6d8f96f638..0fe98fe6f5ab2 100644
--- a/packages/x-date-pickers/src/TimeField/TimeField.types.ts
+++ b/packages/x-date-pickers/src/TimeField/TimeField.types.ts
@@ -14,7 +14,13 @@ export interface UseTimeFieldParams {
export interface UseTimeFieldProps
extends MakeOptional, 'format'>,
TimeValidationProps,
- BaseTimeValidationProps {}
+ BaseTimeValidationProps {
+ /**
+ * 12h/24h view for hour selection clock.
+ * @default `utils.is12HourCycleInCurrentLocale()`
+ */
+ ampm?: boolean;
+}
export type UseTimeFieldDefaultizedProps = DefaultizedProps<
UseTimeFieldProps,
diff --git a/packages/x-date-pickers/src/TimeField/index.ts b/packages/x-date-pickers/src/TimeField/index.ts
index 3c6e2bbb3d594..8705e11f26e53 100644
--- a/packages/x-date-pickers/src/TimeField/index.ts
+++ b/packages/x-date-pickers/src/TimeField/index.ts
@@ -1,5 +1,5 @@
export { TimeField as Unstable_TimeField } from './TimeField';
-export { useTimeField as unstable_useDateTimeField } from './useTimeField';
+export { useTimeField as unstable_useTimeField } from './useTimeField';
export type {
UseTimeFieldProps,
UseTimeFieldComponentProps,
diff --git a/packages/x-date-pickers/src/TimeField/useTimeField.ts b/packages/x-date-pickers/src/TimeField/useTimeField.ts
index 2ad8bd02c4684..e515988ac4006 100644
--- a/packages/x-date-pickers/src/TimeField/useTimeField.ts
+++ b/packages/x-date-pickers/src/TimeField/useTimeField.ts
@@ -24,11 +24,14 @@ const useDefaultizedTimeField = (
): AdditionalProps & UseTimeFieldDefaultizedProps => {
const utils = useUtils();
+ const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale();
+ const defaultFormat = ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h;
+
return {
...props,
disablePast: props.disablePast ?? false,
disableFuture: props.disableFuture ?? false,
- format: props.format ?? utils.formats.fullTime,
+ format: props.format ?? defaultFormat,
} as any;
};
@@ -52,6 +55,7 @@ export const useTimeField = ({
disableIgnoringDatePartForTimeValidation,
selectedSections,
onSelectedSectionsChange,
+ ampm,
...other
} = useDefaultizedTimeField(props);
@@ -75,6 +79,7 @@ export const useTimeField = ({
selectedSections,
onSelectedSectionsChange,
inputRef,
+ ampm,
},
valueManager: timePickerValueManager,
fieldValueManager: timeFieldValueManager,
diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts
index 5557fb15cc191..aff77ab9e914c 100644
--- a/packages/x-date-pickers/src/internals/index.ts
+++ b/packages/x-date-pickers/src/internals/index.ts
@@ -82,7 +82,12 @@ export { usePickerState } from './hooks/usePickerState';
export type { PickerStateProps, PickerStatePickerProps } from './hooks/usePickerState';
export type { PickerStateValueManager, PickerSelectionState } from './hooks/usePickerState';
export { useLocalizationContext, useDefaultDates, useUtils, useLocaleText } from './hooks/useUtils';
-export type { BaseDateValidationProps, DayValidationProps } from './hooks/validation/models';
+export type {
+ BaseDateValidationProps,
+ BaseTimeValidationProps,
+ TimeValidationProps,
+ DayValidationProps,
+} from './hooks/validation/models';
export { useValidation } from './hooks/validation/useValidation';
export type {
ValidationCommonProps,
@@ -90,7 +95,11 @@ export type {
Validator,
} from './hooks/validation/useValidation';
export { validateDate } from './hooks/validation/useDateValidation';
+export { validateTime } from './hooks/validation/useTimeValidation';
+export { validateDateTime } from './hooks/validation/useDateTimeValidation';
export type { DateValidationError } from './hooks/validation/useDateValidation';
+export type { TimeValidationError } from './hooks/validation/useTimeValidation';
+export type { DateTimeValidationError } from './hooks/validation/useDateTimeValidation';
export { usePreviousMonthDisabled, useNextMonthDisabled } from './hooks/date-helpers-hooks';
export type { BasePickerProps } from './models/props/basePickerProps';