diff --git a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx b/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx index 31ca80124867..feaaf51b49e6 100644 --- a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx @@ -15,7 +15,12 @@ import { interface AutoCompleteFieldProps extends UseDateFieldProps, - BaseSingleInputFieldProps { + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateValidationError + > { /** * @typescript-to-proptypes-ignore */ diff --git a/docs/data/date-pickers/custom-field/PickerWithBrowserField.tsx b/docs/data/date-pickers/custom-field/PickerWithBrowserField.tsx index b57d631f7956..a7bf9adc4778 100644 --- a/docs/data/date-pickers/custom-field/PickerWithBrowserField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithBrowserField.tsx @@ -76,6 +76,7 @@ interface BrowserMultiInputDateRangeFieldProps extends UseDateRangeFieldProps, BaseMultiInputFieldProps< DateRange, + Dayjs, RangeFieldSection, DateRangeValidationError > {} @@ -161,7 +162,12 @@ function BrowserDateRangePicker(props: DateRangePickerProps) { interface BrowserDateFieldProps extends UseDateFieldProps, - BaseSingleInputFieldProps {} + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateValidationError + > {} function BrowserDateField(props: BrowserDateFieldProps) { const { inputRef: externalInputRef, slots, slotProps, ...textFieldProps } = props; diff --git a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx index 4874aa92e011..e7137238b01c 100644 --- a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx @@ -14,7 +14,12 @@ import { interface ButtonFieldProps extends UseDateFieldProps, - BaseSingleInputFieldProps { + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateValidationError + > { setOpen?: React.Dispatch>; } diff --git a/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx b/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx index ccc0e768af09..936530b3ae4c 100644 --- a/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithJoyField.tsx @@ -125,6 +125,7 @@ interface JoyMultiInputDateRangeFieldProps extends UseDateRangeFieldProps, BaseMultiInputFieldProps< DateRange, + Dayjs, RangeFieldSection, DateRangeValidationError > {} @@ -210,7 +211,12 @@ function JoyDateRangePicker(props: DateRangePickerProps) { interface JoyDateFieldProps extends UseDateFieldProps, - BaseSingleInputFieldProps {} + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateValidationError + > {} function JoyDateField(props: JoyDateFieldProps) { const { inputRef: externalInputRef, slots, slotProps, ...textFieldProps } = props; diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index 82a1e5314fa7..eefbed668457 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -92,11 +92,21 @@ On the examples below, you can see that the typing of the props received by a cu ```tsx interface JoyDateFieldProps extends UseDateFieldProps, // The headless field props - BaseSingleInputFieldProps {} // The DOM field props + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateValidationError + > {} // The DOM field props interface JoyDateTimeFieldProps extends UseDateTimeFieldProps, // The headless field props - BaseSingleInputFieldProps {} // The DOM field props + BaseSingleInputFieldProps< + Dayjs | null, + Dayjs, + FieldSection, + DateTimeValidationError + > {} // The DOM field props ``` ### The headless field props diff --git a/docs/pages/x/api/date-pickers/date-field.json b/docs/pages/x/api/date-pickers/date-field.json index 37c78cc725b4..05c27f9ade47 100644 --- a/docs/pages/x/api/date-pickers/date-field.json +++ b/docs/pages/x/api/date-pickers/date-field.json @@ -56,6 +56,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/pages/x/api/date-pickers/date-time-field.json b/docs/pages/x/api/date-pickers/date-time-field.json index c3e3f23f0fa4..250ec2ba5eb2 100644 --- a/docs/pages/x/api/date-pickers/date-time-field.json +++ b/docs/pages/x/api/date-pickers/date-time-field.json @@ -63,6 +63,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/pages/x/api/date-pickers/multi-input-date-range-field.json b/docs/pages/x/api/date-pickers/multi-input-date-range-field.json index 28ffe4c20aea..59b0c80099aa 100644 --- a/docs/pages/x/api/date-pickers/multi-input-date-range-field.json +++ b/docs/pages/x/api/date-pickers/multi-input-date-range-field.json @@ -35,6 +35,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "selectedSections": { "type": { "name": "union", diff --git a/docs/pages/x/api/date-pickers/multi-input-date-time-range-field.json b/docs/pages/x/api/date-pickers/multi-input-date-time-range-field.json index c37c3087c5aa..a347b09a2006 100644 --- a/docs/pages/x/api/date-pickers/multi-input-date-time-range-field.json +++ b/docs/pages/x/api/date-pickers/multi-input-date-time-range-field.json @@ -42,6 +42,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "selectedSections": { "type": { "name": "union", diff --git a/docs/pages/x/api/date-pickers/multi-input-time-range-field.json b/docs/pages/x/api/date-pickers/multi-input-time-range-field.json index 5adefd88cc8c..f35fea9e0405 100644 --- a/docs/pages/x/api/date-pickers/multi-input-time-range-field.json +++ b/docs/pages/x/api/date-pickers/multi-input-time-range-field.json @@ -38,6 +38,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "selectedSections": { "type": { "name": "union", diff --git a/docs/pages/x/api/date-pickers/single-input-date-range-field.json b/docs/pages/x/api/date-pickers/single-input-date-range-field.json index d2efc36b8d90..27221cfe67a8 100644 --- a/docs/pages/x/api/date-pickers/single-input-date-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-date-range-field.json @@ -56,6 +56,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json b/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json index 8834b102ba2f..5579310aa05f 100644 --- a/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-date-time-range-field.json @@ -63,6 +63,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/pages/x/api/date-pickers/single-input-time-range-field.json b/docs/pages/x/api/date-pickers/single-input-time-range-field.json index 3b386903c6bc..6ac907dd8d67 100644 --- a/docs/pages/x/api/date-pickers/single-input-time-range-field.json +++ b/docs/pages/x/api/date-pickers/single-input-time-range-field.json @@ -59,6 +59,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/pages/x/api/date-pickers/time-field.json b/docs/pages/x/api/date-pickers/time-field.json index 04f20013ff9a..de887cd7924c 100644 --- a/docs/pages/x/api/date-pickers/time-field.json +++ b/docs/pages/x/api/date-pickers/time-field.json @@ -59,6 +59,10 @@ "onError": { "type": { "name": "func" } }, "onSelectedSectionsChange": { "type": { "name": "func" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used." + }, "required": { "type": { "name": "bool" } }, "selectedSections": { "type": { diff --git a/docs/translations/api-docs/date-pickers/date-field.json b/docs/translations/api-docs/date-pickers/date-field.json index 6163c42344f2..661815dc609b 100644 --- a/docs/translations/api-docs/date-pickers/date-field.json +++ b/docs/translations/api-docs/date-pickers/date-field.json @@ -30,6 +30,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableDate": "Disable specific date.

Signature:
function(day: TDate) => boolean
day: The date to test.
returns (boolean): If true the date will be disabled.", diff --git a/docs/translations/api-docs/date-pickers/date-time-field.json b/docs/translations/api-docs/date-pickers/date-time-field.json index 545da8887bd2..5d3b9aeb7f05 100644 --- a/docs/translations/api-docs/date-pickers/date-time-field.json +++ b/docs/translations/api-docs/date-pickers/date-time-field.json @@ -37,6 +37,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", diff --git a/docs/translations/api-docs/date-pickers/multi-input-date-range-field.json b/docs/translations/api-docs/date-pickers/multi-input-date-range-field.json index 532aac004c5b..d5fa06f3562e 100644 --- a/docs/translations/api-docs/date-pickers/multi-input-date-range-field.json +++ b/docs/translations/api-docs/date-pickers/multi-input-date-range-field.json @@ -17,6 +17,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableDate": "Disable specific date.

Signature:
function(day: TDate, position: string) => boolean
day: The date to test.
position: The date to test, 'start' or 'end'.
returns (boolean): Returns true if the date should be disabled.", "shouldRespectLeadingZeros": "If true, the format will respect the leading zeroes (e.g: on dayjs, the format M/D/YYYY will render 8/16/2018) If false, the format will always add leading zeroes (e.g: on dayjs, the format M/D/YYYY will render 08/16/2018)
Warning n°1: Luxon is not able to respect the leading zeroes when using macro tokens (e.g: "DD"), so shouldRespectLeadingZeros={true} might lead to inconsistencies when using AdapterLuxon.
Warning n°2: When shouldRespectLeadingZeros={true}, the field will add an invisible character on the sections containing a single digit to make sure onChange is fired. If you need to get the clean value from the input, you can remove this character using input.value.replace(/\\u200e/g, '').
Warning n°3: When used in strict mode, dayjs and moment require to respect the leading zeros. This mean that when using shouldRespectLeadingZeros={false}, if you retrieve the value directly from the input (not listening to onChange) and your format contains tokens without leading zeros, the value will not be parsed by your library.", diff --git a/docs/translations/api-docs/date-pickers/multi-input-date-time-range-field.json b/docs/translations/api-docs/date-pickers/multi-input-date-time-range-field.json index a12f3d48611b..de3f66cc2cf8 100644 --- a/docs/translations/api-docs/date-pickers/multi-input-date-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/multi-input-date-time-range-field.json @@ -24,6 +24,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "shouldDisableDate": "Disable specific date.

Signature:
function(day: TDate, position: string) => boolean
day: The date to test.
position: The date to test, 'start' or 'end'.
returns (boolean): Returns true if the date should be disabled.", diff --git a/docs/translations/api-docs/date-pickers/multi-input-time-range-field.json b/docs/translations/api-docs/date-pickers/multi-input-time-range-field.json index 944b30e82ad0..b1055183d367 100644 --- a/docs/translations/api-docs/date-pickers/multi-input-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/multi-input-time-range-field.json @@ -20,6 +20,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "shouldDisableTime": "Disable specific time.

Signature:
function(value: TDate, view: TimeView) => boolean
value: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", diff --git a/docs/translations/api-docs/date-pickers/single-input-date-range-field.json b/docs/translations/api-docs/date-pickers/single-input-date-range-field.json index d2b8aa10caab..8404f0fd0c35 100644 --- a/docs/translations/api-docs/date-pickers/single-input-date-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-date-range-field.json @@ -30,6 +30,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableDate": "Disable specific date.

Signature:
function(day: TDate, position: string) => boolean
day: The date to test.
position: The date to test, 'start' or 'end'.
returns (boolean): Returns true if the date should be disabled.", diff --git a/docs/translations/api-docs/date-pickers/single-input-date-time-range-field.json b/docs/translations/api-docs/date-pickers/single-input-date-time-range-field.json index 9c3d66cb8386..1a4429f065e0 100644 --- a/docs/translations/api-docs/date-pickers/single-input-date-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-date-time-range-field.json @@ -37,6 +37,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", diff --git a/docs/translations/api-docs/date-pickers/single-input-time-range-field.json b/docs/translations/api-docs/date-pickers/single-input-time-range-field.json index 224a20356ebe..cb886f4ef37a 100644 --- a/docs/translations/api-docs/date-pickers/single-input-time-range-field.json +++ b/docs/translations/api-docs/date-pickers/single-input-time-range-field.json @@ -33,6 +33,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", diff --git a/docs/translations/api-docs/date-pickers/time-field.json b/docs/translations/api-docs/date-pickers/time-field.json index 224a20356ebe..cb886f4ef37a 100644 --- a/docs/translations/api-docs/date-pickers/time-field.json +++ b/docs/translations/api-docs/date-pickers/time-field.json @@ -33,6 +33,7 @@ "onError": "Callback fired when the error associated to the current value changes.

Signature:
function(error: TError, value: TValue) => void
error: The new error.
value: The value associated to the error.", "onSelectedSectionsChange": "Callback fired when the selected sections change.

Signature:
function(newValue: FieldSelectedSections) => void
newValue: The new selected sections.", "readOnly": "It prevents the user from changing the value of the field (not from interacting with the field).", + "referenceDate": "The date used to generate a part of the date-time that is not present in the format when both value and defaultValue are not present. For example, on time fields it will be used to determine the date to set.", "required": "If true, the label is displayed as required and the input element is required.", "selectedSections": "The currently selected sections. This prop accept four formats: 1. If a number is provided, the section at this index will be selected. 2. If an object with a startIndex and endIndex properties are provided, the sections between those two indexes will be selected. 3. If a string of type FieldSectionType is provided, the first section with that name will be selected. 4. If null is provided, no section will be selected If not provided, the selected sections will be handled internally.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx index 7f57aa27c260..152a7e79946d 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx @@ -259,6 +259,12 @@ MultiInputDateRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * The currently selected sections. * This prop accept four formats: diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx index b76681979c23..a696dfd2181f 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx @@ -292,6 +292,12 @@ MultiInputDateTimeRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * The currently selected sections. * This prop accept four formats: diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx index 393d7e874776..821c4177ad0b 100644 --- a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx @@ -277,6 +277,12 @@ MultiInputTimeRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * The currently selected sections. * This prop accept four formats: diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx index 42834dce277a..32eb03646a6a 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx @@ -227,6 +227,12 @@ SingleInputDateRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx index 23b2245f72ec..c62540b53ce2 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx @@ -261,6 +261,12 @@ SingleInputDateTimeRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx index 669ee8d2d48b..fa2824441a89 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx @@ -244,6 +244,12 @@ SingleInputTimeRangeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers-pro/src/internal/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx b/packages/x-date-pickers-pro/src/internal/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx index 3186163a2cbe..d42353021b6f 100644 --- a/packages/x-date-pickers-pro/src/internal/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internal/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx @@ -103,6 +103,7 @@ export const useDesktopRangePicker = < const fieldProps: BaseFieldProps< DateRange, + TDate, RangeFieldSection, InferError > = useSlotProps({ diff --git a/packages/x-date-pickers-pro/src/internal/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internal/hooks/useEnrichedRangePickerFieldProps.ts index 08ee993b34f2..fc84c056a254 100644 --- a/packages/x-date-pickers-pro/src/internal/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internal/hooks/useEnrichedRangePickerFieldProps.ts @@ -52,7 +52,9 @@ export interface RangePickerFieldSlotsComponent { export interface RangePickerFieldSlotsComponentsProps { field?: SlotComponentProps< - React.ElementType, RangeFieldSection, unknown>>, + React.ElementType< + BaseMultiInputFieldProps, TDate, RangeFieldSection, unknown> + >, {}, UsePickerProps, any, RangeFieldSection, any, any, any> >; @@ -70,11 +72,12 @@ export interface UseEnrichedRangePickerFieldPropsParams< TDate, TView extends DateOrTimeViewWithMeridiem, TError, - FieldProps extends BaseFieldProps, RangeFieldSection, TError> = BaseFieldProps< + FieldProps extends BaseFieldProps< DateRange, + TDate, RangeFieldSection, TError - >, + > = BaseFieldProps, TDate, RangeFieldSection, TError>, > extends Pick< UsePickerResponse, TView, RangeFieldSection, any>, 'open' | 'actions' @@ -112,9 +115,9 @@ const useMultiInputFieldSlotProps = , RangeFieldSection, TError> + BaseMultiInputFieldProps, TDate, RangeFieldSection, TError> >) => { - type ReturnType = BaseMultiInputFieldProps, RangeFieldSection, TError>; + type ReturnType = BaseMultiInputFieldProps, TDate, RangeFieldSection, TError>; const localeText = useLocaleText(); const startRef = React.useRef(null); @@ -247,9 +250,9 @@ const useSingleInputFieldSlotProps = , RangeFieldSection, TError> + BaseSingleInputFieldProps, TDate, RangeFieldSection, TError> >) => { - type ReturnType = BaseSingleInputFieldProps, RangeFieldSection, TError>; + type ReturnType = BaseSingleInputFieldProps, TDate, RangeFieldSection, TError>; const inputRef = React.useRef(null); const handleInputRef = useForkRef(inInputRef, inputRef); diff --git a/packages/x-date-pickers-pro/src/internal/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internal/hooks/useMobileRangePicker/useMobileRangePicker.tsx index 33abbf9de8b6..f93b68777e02 100644 --- a/packages/x-date-pickers-pro/src/internal/hooks/useMobileRangePicker/useMobileRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internal/hooks/useMobileRangePicker/useMobileRangePicker.tsx @@ -86,6 +86,7 @@ export const useMobileRangePicker = < const fieldProps: BaseMultiInputFieldProps< DateRange, + TDate, RangeFieldSection, InferError > = useSlotProps({ 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 c3d2dd636943..e509679ab14d 100644 --- a/packages/x-date-pickers-pro/src/internal/models/dateRange.ts +++ b/packages/x-date-pickers-pro/src/internal/models/dateRange.ts @@ -35,7 +35,7 @@ export interface BaseRangeProps { export interface UseDateRangeFieldProps extends MakeOptional< - UseFieldInternalProps, RangeFieldSection, DateRangeValidationError>, + UseFieldInternalProps, TDate, RangeFieldSection, DateRangeValidationError>, 'format' >, DayRangeValidationProps, diff --git a/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts b/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts index 48223acd9672..17bfda22b4f4 100644 --- a/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts +++ b/packages/x-date-pickers-pro/src/internal/models/dateTimeRange.ts @@ -13,7 +13,12 @@ import { RangeFieldSection } from './fields'; export interface UseDateTimeRangeFieldProps extends MakeOptional< - UseFieldInternalProps, RangeFieldSection, DateTimeRangeValidationError>, + UseFieldInternalProps< + DateRange, + TDate, + RangeFieldSection, + DateTimeRangeValidationError + >, 'format' >, DayRangeValidationProps, diff --git a/packages/x-date-pickers-pro/src/internal/models/fields.ts b/packages/x-date-pickers-pro/src/internal/models/fields.ts index 7f9b5d684481..da95e6640591 100644 --- a/packages/x-date-pickers-pro/src/internal/models/fields.ts +++ b/packages/x-date-pickers-pro/src/internal/models/fields.ts @@ -32,8 +32,8 @@ export interface MultiInputFieldSlotRootProps { * Props the multi input field can receive when used inside a picker. * Only contains what the MUI component are passing to the field, not what users can pass using the `props.slotProps.field`. */ -export interface BaseMultiInputFieldProps - extends BaseFieldProps { +export interface BaseMultiInputFieldProps + extends BaseFieldProps { slots?: { root?: React.ElementType; separator?: React.ElementType; diff --git a/packages/x-date-pickers-pro/src/internal/models/timeRange.ts b/packages/x-date-pickers-pro/src/internal/models/timeRange.ts index 9b5130389be1..239def969cb5 100644 --- a/packages/x-date-pickers-pro/src/internal/models/timeRange.ts +++ b/packages/x-date-pickers-pro/src/internal/models/timeRange.ts @@ -12,7 +12,7 @@ import { RangeFieldSection } from './fields'; export interface UseTimeRangeFieldProps extends MakeOptional< - UseFieldInternalProps, RangeFieldSection, TimeRangeValidationError>, + UseFieldInternalProps, TDate, RangeFieldSection, TimeRangeValidationError>, 'format' >, TimeValidationProps, diff --git a/packages/x-date-pickers-pro/src/internal/utils/valueManagers.ts b/packages/x-date-pickers-pro/src/internal/utils/valueManagers.ts index 8b53f7728de1..b5cb0862b166 100644 --- a/packages/x-date-pickers-pro/src/internal/utils/valueManagers.ts +++ b/packages/x-date-pickers-pro/src/internal/utils/valueManagers.ts @@ -5,6 +5,8 @@ import { addPositionPropertiesToSections, createDateStrForInputFromSections, areDatesEqual, + getTodayDate, + getDefaultReferenceDate, } from '@mui/x-date-pickers/internals'; import { DateRange, RangePosition } from '../models/range'; import { splitDateRangeSections, removeLastSeparator } from './date-fields-utils'; @@ -26,10 +28,25 @@ export type RangePickerValueManager< export const rangeValueManager: RangePickerValueManager = { emptyValue: [null, null], - getTodayValue: (utils, valueType) => - valueType === 'date' - ? [utils.startOfDay(utils.date())!, utils.startOfDay(utils.date())!] - : [utils.date()!, utils.date()!], + getTodayValue: (utils, valueType) => [ + getTodayDate(utils, valueType), + getTodayDate(utils, valueType), + ], + getInitialReferenceValue: ({ value, referenceDate: referenceDateProp, ...params }) => { + const shouldKeepStartDate = value[0] != null && params.utils.isValid(value[0]); + const shouldKeepEndDate = value[1] != null && params.utils.isValid(value[1]); + + if (shouldKeepStartDate && shouldKeepEndDate) { + return value; + } + + const referenceDate = referenceDateProp ?? getDefaultReferenceDate(params); + + return [ + shouldKeepStartDate ? value[0] : referenceDate, + shouldKeepEndDate ? value[1] : referenceDate, + ]; + }, cleanValue: (utils, value) => value.map((date) => replaceInvalidDateByNull(utils, date)) as DateRange, areValuesEqual: (utils, a, b) => diff --git a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts index dc56476b80ad..1233fc18151b 100644 --- a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts +++ b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts @@ -26,6 +26,7 @@ import getHours from 'date-fns/getHours'; import getMinutes from 'date-fns/getMinutes'; import getMonth from 'date-fns/getMonth'; import getSeconds from 'date-fns/getSeconds'; +import getMilliseconds from 'date-fns/getMilliseconds'; import getWeek from 'date-fns/getWeek'; import getYear from 'date-fns/getYear'; import isAfter from 'date-fns/isAfter'; @@ -42,6 +43,7 @@ import setHours from 'date-fns/setHours'; import setMinutes from 'date-fns/setMinutes'; import setMonth from 'date-fns/setMonth'; import setSeconds from 'date-fns/setSeconds'; +import setMilliseconds from 'date-fns/setMilliseconds'; import setYear from 'date-fns/setYear'; import startOfDay from 'date-fns/startOfDay'; import startOfMonth from 'date-fns/startOfMonth'; @@ -453,6 +455,10 @@ export class AdapterDateFns implements MuiPickersAdapter { return getSeconds(value); }; + public getMilliseconds = (value: Date) => { + return getMilliseconds(value); + }; + public setYear = (value: Date, year: number) => { return setYear(value, year); }; @@ -477,6 +483,10 @@ export class AdapterDateFns implements MuiPickersAdapter { return setSeconds(value, seconds); }; + public setMilliseconds = (value: Date, milliseconds: number) => { + return setMilliseconds(value, milliseconds); + }; + public getDaysInMonth = (value: Date) => { return getDaysInMonth(value); }; diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts index 268acf0bab33..231e4b9995b5 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts @@ -23,6 +23,7 @@ import dateFnsFormat from 'date-fns-jalali/format'; import formatISO from 'date-fns-jalali/formatISO'; import getHours from 'date-fns-jalali/getHours'; import getSeconds from 'date-fns-jalali/getSeconds'; +import getMilliseconds from 'date-fns-jalali/getMilliseconds'; import getWeek from 'date-fns-jalali/getWeek'; import getYear from 'date-fns-jalali/getYear'; import getMonth from 'date-fns-jalali/getMonth'; @@ -44,6 +45,7 @@ import setHours from 'date-fns-jalali/setHours'; import setMinutes from 'date-fns-jalali/setMinutes'; import setMonth from 'date-fns-jalali/setMonth'; import setSeconds from 'date-fns-jalali/setSeconds'; +import setMilliseconds from 'date-fns-jalali/setMilliseconds'; import setYear from 'date-fns-jalali/setYear'; import startOfDay from 'date-fns-jalali/startOfDay'; import startOfMonth from 'date-fns-jalali/startOfMonth'; @@ -467,6 +469,10 @@ export class AdapterDateFnsJalali implements MuiPickersAdapter { + return getMilliseconds(value); + }; + public setYear = (value: Date, year: number) => { return setYear(value, year); }; @@ -491,6 +497,10 @@ export class AdapterDateFnsJalali implements MuiPickersAdapter { + return setMilliseconds(value, milliseconds); + }; + public getDaysInMonth = (value: Date) => { return getDaysInMonth(value); }; diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 166cfa94c2d6..36e107aa11b7 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -389,6 +389,10 @@ export class AdapterDayjs implements MuiPickersAdapter { return value.second(); }; + public getMilliseconds = (value: Dayjs) => { + return value.millisecond(); + }; + public setYear = (value: Dayjs, year: number) => { return value.set('year', year); }; @@ -413,6 +417,10 @@ export class AdapterDayjs implements MuiPickersAdapter { return value.set('second', seconds); }; + public setMilliseconds = (value: Dayjs, milliseconds: number) => { + return value.set('millisecond', milliseconds); + }; + public getDaysInMonth = (value: Dayjs) => { return value.daysInMonth(); }; diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts index 9a9740994aa6..341ef5e92534 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts @@ -399,6 +399,10 @@ export class AdapterLuxon implements MuiPickersAdapter { return value.get('second'); }; + public getMilliseconds = (value: DateTime) => { + return value.get('millisecond'); + }; + public setYear = (value: DateTime, year: number) => { return value.set({ year }); }; @@ -423,6 +427,10 @@ export class AdapterLuxon implements MuiPickersAdapter { return value.set({ second: seconds }); }; + public setMilliseconds = (value: DateTime, milliseconds: number) => { + return value.set({ millisecond: milliseconds }); + }; + public getDaysInMonth = (value: DateTime) => { return value.daysInMonth!; }; diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts index 269c267396bb..e3ca90e61773 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts @@ -371,6 +371,10 @@ export class AdapterMoment implements MuiPickersAdapter { return value.get('seconds'); }; + public getMilliseconds = (value: Moment) => { + return value.get('milliseconds'); + }; + public setYear = (value: Moment, year: number) => { return value.clone().year(year); }; @@ -395,6 +399,10 @@ export class AdapterMoment implements MuiPickersAdapter { return value.clone().seconds(seconds); }; + public setMilliseconds = (value: Moment, milliseconds: number) => { + return value.clone().milliseconds(milliseconds); + }; + public getDaysInMonth = (value: Moment) => { return value.daysInMonth(); }; diff --git a/packages/x-date-pickers/src/DateField/DateField.tsx b/packages/x-date-pickers/src/DateField/DateField.tsx index 89ea32a651c3..94eeed741bd1 100644 --- a/packages/x-date-pickers/src/DateField/DateField.tsx +++ b/packages/x-date-pickers/src/DateField/DateField.tsx @@ -224,6 +224,12 @@ DateField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers/src/DateField/DateField.types.ts b/packages/x-date-pickers/src/DateField/DateField.types.ts index acf3abdea8a1..2664c54f8769 100644 --- a/packages/x-date-pickers/src/DateField/DateField.types.ts +++ b/packages/x-date-pickers/src/DateField/DateField.types.ts @@ -20,7 +20,7 @@ export interface UseDateFieldParams { export interface UseDateFieldProps extends MakeOptional< - UseFieldInternalProps, + UseFieldInternalProps, 'format' >, DayValidationProps, diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx index c4a1e3775a6e..1b258a28f0b1 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx @@ -259,6 +259,12 @@ DateTimeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts index 14c2c3476d38..5b860c84e24b 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts @@ -23,7 +23,7 @@ export interface UseDateTimeFieldParams { export interface UseDateTimeFieldProps extends MakeOptional< - UseFieldInternalProps, + UseFieldInternalProps, 'format' >, DayValidationProps, diff --git a/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx new file mode 100644 index 000000000000..64f8fdce6b01 --- /dev/null +++ b/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx @@ -0,0 +1,199 @@ +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { userEvent } from '@mui/monorepo/test/utils'; +import { DateTimeField } from '@mui/x-date-pickers/DateTimeField'; +import { + adapterToUse, + buildFieldInteractions, + createPickerRenderer, +} from 'test/utils/pickers-utils'; + +describe(' - Editing', () => { + const { render, clock } = createPickerRenderer({ + clock: 'fake', + clockConfig: new Date(2012, 4, 3, 14, 30, 15, 743), + }); + + const { renderWithProps } = buildFieldInteractions({ + clock, + render, + Component: DateTimeField, + }); + + describe('Reference value', () => { + it('should use the referenceDate prop when defined', () => { + const onChange = spy(); + const referenceDate = adapterToUse.date(new Date(2012, 4, 3, 14, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + referenceDate, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // All sections not present should equal the one from the referenceDate, and the month should equal January (because it's an ArrowUp on an empty month). + expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(referenceDate, 0)); + }); + + it('should not use the referenceDate prop when a value is defined', () => { + const onChange = spy(); + const value = adapterToUse.date(new Date(2018, 10, 3, 22, 15)); + const referenceDate = adapterToUse.date(new Date(2012, 4, 3, 14, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + referenceDate, + value, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Should equal the initial `value` prop with one less month. + expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(value, 11)); + }); + + it('should not use the referenceDate prop when a defaultValue is defined', () => { + const onChange = spy(); + const defaultValue = adapterToUse.date(new Date(2018, 10, 3, 22, 15)); + const referenceDate = adapterToUse.date(new Date(2012, 4, 3, 14, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + referenceDate, + defaultValue, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Should equal the initial `defaultValue` prop with one less month. + expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(defaultValue, 11)); + }); + + describe('Reference value based on section granularity', () => { + it('should only keep year when granularity = month', () => { + const onChange = spy(); + + const { input, selectSection } = renderWithProps({ + onChange, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01'); + }); + + it('should only keep year and month when granularity = day', () => { + const onChange = spy(); + + const { input, selectSection } = renderWithProps({ + onChange, + format: adapterToUse.formats.dayOfMonth, + }); + + selectSection('day'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + expect(onChange.lastCall.firstArg).toEqualDateTime('2012-05-01'); + }); + + it('should only keep up to the hours when granularity = minutes', () => { + const onChange = spy(); + + const { input, selectSection } = renderWithProps({ + onChange, + format: adapterToUse.formats.fullTime24h, + }); + + selectSection('hours'); + + // Set hours + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Set minutes + userEvent.keyPress(input, { key: 'ArrowRight' }); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + expect(onChange.lastCall.firstArg).toEqualDateTime('2012-05-03T00:00:00.000Z'); + }); + }); + + describe('Reference value based on validation props', () => { + it("should create a reference date just after the `minDate` if it's after the current date", () => { + const onChange = spy(); + const minDate = adapterToUse.date(new Date(2030, 4, 5, 18, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + minDate, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Respect the granularity and the minDate + expect(onChange.lastCall.firstArg).toEqualDateTime('2030-01-01T00:00'); + }); + + it("should ignore the `minDate` if it's before the current date", () => { + const onChange = spy(); + const minDate = adapterToUse.date(new Date(2007, 4, 5, 18, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + minDate, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Respect the granularity but not the minDate + expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01T00:00'); + }); + + it("should create a reference date just before the `maxDate` if it's before the current date", () => { + const onChange = spy(); + const maxDate = adapterToUse.date(new Date(2007, 4, 5, 18, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + maxDate, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Respect the granularity and the minDate + expect(onChange.lastCall.firstArg).toEqualDateTime('2007-01-01T00:00'); + }); + + it("should ignore the `maxDate` if it's after the current date", () => { + const onChange = spy(); + const maxDate = adapterToUse.date(new Date(2030, 4, 5, 18, 30)); + + const { input, selectSection } = renderWithProps({ + onChange, + maxDate, + format: adapterToUse.formats.month, + }); + + selectSection('month'); + userEvent.keyPress(input, { key: 'ArrowUp' }); + + // Respect the granularity but not the maxDate + expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01T00:00'); + }); + }); + }); +}); diff --git a/packages/x-date-pickers/src/TimeField/TimeField.tsx b/packages/x-date-pickers/src/TimeField/TimeField.tsx index 5c4f79f05570..fd4d20d73a42 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.tsx +++ b/packages/x-date-pickers/src/TimeField/TimeField.tsx @@ -241,6 +241,12 @@ TimeField.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate a part of the date-time that is not present in the format when both `value` and `defaultValue` are not present. + * For example, on time fields it will be used to determine the date to set. + * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. + */ + referenceDate: PropTypes.any, /** * If `true`, the label is displayed as required and the `input` element is required. * @default false diff --git a/packages/x-date-pickers/src/TimeField/TimeField.types.ts b/packages/x-date-pickers/src/TimeField/TimeField.types.ts index 341af4ddc433..de566f65bd91 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.types.ts +++ b/packages/x-date-pickers/src/TimeField/TimeField.types.ts @@ -15,7 +15,7 @@ export interface UseTimeFieldParams { export interface UseTimeFieldProps extends MakeOptional< - UseFieldInternalProps, + UseFieldInternalProps, 'format' >, TimeValidationProps, 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 970cc59a9080..ecf93fac6825 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -95,6 +95,7 @@ export const useDesktopPicker = < const Field = slots.field; const fieldProps: BaseSingleInputFieldProps< TDate | null, + TDate, FieldSection, InferError > = useSlotProps({ @@ -131,7 +132,12 @@ export const useDesktopPicker = < }; } - const slotsForField: BaseSingleInputFieldProps['slots'] = { + const slotsForField: BaseSingleInputFieldProps< + TDate | null, + TDate, + FieldSection, + unknown + >['slots'] = { textField: slots.textField, ...fieldProps.slots, }; 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 f19c210f4c19..c179b8d96771 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 @@ -33,7 +33,7 @@ export interface UseDesktopPickerSlotsComponent>; + Field: React.ElementType>; /** * Form control with an input to render the value inside the default field. * Receives the same props as `@mui/material/TextField`. @@ -68,7 +68,7 @@ export interface ExportedUseDesktopPickerSlotsComponentsProps< > extends PickersPopperSlotsComponentsProps, ExportedPickersLayoutSlotsComponentsProps { field?: SlotComponentProps< - React.ElementType>, + React.ElementType>, {}, UsePickerProps >; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 7f5b4e82cf97..ffc1308608eb 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -23,7 +23,7 @@ export const useField = < TDate, TSection extends FieldSection, TForwardedProps extends UseFieldForwardedProps, - TInternalProps extends UseFieldInternalProps, + TInternalProps extends UseFieldInternalProps, >( params: UseFieldParams, ): UseFieldResponse => { diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts index 6fafe5697175..eac46b004cd6 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts @@ -15,7 +15,7 @@ export interface UseFieldParams< TDate, TSection extends FieldSection, TForwardedProps extends UseFieldForwardedProps, - TInternalProps extends UseFieldInternalProps, + TInternalProps extends UseFieldInternalProps, > { inputRef?: React.Ref; forwardedProps: TForwardedProps; @@ -31,7 +31,7 @@ export interface UseFieldParams< valueType: FieldValueType; } -export interface UseFieldInternalProps { +export interface UseFieldInternalProps { /** * The selected value. * Used when the component is controlled. @@ -41,6 +41,12 @@ export interface UseFieldInternalProps { /** @@ -42,7 +46,7 @@ export const useFieldState = < TDate, TSection extends FieldSection, TForwardedProps extends UseFieldForwardedProps, - TInternalProps extends UseFieldInternalProps, + TInternalProps extends UseFieldInternalProps, >( params: UseFieldParams, ) => { @@ -61,6 +65,7 @@ export const useFieldState = < internalProps: { value: valueProp, defaultValue, + referenceDate: referenceDateProp, onChange, format, formatDensity = 'dense', @@ -104,16 +109,27 @@ export const useFieldState = < const sections = getSectionsFromValue(valueFromTheOutside); validateSections(sections, valueType); - return { + const stateWithoutReferenceDate: UseFieldState = { sections, value: valueFromTheOutside, - referenceValue: fieldValueManager.updateReferenceValue( - utils, - valueFromTheOutside, - valueManager.getTodayValue(utils, valueType), - ), + referenceValue: valueManager.emptyValue, tempValueStrAndroid: null, }; + + const granularity = getSectionTypeGranularity(sections); + const referenceValue = valueManager.getInitialReferenceValue({ + referenceDate: referenceDateProp, + value: valueFromTheOutside, + valueType, + utils, + props: internalProps as GetDefaultReferenceDateProps, + granularity, + }); + + return { + ...stateWithoutReferenceDate, + referenceValue, + }; }); const [selectedSections, innerSetSelectedSections] = useControlled({ 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 5f0187380290..54c53a7181af 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -65,6 +65,7 @@ export const useMobilePicker = < const Field = slots.field; const fieldProps: BaseSingleInputFieldProps< TDate | null, + TDate, FieldSection, InferError > = useSlotProps({ @@ -94,7 +95,12 @@ export const useMobilePicker = < 'aria-label': getOpenDialogAriaText(pickerFieldProps.value, utils), }; - const slotsForField: BaseSingleInputFieldProps['slots'] = { + const slotsForField: BaseSingleInputFieldProps< + TDate | null, + TDate, + FieldSection, + unknown + >['slots'] = { textField: slots.textField, ...fieldProps.slots, }; 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 769717b6523c..c3094e57cd0c 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 @@ -28,7 +28,7 @@ export interface UseMobilePickerSlotsComponent>; + Field: React.ElementType>; /** * Form control with an input to render the value inside the default field. * Receives the same props as `@mui/material/TextField`. @@ -43,7 +43,7 @@ export interface ExportedUseMobilePickerSlotsComponentsProps< > extends PickersModalDialogSlotsComponentsProps, ExportedPickersLayoutSlotsComponentsProps { field?: SlotComponentProps< - React.ElementType>, + React.ElementType>, {}, UsePickerProps >; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 208a1569ddff..b839c9327873 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -8,6 +8,7 @@ import { FieldValueType, MuiPickersAdapter, } from '../../../models'; +import { GetDefaultReferenceDateProps } from '../../utils/getDefaultReferenceDate'; export interface PickerValueManager { /** @@ -35,6 +36,26 @@ export interface PickerValueManager { * @returns {TValue} The value to set when clicking the "Today" button. */ getTodayValue: (utils: MuiPickersAdapter, valueType: FieldValueType) => TValue; + /** + * @template TDate, TValue + * Method returning the reference value to use when mounting the component. + * @param {object} params The params of the method. + * @param {TDate | undefined} params.referenceDate The referenceDate provided by the user. + * @param {TValue} params.value The value provided by the user. + * @param {GetDefaultReferenceDateProps} params.props The validation props needed to compute the reference value. + * @param {MuiPickersAdapter} params.utils The adapter. + * @param {FieldValueType} params.valueType The type of the value being edited. + * @param {granularity} params.granularity The granularity of the selection possible on this component. + * @returns {TValue} The reference value to use for non-provided dates. + */ + getInitialReferenceValue: (params: { + referenceDate: TDate | undefined; + value: TValue; + props: GetDefaultReferenceDateProps; + utils: MuiPickersAdapter; + valueType: FieldValueType; + granularity: number; + }) => TValue; /** * Method parsing the input value to replace all invalid dates by `null`. * @template TDate, TValue @@ -190,7 +211,7 @@ export interface UsePickerValueBaseProps { */ export interface UsePickerValueNonStaticProps extends Pick< - UseFieldInternalProps, + UseFieldInternalProps, 'selectedSections' | 'onSelectedSectionsChange' > { /** @@ -252,7 +273,7 @@ export interface UsePickerValueActions { export type UsePickerValueFieldResponse = Required< Pick< - UseFieldInternalProps, + UseFieldInternalProps, 'value' | 'onChange' | 'selectedSections' | 'onSelectedSectionsChange' > >; diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index d4a5d29fce35..e14c4907065d 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -116,8 +116,14 @@ export type { DateTimeValidationProps, } from './models/validation'; -export { applyDefaultDate, replaceInvalidDateByNull, areDatesEqual } from './utils/date-utils'; +export { + applyDefaultDate, + replaceInvalidDateByNull, + areDatesEqual, + getTodayDate, +} from './utils/date-utils'; export { splitFieldInternalAndForwardedProps } from './utils/fields'; +export { getDefaultReferenceDate } from './utils/getDefaultReferenceDate'; export { executeInTheNextEventLoopTick, getActiveElement, diff --git a/packages/x-date-pickers/src/internals/models/fields.ts b/packages/x-date-pickers/src/internals/models/fields.ts index 76d5e2e478de..552dfb3bef9d 100644 --- a/packages/x-date-pickers/src/internals/models/fields.ts +++ b/packages/x-date-pickers/src/internals/models/fields.ts @@ -3,8 +3,8 @@ import { TextFieldProps } from '@mui/material/TextField'; import type { UseFieldInternalProps } from '../hooks/useField'; import type { FieldSection } from '../../models'; -export interface BaseFieldProps - extends Omit, 'format'> { +export interface BaseFieldProps + extends Omit, 'format'> { className?: string; format?: string; disabled?: boolean; diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.ts b/packages/x-date-pickers/src/internals/utils/date-utils.ts index 2ea3e7562a8e..21e1f29acdc5 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.ts @@ -1,4 +1,4 @@ -import { MuiPickersAdapter } from '../../models'; +import { FieldValueType, MuiPickersAdapter } from '../../models'; interface FindClosestDateParams { date: TDate; @@ -134,3 +134,6 @@ export const mergeDateAndTime = ( return mergedDate; }; + +export const getTodayDate = (utils: MuiPickersAdapter, valueType: FieldValueType) => + valueType === 'date' ? utils.startOfDay(utils.date()!) : utils.date()!; diff --git a/packages/x-date-pickers/src/internals/utils/fields.ts b/packages/x-date-pickers/src/internals/utils/fields.ts index f41a36e2befd..d5fb9040ace5 100644 --- a/packages/x-date-pickers/src/internals/utils/fields.ts +++ b/packages/x-date-pickers/src/internals/utils/fields.ts @@ -8,6 +8,7 @@ import { const SHARED_FIELD_INTERNAL_PROP_NAMES = [ 'value', 'defaultValue', + 'referenceDate', 'format', 'formatDensity', 'onChange', diff --git a/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts new file mode 100644 index 000000000000..7aeca16f8cb7 --- /dev/null +++ b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts @@ -0,0 +1,103 @@ +import { createIsAfterIgnoreDatePart } from './time-utils'; +import { mergeDateAndTime, getTodayDate } from './date-utils'; +import { FieldSection, FieldValueType, MuiPickersAdapter } from '../../models'; + +export interface GetDefaultReferenceDateProps { + maxDate?: TDate; + minDate?: TDate; + minTime?: TDate; + maxTime?: TDate; + disableIgnoringDatePartForTimeValidation?: boolean; +} + +const SECTION_TYPE_GRANULARITY = { + year: 1, + month: 2, + day: 3, + hours: 4, + minutes: 5, + seconds: 6, + milliseconds: 7, +}; + +export const getSectionTypeGranularity = (sections: FieldSection[]) => + Math.max( + ...sections.map( + (section) => + SECTION_TYPE_GRANULARITY[section.type as keyof typeof SECTION_TYPE_GRANULARITY] ?? 1, + ), + ); + +const roundDate = (utils: MuiPickersAdapter, granularity: number, date: TDate) => { + if (granularity === SECTION_TYPE_GRANULARITY.year) { + return utils.startOfYear(date); + } + if (granularity === SECTION_TYPE_GRANULARITY.month) { + return utils.startOfMonth(date); + } + if (granularity === SECTION_TYPE_GRANULARITY.day) { + return utils.startOfDay(date); + } + + // We don't have startOfHour / startOfMinute / startOfSecond + let roundedDate = date; + if (granularity < SECTION_TYPE_GRANULARITY.minutes) { + roundedDate = utils.setMinutes(roundedDate, 0); + } + if (granularity < SECTION_TYPE_GRANULARITY.seconds) { + roundedDate = utils.setSeconds(roundedDate, 0); + } + if (granularity < SECTION_TYPE_GRANULARITY.milliseconds) { + roundedDate = utils.setMilliseconds(roundedDate, 0); + } + + return roundedDate; +}; + +export const getDefaultReferenceDate = ({ + props, + utils, + valueType, + granularity, +}: { + props: GetDefaultReferenceDateProps; + utils: MuiPickersAdapter; + valueType: FieldValueType; + granularity: number; +}) => { + let referenceDate = roundDate(utils, granularity, getTodayDate(utils, valueType)); + + if (props.minDate != null && utils.isAfterDay(props.minDate, referenceDate)) { + referenceDate = roundDate(utils, granularity, props.minDate); + } + + if (props.maxDate != null && utils.isBeforeDay(props.maxDate, referenceDate)) { + referenceDate = roundDate(utils, granularity, props.maxDate); + } + + const isAfter = createIsAfterIgnoreDatePart( + props.disableIgnoringDatePartForTimeValidation ?? false, + utils, + ); + if (props.minTime != null && isAfter(props.minTime, referenceDate)) { + referenceDate = roundDate( + utils, + granularity, + props.disableIgnoringDatePartForTimeValidation + ? props.minTime + : mergeDateAndTime(utils, referenceDate, props.minTime), + ); + } + + if (props.maxTime != null && isAfter(referenceDate, props.maxTime)) { + referenceDate = roundDate( + utils, + granularity, + props.disableIgnoringDatePartForTimeValidation + ? props.maxTime + : mergeDateAndTime(utils, referenceDate, props.maxTime), + ); + } + + return referenceDate; +}; diff --git a/packages/x-date-pickers/src/internals/utils/valueManagers.ts b/packages/x-date-pickers/src/internals/utils/valueManagers.ts index 6fcb3a04d46a..f0e399ebf58c 100644 --- a/packages/x-date-pickers/src/internals/utils/valueManagers.ts +++ b/packages/x-date-pickers/src/internals/utils/valueManagers.ts @@ -6,7 +6,8 @@ import { FieldSection, } from '../../models'; import type { FieldValueManager } from '../hooks/useField'; -import { areDatesEqual, replaceInvalidDateByNull } from './date-utils'; +import { areDatesEqual, getTodayDate, replaceInvalidDateByNull } from './date-utils'; +import { getDefaultReferenceDate } from './getDefaultReferenceDate'; import { addPositionPropertiesToSections, createDateStrForInputFromSections, @@ -20,8 +21,18 @@ export type SingleItemPickerValueManager< export const singleItemValueManager: SingleItemPickerValueManager = { emptyValue: null, - getTodayValue: (utils, valueType) => - valueType === 'date' ? utils.startOfDay(utils.date())! : utils.date()!, + getTodayValue: getTodayDate, + getInitialReferenceValue: ({ value, referenceDate, ...params }) => { + if (value != null && params.utils.isValid(value)) { + return value; + } + + if (referenceDate != null) { + return referenceDate; + } + + return getDefaultReferenceDate(params); + }, cleanValue: replaceInvalidDateByNull, areValuesEqual: areDatesEqual, isSameError: (a, b) => a === b, diff --git a/packages/x-date-pickers/src/models/adapters.ts b/packages/x-date-pickers/src/models/adapters.ts index d50e16ec25db..1b4c580baece 100644 --- a/packages/x-date-pickers/src/models/adapters.ts +++ b/packages/x-date-pickers/src/models/adapters.ts @@ -474,6 +474,13 @@ export interface MuiPickersAdapter { * @returns {number} The seconds of the given date. */ getSeconds(value: TDate): number; + /** + * Get the milliseconds of the given date. + * @template TDate + * @param {TDate} value The given date. + * @returns {number} The milliseconds of the given date. + */ + getMilliseconds(value: TDate): number; /** * Set the year to the given date. * @template TDate @@ -522,6 +529,14 @@ export interface MuiPickersAdapter { * @returns {TDate} The new date with the seconds set. */ setSeconds(value: TDate, seconds: number): TDate; + /** + * Set the milliseconds to the given date. + * @template TDate + * @param {TDate} value The date to be changed. + * @param {number} milliseconds The milliseconds of the new date. + * @returns {TDate} The new date with the milliseconds set. + */ + setMilliseconds(value: TDate, milliseconds: number): TDate; /** * Get the number of days in a month of the given date. * @template TDate diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index b7877c79039a..06b589a3a9d3 100644 --- a/packages/x-date-pickers/src/models/fields.ts +++ b/packages/x-date-pickers/src/models/fields.ts @@ -132,8 +132,8 @@ export type FieldSelectedSections = * Props the single input field can receive when used inside a picker. * Only contains what the MUI component are passing to the field, not what users can pass using the `props.slotProps.field`. */ -export interface BaseSingleInputFieldProps - extends BaseFieldProps { +export interface BaseSingleInputFieldProps + extends BaseFieldProps { label?: React.ReactNode; id?: string; inputRef?: React.Ref;