From 20e037b0270c1de1e4c0d42e4f4410f603f3ba82 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Tue, 31 Dec 2024 10:56:57 +0100 Subject: [PATCH] [pickers] Introduce a new concept of `manager` (#15339) Signed-off-by: Flavien DELANGLE Co-authored-by: Lukas Tyla --- .../useSingleInputDateRangeField.ts | 39 +++--- .../useSingleInputDateTimeRangeField.ts | 35 ++--- .../useSingleInputTimeRangeField.ts | 39 +++--- packages/x-date-pickers-pro/src/index.ts | 1 + .../useMultiInputDateRangeField.ts | 25 ++-- .../useMultiInputDateTimeRangeField.ts | 31 ++--- .../useMultiInputTimeRangeField.ts | 31 ++--- .../x-date-pickers-pro/src/managers/index.ts | 17 +++ .../src/managers/useDateRangeManager.ts | 76 ++++++++++ .../src/managers/useDateTimeRangeManager.ts | 79 +++++++++++ .../src/managers/useTimeRangeManager.ts | 77 ++++++++++ .../src/DateField/useDateField.ts | 33 ++--- .../src/DateTimeField/useDateTimeField.ts | 33 ++--- .../src/TimeField/useTimeField.ts | 33 ++--- packages/x-date-pickers/src/index.ts | 1 + .../internals/hooks/defaultizedFieldProps.ts | 90 ------------ .../src/internals/hooks/useField/index.ts | 2 +- .../src/internals/hooks/useField/useField.ts | 30 +++- .../x-date-pickers/src/internals/index.ts | 10 +- .../src/internals/models/index.ts | 1 + .../src/internals/models/manager.ts | 26 ++++ packages/x-date-pickers/src/managers/index.ts | 11 ++ .../src/managers/useDateManager.ts | 97 +++++++++++++ .../src/managers/useDateTimeManager.ts | 131 ++++++++++++++++++ .../src/managers/useTimeManager.ts | 98 +++++++++++++ packages/x-date-pickers/src/models/index.ts | 1 + packages/x-date-pickers/src/models/manager.ts | 92 ++++++++++++ scripts/x-date-pickers-pro.exports.json | 19 +++ scripts/x-date-pickers.exports.json | 10 ++ 29 files changed, 905 insertions(+), 263 deletions(-) create mode 100644 packages/x-date-pickers-pro/src/managers/index.ts create mode 100644 packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts create mode 100644 packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts create mode 100644 packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts delete mode 100644 packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts create mode 100644 packages/x-date-pickers/src/internals/models/manager.ts create mode 100644 packages/x-date-pickers/src/managers/index.ts create mode 100644 packages/x-date-pickers/src/managers/useDateManager.ts create mode 100644 packages/x-date-pickers/src/managers/useDateTimeManager.ts create mode 100644 packages/x-date-pickers/src/managers/useTimeManager.ts create mode 100644 packages/x-date-pickers/src/models/manager.ts diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts index e1b71090039e7..3148b6e40b37b 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts @@ -1,40 +1,37 @@ 'use client'; -import * as React from 'react'; -import { useField, useDefaultizedDateField, PickerRangeValue } from '@mui/x-date-pickers/internals'; +import { + useField, + useFieldInternalPropsWithDefaults, + PickerRangeValue, +} from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputDateRangeFieldProps } from './SingleInputDateRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateDateRange } from '../validation'; +import { useDateRangeManager } from '../managers'; export const useSingleInputDateRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputDateRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateField< - UseSingleInputDateRangeFieldProps, - TAllProps - >(inProps); - + const manager = useDateRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateDateRange, - valueType: 'date', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts index 1fbed22a25d09..12835555e9a57 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts @@ -1,44 +1,37 @@ 'use client'; -import * as React from 'react'; import { useField, - useDefaultizedDateTimeField, + useFieldInternalPropsWithDefaults, PickerRangeValue, } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputDateTimeRangeFieldProps } from './SingleInputDateTimeRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateDateTimeRange } from '../validation'; +import { useDateTimeRangeManager } from '../managers'; export const useSingleInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputDateTimeRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateTimeField< - UseSingleInputDateTimeRangeFieldProps, - TAllProps - >(inProps); - + const manager = useDateTimeRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date-time'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateDateTimeRange, - valueType: 'date-time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts index a55e35d42c97f..bfa482d68eedf 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts @@ -1,40 +1,37 @@ 'use client'; -import * as React from 'react'; -import { useField, useDefaultizedTimeField, PickerRangeValue } from '@mui/x-date-pickers/internals'; +import { + useField, + useFieldInternalPropsWithDefaults, + PickerRangeValue, +} from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputTimeRangeFieldProps } from './SingleInputTimeRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateTimeRange } from '../validation'; +import { useTimeRangeManager } from '../managers'; export const useSingleInputTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputTimeRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedTimeField< - UseSingleInputTimeRangeFieldProps, - TAllProps - >(inProps); - + const manager = useTimeRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'time'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateTimeRange, - valueType: 'time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers-pro/src/index.ts b/packages/x-date-pickers-pro/src/index.ts index 51211f5603742..64bee4c2ef0e7 100644 --- a/packages/x-date-pickers-pro/src/index.ts +++ b/packages/x-date-pickers-pro/src/index.ts @@ -30,3 +30,4 @@ export * from './dateRangeViewRenderers'; export * from './models'; export * from './hooks'; export * from './validation'; +export * from './managers'; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts index 081fbf140f0ee..d255c2e719c75 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts @@ -7,26 +7,24 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedDateField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; import { useValidation } from '@mui/x-date-pickers/validation'; import { DateValidationError } from '@mui/x-date-pickers/models'; -import { - UseMultiInputDateRangeFieldParams, - UseMultiInputDateRangeFieldProps, -} from '../../../MultiInputDateRangeField/MultiInputDateRangeField.types'; +import { UseMultiInputDateRangeFieldParams } from '../../../MultiInputDateRangeField/MultiInputDateRangeField.types'; import { validateDateRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; import { DateRangeValidationError } from '../../../models'; import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useDateRangeManager } from '../../../managers'; export const useMultiInputDateRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +33,11 @@ export const useMultiInputDateRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedDateField< - UseMultiInputDateRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useDateRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,7 +54,7 @@ export const useMultiInputDateRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ name: 'useMultiInputDateRangeField', @@ -68,11 +67,11 @@ export const useMultiInputDateRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, + props: sharedPropsWithDefaults, value, timezone, validator: validateDateRange, - onError: sharedProps.onError, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts index 3a006da7c913b..f4a13c557de6d 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts @@ -7,26 +7,24 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedDateTimeField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; -import { DateTimeValidationError } from '@mui/x-date-pickers/models'; import { useValidation } from '@mui/x-date-pickers/validation'; -import type { - UseMultiInputDateTimeRangeFieldParams, - UseMultiInputDateTimeRangeFieldProps, -} from '../../../MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types'; -import { DateTimeRangeValidationError } from '../../../models'; +import { DateTimeValidationError } from '@mui/x-date-pickers/models'; +import { UseMultiInputDateTimeRangeFieldParams } from '../../../MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types'; import { validateDateTimeRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; +import { DateTimeRangeValidationError } from '../../../models'; import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useDateTimeRangeManager } from '../../../managers'; export const useMultiInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +33,11 @@ export const useMultiInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedDateTimeField< - UseMultiInputDateTimeRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useDateTimeRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,10 +54,10 @@ export const useMultiInputDateTimeRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ - name: 'useMultiInputDateRangeField', + name: 'useMultiInputDateTimeRangeField', timezone: timezoneProp, value: valueProp, defaultValue, @@ -68,11 +67,11 @@ export const useMultiInputDateTimeRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, + props: sharedPropsWithDefaults, value, timezone, validator: validateDateTimeRange, - onError: sharedProps.onError, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts index 9f8b36291f5fe..ea881fb4349d3 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts @@ -7,26 +7,24 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedTimeField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; import { useValidation } from '@mui/x-date-pickers/validation'; import { TimeValidationError } from '@mui/x-date-pickers/models'; +import { UseMultiInputTimeRangeFieldParams } from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types'; import { validateTimeRange } from '../../../validation'; -import { TimeRangeValidationError } from '../../../models'; -import type { - UseMultiInputTimeRangeFieldParams, - UseMultiInputTimeRangeFieldProps, -} from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; +import { TimeRangeValidationError } from '../../../models'; import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useTimeRangeManager } from '../../../managers'; export const useMultiInputTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +33,11 @@ export const useMultiInputTimeRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedTimeField< - UseMultiInputTimeRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useTimeRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,10 +54,10 @@ export const useMultiInputTimeRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ - name: 'useMultiInputDateRangeField', + name: 'useMultiInputTimeRangeField', timezone: timezoneProp, value: valueProp, defaultValue, @@ -68,11 +67,11 @@ export const useMultiInputTimeRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, - validator: validateTimeRange, + props: sharedPropsWithDefaults, value, timezone, - onError: sharedProps.onError, + validator: validateTimeRange, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic diff --git a/packages/x-date-pickers-pro/src/managers/index.ts b/packages/x-date-pickers-pro/src/managers/index.ts new file mode 100644 index 0000000000000..07a939c8a1be5 --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/index.ts @@ -0,0 +1,17 @@ +export { useDateRangeManager } from './useDateRangeManager'; +export type { + UseDateRangeManagerReturnValue, + UseDateRangeManagerParameters, +} from './useDateRangeManager'; + +export { useTimeRangeManager } from './useTimeRangeManager'; +export type { + UseTimeRangeManagerReturnValue, + UseTimeRangeManagerParameters, +} from './useTimeRangeManager'; + +export { useDateTimeRangeManager } from './useDateTimeRangeManager'; +export type { + UseDateTimeRangeManagerReturnValue, + UseDateTimeRangeManagerParameters, +} from './useDateTimeRangeManager'; diff --git a/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts new file mode 100644 index 0000000000000..ed4785f5e9ead --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts @@ -0,0 +1,76 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + PickerRangeValue, + UseFieldInternalProps, + getDateFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { DateRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateDateRange } from '../validation'; +import { + ExportedValidateDateRangeProps, + ValidateDateRangeProps, +} from '../validation/validateDateRange'; + +export function useDateRangeManager( + parameters: UseDateRangeManagerParameters = {}, +): UseDateRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'date', + validator: validateDateRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateFieldInternalPropsDefaults({ defaultDates, utils, internalProps }), + }), + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseDateRangeManagerParameters + extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError, + DateRangeManagerFieldInternalProps, + DateRangeManagerFieldInternalPropsWithDefaults + >; + +interface DateRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError + >, + 'format' + >, + RangeFieldSeparatorProps, + ExportedValidateDateRangeProps {} + +interface DateRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError + >, + ValidateDateRangeProps, + RangeFieldSeparatorProps {} diff --git a/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts new file mode 100644 index 0000000000000..8e779c3230b8b --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts @@ -0,0 +1,79 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + AmPmProps, + PickerRangeValue, + UseFieldInternalProps, + getDateTimeFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { DateTimeRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateDateTimeRange } from '../validation'; +import { + ExportedValidateDateTimeRangeProps, + ValidateDateTimeRangeProps, +} from '../validation/validateDateTimeRange'; + +export function useDateTimeRangeManager( + parameters: UseDateTimeRangeManagerParameters = {}, +): UseDateTimeRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'date-time', + validator: validateDateTimeRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateTimeFieldInternalPropsDefaults({ internalProps, utils, defaultDates }), + }), + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseDateTimeRangeManagerParameters< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateTimeRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError, + DateTimeRangeManagerFieldInternalProps, + DateTimeRangeManagerFieldInternalPropsWithDefaults + >; + +interface DateTimeRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError + >, + 'format' + >, + ExportedValidateDateTimeRangeProps, + AmPmProps, + RangeFieldSeparatorProps {} + +interface DateTimeRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError + >, + ValidateDateTimeRangeProps, + RangeFieldSeparatorProps {} diff --git a/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts new file mode 100644 index 0000000000000..a17555bb6b5e9 --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts @@ -0,0 +1,77 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + AmPmProps, + PickerRangeValue, + UseFieldInternalProps, + getTimeFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { TimeRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateTimeRange } from '../validation'; +import { + ExportedValidateTimeRangeProps, + ValidateTimeRangeProps, +} from '../validation/validateTimeRange'; + +export function useTimeRangeManager( + parameters: UseTimeRangeManagerParameters = {}, +): UseTimeRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'time', + validator: validateTimeRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils }) => ({ + ...internalProps, + ...getTimeFieldInternalPropsDefaults({ utils, internalProps }), + }), + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseTimeRangeManagerParameters + extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseTimeRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError, + TimeRangeManagerFieldInternalProps, + TimeRangeManagerFieldInternalPropsWithDefaults + >; + +interface TimeRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError + >, + 'format' + >, + ExportedValidateTimeRangeProps, + AmPmProps, + RangeFieldSeparatorProps {} + +interface TimeRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError + >, + ValidateTimeRangeProps {} diff --git a/packages/x-date-pickers/src/DateField/useDateField.ts b/packages/x-date-pickers/src/DateField/useDateField.ts index 42d7b01fe3a82..49b1e6d8b57df 100644 --- a/packages/x-date-pickers/src/DateField/useDateField.ts +++ b/packages/x-date-pickers/src/DateField/useDateField.ts @@ -1,39 +1,34 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseDateFieldProps } from './DateField.types'; -import { validateDate } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedDateField } from '../internals/hooks/defaultizedFieldProps'; +import { useDateManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useDateField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseDateFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateField< - UseDateFieldProps, - TAllProps - >(inProps); - + const manager = useDateManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateDate, - valueType: 'date', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts index bd3b0b585352b..5943673b20c9b 100644 --- a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts +++ b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts @@ -1,39 +1,34 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseDateTimeFieldProps } from './DateTimeField.types'; -import { validateDateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedDateTimeField } from '../internals/hooks/defaultizedFieldProps'; +import { useDateTimeManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useDateTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseDateTimeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateTimeField< - UseDateTimeFieldProps, - TAllProps - >(inProps); - + const manager = useDateTimeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date-time'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateDateTime, - valueType: 'date-time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers/src/TimeField/useTimeField.ts b/packages/x-date-pickers/src/TimeField/useTimeField.ts index 51a88016a8dc3..5b6e155433d54 100644 --- a/packages/x-date-pickers/src/TimeField/useTimeField.ts +++ b/packages/x-date-pickers/src/TimeField/useTimeField.ts @@ -1,39 +1,34 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseTimeFieldProps } from './TimeField.types'; -import { validateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedTimeField } from '../internals/hooks/defaultizedFieldProps'; +import { useTimeManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseTimeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedTimeField< - UseTimeFieldProps, - TAllProps - >(inProps); - + const manager = useTimeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'time'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateTime, - valueType: 'time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, }); }; diff --git a/packages/x-date-pickers/src/index.ts b/packages/x-date-pickers/src/index.ts index f0987002adb1a..e5127dacbb266 100644 --- a/packages/x-date-pickers/src/index.ts +++ b/packages/x-date-pickers/src/index.ts @@ -56,3 +56,4 @@ export * from './models'; export * from './icons'; export * from './hooks'; export * from './validation'; +export * from './managers'; diff --git a/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts b/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts deleted file mode 100644 index 9a68e75919d41..0000000000000 --- a/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { DefaultizedProps } from '@mui/x-internals/types'; -import { applyDefaultDate } from '../utils/date-utils'; -import { useUtils, useDefaultDates } from './useUtils'; -import { - BaseDateValidationProps, - BaseTimeValidationProps, - DateTimeValidationProps, - TimeValidationProps, -} from '../models/validation'; - -export interface UseDefaultizedDateFieldBaseProps extends BaseDateValidationProps { - format?: string; -} - -export const useDefaultizedDateField = < - TKnownProps extends UseDefaultizedDateFieldBaseProps, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - const utils = useUtils(); - const defaultDates = useDefaultDates(); - - return { - ...props, - disablePast: props.disablePast ?? false, - disableFuture: props.disableFuture ?? false, - format: props.format ?? utils.formats.keyboardDate, - minDate: applyDefaultDate(utils, props.minDate, defaultDates.minDate), - maxDate: applyDefaultDate(utils, props.maxDate, defaultDates.maxDate), - }; -}; - -export interface UseDefaultizedTimeFieldBaseProps extends BaseTimeValidationProps { - format?: string; -} - -export const useDefaultizedTimeField = < - TKnownProps extends UseDefaultizedTimeFieldBaseProps & { ampm?: boolean }, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - 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, - }; -}; - -export interface UseDefaultizedDateTimeFieldBaseProps - extends BaseDateValidationProps, - BaseTimeValidationProps { - format?: string; -} - -export const useDefaultizedDateTimeField = < - TKnownProps extends UseDefaultizedDateTimeFieldBaseProps & - DateTimeValidationProps & - TimeValidationProps & { ampm?: boolean }, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - 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, - disableIgnoringDatePartForTimeValidation: Boolean(props.minDateTime || props.maxDateTime), - 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, - }; -}; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/index.ts b/packages/x-date-pickers/src/internals/hooks/useField/index.ts index af21ba83d5b90..1880c3dd20fd9 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/index.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/index.ts @@ -1,4 +1,4 @@ -export { useField } from './useField'; +export { useField, useFieldInternalPropsWithDefaults } from './useField'; export type { FieldValueManager, UseFieldInternalProps, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 8bec0c4e9afa6..bba441178e48c 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -3,7 +3,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import useEventCallback from '@mui/utils/useEventCallback'; import { useRtl } from '@mui/system/RtlProvider'; import { useValidation } from '../../../validation'; -import { useUtils } from '../useUtils'; +import { useLocalizationContext, useUtils } from '../useUtils'; import { UseFieldParams, UseFieldResponse, @@ -19,7 +19,33 @@ import { useFieldState } from './useFieldState'; import { useFieldCharacterEditing } from './useFieldCharacterEditing'; import { useFieldV7TextField } from './useFieldV7TextField'; import { useFieldV6TextField } from './useFieldV6TextField'; -import { PickerValidValue } from '../../models'; +import { + PickerValidValue, + PickerAnyManager, + PickerManagerFieldInternalProps, + PickerManagerFieldInternalPropsWithDefaults, +} from '../../models'; + +/** + * Applies the default values to the field internal props. + * This is a temporary hook that will be removed during a follow up when `useField` will receive the internal props without the defaults. + * It is only here to allow the migration to be done in smaller steps. + */ +export const useFieldInternalPropsWithDefaults = ({ + manager, + internalProps, +}: { + manager: TManager; + internalProps: PickerManagerFieldInternalProps; +}): PickerManagerFieldInternalPropsWithDefaults => { + const localizationContext = useLocalizationContext(); + return React.useMemo(() => { + return manager.internal_applyDefaultsToFieldInternalProps({ + ...localizationContext, + internalProps, + }); + }, [manager, internalProps, localizationContext]); +}; export const useField = < TValue extends PickerValidValue, diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 28aeb4799b72b..35676e0fc5984 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -53,6 +53,7 @@ export { useControlledValueWithTimezone } from './hooks/useValueWithTimezone'; export type { DesktopOnlyPickerProps } from './hooks/useDesktopPicker'; export { useField, + useFieldInternalPropsWithDefaults, createDateStrForV7HiddenInputFromSections, createDateStrForV6InputFromSections, } from './hooks/useField'; @@ -151,11 +152,6 @@ export { onSpaceOrEnter, DEFAULT_DESKTOP_MODE_MEDIA_QUERY, } from './utils/utils'; -export { - useDefaultizedDateField, - useDefaultizedTimeField, - useDefaultizedDateTimeField, -} from './hooks/defaultizedFieldProps'; export { useDefaultReduceAnimations } from './hooks/useDefaultReduceAnimations'; export { applyDefaultViewProps } from './utils/views'; @@ -173,3 +169,7 @@ export { useCalendarState } from '../DateCalendar/useCalendarState'; export { isInternalTimeView, isTimeView } from './utils/time-utils'; export { DateTimePickerToolbarOverrideContext } from '../DateTimePicker/DateTimePickerToolbar'; + +export { getDateFieldInternalPropsDefaults } from '../managers/useDateManager'; +export { getTimeFieldInternalPropsDefaults } from '../managers/useTimeManager'; +export { getDateTimeFieldInternalPropsDefaults } from '../managers/useDateTimeManager'; diff --git a/packages/x-date-pickers/src/internals/models/index.ts b/packages/x-date-pickers/src/internals/models/index.ts index 45ae41a75a2e3..fdaa9b0a839b0 100644 --- a/packages/x-date-pickers/src/internals/models/index.ts +++ b/packages/x-date-pickers/src/internals/models/index.ts @@ -2,3 +2,4 @@ export * from './fields'; export * from './common'; export * from './value'; export * from './formProps'; +export * from './manager'; diff --git a/packages/x-date-pickers/src/internals/models/manager.ts b/packages/x-date-pickers/src/internals/models/manager.ts new file mode 100644 index 0000000000000..178ab9acfa34e --- /dev/null +++ b/packages/x-date-pickers/src/internals/models/manager.ts @@ -0,0 +1,26 @@ +import type { PickerManager } from '../../models'; + +export type PickerAnyManager = PickerManager; + +type PickerManagerProperties = + TManager extends PickerManager< + infer TValue, + infer TEnableAccessibleFieldDOMStructure, + infer TError, + infer TFieldInternalProps, + infer TFieldInternalPropsWithDefaults + > + ? { + value: TValue; + enableAccessibleFieldDOMStructure: TEnableAccessibleFieldDOMStructure; + error: TError; + fieldInternalProps: TFieldInternalProps; + fieldInternalPropsWithDefaults: TFieldInternalPropsWithDefaults; + } + : never; + +export type PickerManagerFieldInternalProps = + PickerManagerProperties['fieldInternalProps']; + +export type PickerManagerFieldInternalPropsWithDefaults = + PickerManagerProperties['fieldInternalPropsWithDefaults']; diff --git a/packages/x-date-pickers/src/managers/index.ts b/packages/x-date-pickers/src/managers/index.ts new file mode 100644 index 0000000000000..085026d5225fa --- /dev/null +++ b/packages/x-date-pickers/src/managers/index.ts @@ -0,0 +1,11 @@ +export { useDateManager } from './useDateManager'; +export type { UseDateManagerReturnValue, UseDateManagerParameters } from './useDateManager'; + +export { useTimeManager } from './useTimeManager'; +export type { UseTimeManagerReturnValue, UseTimeManagerParameters } from './useTimeManager'; + +export { useDateTimeManager } from './useDateTimeManager'; +export type { + UseDateTimeManagerReturnValue, + UseDateTimeManagerParameters, +} from './useDateTimeManager'; diff --git a/packages/x-date-pickers/src/managers/useDateManager.ts b/packages/x-date-pickers/src/managers/useDateManager.ts new file mode 100644 index 0000000000000..aec21fd0379bf --- /dev/null +++ b/packages/x-date-pickers/src/managers/useDateManager.ts @@ -0,0 +1,97 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { applyDefaultDate } from '../internals/utils/date-utils'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, DateValidationError } from '../models'; +import { validateDate } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { + ExportedValidateDateProps, + ValidateDatePropsToDefault, + ValidateDateProps, +} from '../validation/validateDate'; +import { PickerValue } from '../internals/models'; + +export function useDateManager( + parameters: UseDateManagerParameters = {}, +): UseDateManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'date', + validator: validateDate, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateFieldInternalPropsDefaults({ defaultDates, utils, internalProps }), + }), + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the fields with date editing. + * Is used by the `useDateManager` and `useDateRangeManager` hooks. + */ +export function getDateFieldInternalPropsDefaults( + parameters: GetDateFieldInternalPropsDefaultsParameters, +): GetDateFieldInternalPropsDefaultsReturnValue { + const { defaultDates, utils, internalProps } = parameters; + + return { + format: internalProps.format ?? utils.formats.keyboardDate, + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + minDate: applyDefaultDate(utils, internalProps.minDate, defaultDates.minDate), + maxDate: applyDefaultDate(utils, internalProps.maxDate, defaultDates.maxDate), + }; +} + +export interface UseDateManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateValidationError, + DateManagerFieldInternalProps, + DateManagerFieldInternalPropsWithDefaults + >; + +interface DateManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps, + 'format' + >, + ExportedValidateDateProps {} + +interface DateManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateValidationError + >, + ValidateDateProps {} + +type DateManagerFieldPropsToDefault = 'format' | ValidateDatePropsToDefault; + +interface GetDateFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick, DateManagerFieldPropsToDefault>; +} + +interface GetDateFieldInternalPropsDefaultsReturnValue + extends Pick, DateManagerFieldPropsToDefault> {} diff --git a/packages/x-date-pickers/src/managers/useDateTimeManager.ts b/packages/x-date-pickers/src/managers/useDateTimeManager.ts new file mode 100644 index 0000000000000..965333a10b704 --- /dev/null +++ b/packages/x-date-pickers/src/managers/useDateTimeManager.ts @@ -0,0 +1,131 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { applyDefaultDate } from '../internals/utils/date-utils'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, DateTimeValidationError } from '../models'; +import { validateDateTime } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { AmPmProps } from '../internals/models/props/time'; +import { + ExportedValidateDateTimeProps, + ValidateDateTimeProps, + ValidateDateTimePropsToDefault, +} from '../validation/validateDateTime'; +import { PickerValue } from '../internals/models'; + +export function useDateTimeManager( + parameters: UseDateTimeManagerParameters = {}, +): UseDateTimeManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'date-time', + validator: validateDateTime, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateTimeFieldInternalPropsDefaults({ internalProps, utils, defaultDates }), + }), + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the field with date time editing. + * Is used by the `useDateTimeManager` and `useDateTimeRangeManager` hooks. + */ +export function getDateTimeFieldInternalPropsDefaults( + parameters: GetDateTimeFieldInternalPropsDefaultsParameters, +): GetDateTimeFieldInternalPropsDefaultsReturnValue { + const { defaultDates, utils, internalProps } = parameters; + const ampm = internalProps.ampm ?? utils.is12HourCycleInCurrentLocale(); + const defaultFormat = ampm + ? utils.formats.keyboardDateTime12h + : utils.formats.keyboardDateTime24h; + + return { + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + format: internalProps.format ?? defaultFormat, + disableIgnoringDatePartForTimeValidation: Boolean( + internalProps.minDateTime || internalProps.maxDateTime, + ), + minDate: applyDefaultDate( + utils, + internalProps.minDateTime ?? internalProps.minDate, + defaultDates.minDate, + ), + maxDate: applyDefaultDate( + utils, + internalProps.maxDateTime ?? internalProps.maxDate, + defaultDates.maxDate, + ), + minTime: internalProps.minDateTime ?? internalProps.minTime, + maxTime: internalProps.maxDateTime ?? internalProps.maxTime, + }; +} + +export interface UseDateTimeManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateTimeManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError, + DateTimeManagerFieldInternalProps, + DateTimeManagerFieldInternalPropsWithDefaults + >; + +interface DateTimeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError + >, + 'format' + >, + ExportedValidateDateTimeProps, + AmPmProps {} + +interface DateTimeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError + >, + ValidateDateTimeProps {} + +type DateTimeManagerFieldPropsToDefault = + | 'format' + // minTime and maxTime can still be undefined after applying defaults. + | 'minTime' + | 'maxTime' + | ValidateDateTimePropsToDefault; + +interface GetDateTimeFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick< + DateTimeManagerFieldInternalProps, + DateTimeManagerFieldPropsToDefault | 'minDateTime' | 'maxDateTime' | 'ampm' + >; +} + +interface GetDateTimeFieldInternalPropsDefaultsReturnValue + extends Pick< + DateTimeManagerFieldInternalPropsWithDefaults, + DateTimeManagerFieldPropsToDefault | 'disableIgnoringDatePartForTimeValidation' + > {} diff --git a/packages/x-date-pickers/src/managers/useTimeManager.ts b/packages/x-date-pickers/src/managers/useTimeManager.ts new file mode 100644 index 0000000000000..cfbccbabbfe33 --- /dev/null +++ b/packages/x-date-pickers/src/managers/useTimeManager.ts @@ -0,0 +1,98 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, TimeValidationError } from '../models'; +import { validateTime } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { AmPmProps } from '../internals/models/props/time'; +import { + ExportedValidateTimeProps, + ValidateTimeProps, + ValidateTimePropsToDefault, +} from '../validation/validateTime'; +import { PickerValue } from '../internals/models'; + +export function useTimeManager( + parameters: UseTimeManagerParameters = {}, +): UseTimeManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'time', + validator: validateTime, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils }) => ({ + ...internalProps, + ...getTimeFieldInternalPropsDefaults({ utils, internalProps }), + }), + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the fields with time editing. + * Is used by the `useTimeManager` and `useTimeRangeManager` hooks. + */ +export function getTimeFieldInternalPropsDefaults( + parameters: GetTimeFieldInternalPropsDefaultsParameters, +): GetTimeFieldInternalPropsDefaultsReturnValue { + const { utils, internalProps } = parameters; + const ampm = internalProps.ampm ?? utils.is12HourCycleInCurrentLocale(); + const defaultFormat = ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h; + + return { + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + format: internalProps.format ?? defaultFormat, + }; +} + +export interface UseTimeManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseTimeManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + TimeValidationError, + TimeManagerFieldInternalProps, + TimeManagerFieldInternalPropsWithDefaults + >; + +interface TimeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps, + 'format' + >, + ExportedValidateTimeProps, + AmPmProps {} + +interface TimeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + TimeValidationError + >, + ValidateTimeProps {} + +type TimeManagerFieldPropsToDefault = 'format' | ValidateTimePropsToDefault; + +interface GetTimeFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick, TimeManagerFieldPropsToDefault | 'ampm'>; +} + +interface GetTimeFieldInternalPropsDefaultsReturnValue + extends Pick, TimeManagerFieldPropsToDefault> {} diff --git a/packages/x-date-pickers/src/models/index.ts b/packages/x-date-pickers/src/models/index.ts index 06374a0067505..ca1c963e4520e 100644 --- a/packages/x-date-pickers/src/models/index.ts +++ b/packages/x-date-pickers/src/models/index.ts @@ -5,6 +5,7 @@ export * from './views'; export * from './adapters'; export * from './common'; export * from './pickers'; +export * from './manager'; // Utils shared across the X packages export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-date-pickers/src/models/manager.ts b/packages/x-date-pickers/src/models/manager.ts new file mode 100644 index 0000000000000..dd2346f71e953 --- /dev/null +++ b/packages/x-date-pickers/src/models/manager.ts @@ -0,0 +1,92 @@ +import type { FieldValueManager, UseFieldInternalProps } from '../internals/hooks/useField'; +import type { PickerValueManager } from '../internals/hooks/usePicker'; +import type { PickerValidValue } from '../internals/models'; +import type { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import type { Validator } from '../validation'; +import type { PickerValueType } from './common'; + +/** + * Object that contains all the necessary methods and properties to adapter a picker or a field for a given value type. + * You should never create your own manager. + * Instead, use the hooks exported from '@mui/x-date-pickers/managers' and '@mui/x-date-pickers-pro/managers'. + * + * ```tsx + * import { useDateManager } from '@mui/x-date-pickers/managers'; + * import { useValidation } from '@mui/x-date-pickers/validation'; + * + * const manager = useDateManager(); + * const { hasValidationError } = useValidation({ + * validator: manager.validator, + * value, + * timezone, + * props, + * }); + * ``` + */ +export interface PickerManager< + TValue extends PickerValidValue, + TEnableAccessibleFieldDOMStructure extends boolean, + TError, + TFieldInternalProps extends {}, + TFieldInternalPropsWithDefaults extends UseFieldInternalProps< + TValue, + TEnableAccessibleFieldDOMStructure, + TError + >, +> { + /** + * The type of the value (e.g. 'date', 'date-time', 'time'). + */ + valueType: PickerValueType; + /** + * Checks if a value is valid and returns an error code otherwise. + * It can be passed to the `useValidation` hook to validate a value: + * + * ```tsx + * import { useDateManager } from '@mui/x-date-pickers/managers'; + * import { useValidation } from '@mui/x-date-pickers/validation'; + * + * const manager = useDateManager(); + * const { hasValidationError } = useValidation({ + * validator: manager.validator, + * value, + * timezone, + * props, + * }); + * ``` + */ + validator: Validator; + /** + * Object containing basic methods to interact with the value of the picker or field. + * This property is not part of the public API and should not be used directly. + */ + internal_valueManager: PickerValueManager; + /** + * Object containing all the necessary methods to interact with the value of the field. + * This property is not part of the public API and should not be used directly. + */ + internal_fieldValueManager: FieldValueManager; + /** + * `true` if the field is using the accessible DOM structure. + * `false` if the field is using the non-accessible DOM structure. + * This property is not part of the public API and should not be used directly. + */ + internal_enableAccessibleFieldDOMStructure: TEnableAccessibleFieldDOMStructure; + /** + * Applies the default values to the field internal props. + * This usually includes: + * - a default format to display the value in the field + * - some default validation props that are needed to validate the value (e.g: minDate, maxDate) + * This property is not part of the public API and should not be used directly. + * @param {ApplyDefaultsToFieldInternalPropsParameters} parameters The parameters to apply the defaults. + * @returns {TFieldInternalPropsWithDefaults} The field internal props with the defaults applied. + */ + internal_applyDefaultsToFieldInternalProps: ( + parameters: ApplyDefaultsToFieldInternalPropsParameters, + ) => TFieldInternalPropsWithDefaults; +} + +interface ApplyDefaultsToFieldInternalPropsParameters + extends MuiPickersAdapterContextValue { + internalProps: TFieldInternalProps; +} diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index a58b7384bd29a..7ff9be95cd478 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -249,6 +249,7 @@ { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, + { "name": "PickerManager", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickerRangeFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickersActionBar", "kind": "Function" }, @@ -405,8 +406,20 @@ { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, { "name": "UseDateFieldProps", "kind": "Interface" }, + { "name": "useDateManager", "kind": "Function" }, + { "name": "UseDateManagerParameters", "kind": "Interface" }, + { "name": "UseDateManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateRangeFieldProps", "kind": "Interface" }, + { "name": "useDateRangeManager", "kind": "Function" }, + { "name": "UseDateRangeManagerParameters", "kind": "Interface" }, + { "name": "UseDateRangeManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, + { "name": "useDateTimeManager", "kind": "Function" }, + { "name": "UseDateTimeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useDateTimeRangeManager", "kind": "Function" }, + { "name": "UseDateTimeRangeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeRangeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useIsValidValue", "kind": "Function" }, { "name": "UseMultiInputDateRangeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseMultiInputDateRangeFieldProps", "kind": "Interface" }, @@ -425,6 +438,12 @@ { "name": "UseSingleInputTimeRangeFieldProps", "kind": "Interface" }, { "name": "useSplitFieldProps", "kind": "Variable" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, + { "name": "useTimeManager", "kind": "Function" }, + { "name": "UseTimeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useTimeRangeManager", "kind": "Function" }, + { "name": "UseTimeRangeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeRangeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, { "name": "ValidateDateProps", "kind": "Interface" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index 86fcd590a5182..270eb6400692e 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -165,6 +165,7 @@ { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, + { "name": "PickerManager", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickersActionBar", "kind": "Function" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" }, @@ -298,7 +299,13 @@ { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, { "name": "UseDateFieldProps", "kind": "Interface" }, + { "name": "useDateManager", "kind": "Function" }, + { "name": "UseDateManagerParameters", "kind": "Interface" }, + { "name": "UseDateManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, + { "name": "useDateTimeManager", "kind": "Function" }, + { "name": "UseDateTimeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useIsValidValue", "kind": "Function" }, { "name": "useParsedFormat", "kind": "Variable" }, { "name": "usePickerActionsContext", "kind": "Variable" }, @@ -307,6 +314,9 @@ { "name": "usePickerTranslations", "kind": "Variable" }, { "name": "useSplitFieldProps", "kind": "Variable" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, + { "name": "useTimeManager", "kind": "Function" }, + { "name": "UseTimeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, { "name": "ValidateDateProps", "kind": "Interface" },