From b1a8dd4d12858198277d30fe698689d85e64e045 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 7 Oct 2024 14:31:25 +0200 Subject: [PATCH] [docs] New recipe of a read-only field (#14606) Signed-off-by: Flavien DELANGLE Co-authored-by: Lukas Tyla --- .../ReadOnlyMaterialTextField.js | 67 +++++++++++++++++ .../ReadOnlyMaterialTextField.tsx | 71 +++++++++++++++++++ .../ReadOnlyMaterialTextField.tsx.preview | 1 + .../date-pickers/custom-field/custom-field.md | 11 ++- .../experimentation/CustomField.js | 47 ------------ .../experimentation/CustomField.tsx | 47 ------------ .../experimentation/CustomField.tsx.preview | 6 -- .../experimentation/experimentation.md | 4 -- .../useDesktopRangePicker.tsx | 7 +- .../useMobileRangePicker.tsx | 7 +- packages/x-date-pickers/src/hooks/index.tsx | 4 ++ .../src/hooks/useParsedFormat.ts | 61 ++++++++++++++++ .../src/hooks/usePickersContext.ts | 19 +++++ .../internals/components/PickersProvider.tsx | 48 +++++++++++++ .../useDesktopPicker/useDesktopPicker.tsx | 7 +- .../hooks/useField/buildSectionsFromFormat.ts | 10 +-- .../hooks/useField/useField.utils.ts | 23 +++--- .../useField/useFieldCharacterEditing.ts | 3 +- .../internals/hooks/useField/useFieldState.ts | 6 +- .../hooks/useMobilePicker/useMobilePicker.tsx | 7 +- .../internals/hooks/usePicker/usePicker.ts | 3 + .../hooks/usePicker/usePickerValue.ts | 11 +++ .../hooks/usePicker/usePickerValue.types.ts | 2 + .../x-date-pickers/src/internals/index.ts | 1 + scripts/x-date-pickers-pro.exports.json | 2 + scripts/x-date-pickers.exports.json | 2 + 26 files changed, 331 insertions(+), 146 deletions(-) create mode 100644 docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js create mode 100644 docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx create mode 100644 docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview delete mode 100644 docs/data/date-pickers/experimentation/CustomField.js delete mode 100644 docs/data/date-pickers/experimentation/CustomField.tsx delete mode 100644 docs/data/date-pickers/experimentation/CustomField.tsx.preview create mode 100644 packages/x-date-pickers/src/hooks/useParsedFormat.ts create mode 100644 packages/x-date-pickers/src/hooks/usePickersContext.ts create mode 100644 packages/x-date-pickers/src/internals/components/PickersProvider.tsx diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js new file mode 100644 index 000000000000..b5050747ff1f --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.js @@ -0,0 +1,67 @@ +import * as React from 'react'; + +import TextField from '@mui/material/TextField'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickersContext, +} from '@mui/x-date-pickers/hooks'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; + +function ReadOnlyDateField(props) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { InputProps, slotProps, slots, ...other } = forwardedProps; + + const pickersContext = usePickersContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event) => { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + return ( + , + sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, + }} + error={hasValidationError} + onClick={handleTogglePicker} + /> + ); +} + +function ReadOnlyFieldDatePicker(props) { + return ( + + ); +} + +export default function ReadOnlyMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx new file mode 100644 index 000000000000..c9cdc16eae4e --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import TextField from '@mui/material/TextField'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DatePicker, + DatePickerProps, + DatePickerFieldProps, +} from '@mui/x-date-pickers/DatePicker'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; +import { + useSplitFieldProps, + useParsedFormat, + usePickersContext, +} from '@mui/x-date-pickers/hooks'; +import { CalendarIcon } from '@mui/x-date-pickers/icons'; + +function ReadOnlyDateField(props: DatePickerFieldProps) { + const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); + const { value, timezone, format } = internalProps; + const { InputProps, slotProps, slots, ...other } = forwardedProps; + + const pickersContext = usePickersContext(); + + const parsedFormat = useParsedFormat(internalProps); + const { hasValidationError } = useValidation({ + validator: validateDate, + value, + timezone, + props: internalProps, + }); + + const handleTogglePicker = (event: React.UIEvent) => { + if (pickersContext.open) { + pickersContext.onClose(event); + } else { + pickersContext.onOpen(event); + } + }; + + return ( + , + sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, + }} + error={hasValidationError} + onClick={handleTogglePicker} + /> + ); +} + +function ReadOnlyFieldDatePicker(props: DatePickerProps) { + return ( + + ); +} + +export default function ReadOnlyMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview new file mode 100644 index 000000000000..e3842a12cb5d --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index b58ea51d833f..4f5b5f782fb3 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -158,10 +158,17 @@ you can replace the field with an `Autocomplete` listing those dates: {{"demo": "PickerWithAutocompleteField.js", "defaultCodeOpen": false}} +### Using a read-only `TextField` + +If you want users to select a value exclusively through the views +but you still want the UI to look like a `TextField`, you can replace the field with a read-only `TextField`: + +{{"demo": "custom-behavior/ReadOnlyMaterialTextField.js", "defaultCodeOpen": false}} + ### Using a `Button` -If you only want to allow the user to pick a value through the views, -you can replace the field with a `Button`: +If you want users to select a value exclusively through the views +and you don't want the UI to look like a `TextField`, you can replace the field with a `Button`: {{"demo": "PickerWithButtonField.js", "defaultCodeOpen": false}} diff --git a/docs/data/date-pickers/experimentation/CustomField.js b/docs/data/date-pickers/experimentation/CustomField.js deleted file mode 100644 index 3a093244e39a..000000000000 --- a/docs/data/date-pickers/experimentation/CustomField.js +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react'; -import dayjs from 'dayjs'; -import TextField from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; -import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; - -function ReadOnlyField(props) { - const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - - const { value, timezone, format } = internalProps; - const { InputProps, slotProps, slots, ...other } = forwardedProps; - - const { hasValidationError } = useValidation({ - validator: validateDate, - value, - timezone, - props: internalProps, - }); - - return ( - - ); -} - -export default function CustomField() { - return ( - - - - - - ); -} diff --git a/docs/data/date-pickers/experimentation/CustomField.tsx b/docs/data/date-pickers/experimentation/CustomField.tsx deleted file mode 100644 index b1c10ad6b095..000000000000 --- a/docs/data/date-pickers/experimentation/CustomField.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react'; -import dayjs, { Dayjs } from 'dayjs'; -import TextField from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker, DatePickerFieldProps } from '@mui/x-date-pickers/DatePicker'; -import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; -import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; - -function ReadOnlyField(props: DatePickerFieldProps) { - const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - - const { value, timezone, format } = internalProps; - const { InputProps, slotProps, slots, ...other } = forwardedProps; - - const { hasValidationError } = useValidation({ - validator: validateDate, - value, - timezone, - props: internalProps, - }); - - return ( - - ); -} - -export default function CustomField() { - return ( - - - - - - ); -} diff --git a/docs/data/date-pickers/experimentation/CustomField.tsx.preview b/docs/data/date-pickers/experimentation/CustomField.tsx.preview deleted file mode 100644 index b489d70b2175..000000000000 --- a/docs/data/date-pickers/experimentation/CustomField.tsx.preview +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/experimentation/experimentation.md b/docs/data/date-pickers/experimentation/experimentation.md index d5f906e32868..88b3ea3fe5ad 100644 --- a/docs/data/date-pickers/experimentation/experimentation.md +++ b/docs/data/date-pickers/experimentation/experimentation.md @@ -5,7 +5,3 @@ productId: x-date-pickers # Date and Time Pickers experimentation

Demos not accessible through the navbar of the doc

- -## Custom field - -{{"demo": "CustomField.js"}} diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx index 412b616b3781..297f44b9fb72 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import { useLicenseVerifier } from '@mui/x-license'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { PickersLayout, PickersLayoutSlotProps } from '@mui/x-date-pickers/PickersLayout'; import { executeInTheNextEventLoopTick, @@ -11,6 +10,7 @@ import { ExportedBaseToolbarProps, DateOrTimeViewWithMeridiem, ExportedBaseTabsProps, + PickersProvider, } from '@mui/x-date-pickers/internals'; import { PickerValidDate, FieldRef, InferError } from '@mui/x-date-pickers/models'; import { @@ -93,6 +93,7 @@ export const useDesktopRangePicker = < renderCurrentView, shouldRestoreFocus, fieldProps: pickerFieldProps, + contextValue, } = usePicker< DateRange, TDate, @@ -209,7 +210,7 @@ export const useDesktopRangePicker = < const Layout = slots?.layout ?? PickersLayout; const renderPicker = () => ( - + - + ); return { diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx index 58f509d3c8aa..c17afc51b900 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import { useLicenseVerifier } from '@mui/x-license'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { PickersLayout, PickersLayoutSlotProps } from '@mui/x-date-pickers/PickersLayout'; import { usePicker, @@ -9,6 +8,7 @@ import { ExportedBaseToolbarProps, DateOrTimeViewWithMeridiem, ExportedBaseTabsProps, + PickersProvider, } from '@mui/x-date-pickers/internals'; import { usePickersTranslations } from '@mui/x-date-pickers/hooks'; import { PickerValidDate, FieldRef, InferError } from '@mui/x-date-pickers/models'; @@ -88,6 +88,7 @@ export const useMobileRangePicker = < layoutProps, renderCurrentView, fieldProps: pickerFieldProps, + contextValue, } = usePicker< DateRange, TDate, @@ -214,7 +215,7 @@ export const useMobileRangePicker = < }; const renderPicker = () => ( - + - + ); return { diff --git a/packages/x-date-pickers/src/hooks/index.tsx b/packages/x-date-pickers/src/hooks/index.tsx index e4ca216906f0..a3d200fd8757 100644 --- a/packages/x-date-pickers/src/hooks/index.tsx +++ b/packages/x-date-pickers/src/hooks/index.tsx @@ -9,3 +9,7 @@ export type { export { usePickersTranslations } from './usePickersTranslations'; export { useSplitFieldProps } from './useSplitFieldProps'; + +export { useParsedFormat } from './useParsedFormat'; + +export { usePickersContext } from './usePickersContext'; diff --git a/packages/x-date-pickers/src/hooks/useParsedFormat.ts b/packages/x-date-pickers/src/hooks/useParsedFormat.ts new file mode 100644 index 000000000000..c5e901ee9c7b --- /dev/null +++ b/packages/x-date-pickers/src/hooks/useParsedFormat.ts @@ -0,0 +1,61 @@ +'use client'; +import * as React from 'react'; +import { useRtl } from '@mui/system/RtlProvider'; +import { useUtils } from '../internals/hooks/useUtils'; +import { buildSectionsFromFormat } from '../internals/hooks/useField/buildSectionsFromFormat'; +import { getLocalizedDigits } from '../internals/hooks/useField/useField.utils'; +import { PickerValidDate } from '../models'; +import { usePickersTranslations } from './usePickersTranslations'; +import type { UseFieldInternalProps } from '../internals/hooks/useField'; + +interface UseParsedFormatParameters + extends Pick< + UseFieldInternalProps, + 'format' | 'formatDensity' | 'shouldRespectLeadingZeros' + > {} + +/** + * Returns the parsed format to be rendered in the field when there is no value or in other parts of the Picker. + * This format is localized (e.g: `AAAA` for the year with the French locale) and cannot be parsed by your date library. + * @param {object} The parameters needed to build the placeholder. + * @param {string} params.format Format of the date to use. + * @param {'dense' | 'spacious'} params.formatDensity Density of the format (setting `formatDensity` to `"spacious"` will add a space before and after each `/`, `-` and `.` character). + * @param {boolean} params.shouldRespectLeadingZeros If `true`, the format will respect the leading zeroes, if `false`, the format will always add leading zeroes. + * @returns + */ +export const useParsedFormat = ( + parameters: UseParsedFormatParameters, +) => { + const { format, formatDensity = 'dense', shouldRespectLeadingZeros = false } = parameters; + const utils = useUtils(); + const isRtl = useRtl(); + const translations = usePickersTranslations(); + const localizedDigits = React.useMemo(() => getLocalizedDigits(utils), [utils]); + + return React.useMemo(() => { + const sections = buildSectionsFromFormat({ + utils, + format, + formatDensity, + isRtl, + shouldRespectLeadingZeros, + localeText: translations, + localizedDigits, + date: null, + // TODO v9: Make sure we still don't reverse in `buildSectionsFromFormat` when using `useParsedFormat`. + enableAccessibleFieldDOMStructure: false, + }); + + return sections + .map((section) => `${section.startSeparator}${section.placeholder}${section.endSeparator}`) + .join(''); + }, [ + utils, + isRtl, + translations, + localizedDigits, + format, + formatDensity, + shouldRespectLeadingZeros, + ]); +}; diff --git a/packages/x-date-pickers/src/hooks/usePickersContext.ts b/packages/x-date-pickers/src/hooks/usePickersContext.ts new file mode 100644 index 000000000000..1303af42b886 --- /dev/null +++ b/packages/x-date-pickers/src/hooks/usePickersContext.ts @@ -0,0 +1,19 @@ +'use client'; +import * as React from 'react'; +import { PickersContext } from '../internals/components/PickersProvider'; + +/** + * Returns the context passed by the picker that wraps the current component. + */ +export const usePickersContext = () => { + const value = React.useContext(PickersContext); + if (value == null) { + throw new Error( + [ + 'MUI X: The `usePickersContext` can only be called in fields that are used as a slot of a picker component', + ].join('\n'), + ); + } + + return value; +}; diff --git a/packages/x-date-pickers/src/internals/components/PickersProvider.tsx b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx new file mode 100644 index 000000000000..8dd0654a846c --- /dev/null +++ b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { PickerValidDate } from '../../models'; +import { PickersInputLocaleText } from '../../locales'; +import { LocalizationProvider } from '../../LocalizationProvider'; + +export const PickersContext = React.createContext(null); + +/** + * Provides the context for the various parts of a picker component: + * - contextValue: the context for the picker sub-components. + * - localizationProvider: the translations passed through the props and through a parent LocalizationProvider. + * + * @ignore - do not document. + */ +export function PickersProvider( + props: PickersFieldProviderProps, +) { + const { contextValue, localeText, children } = props; + + return ( + + {children} + + ); +} + +interface PickersFieldProviderProps { + contextValue: PickersContextValue; + localeText: PickersInputLocaleText | undefined; + children: React.ReactNode; +} + +export interface PickersContextValue { + /** + * Open the picker. + * @param {React.UIEvent} event The DOM event that triggered the change. + */ + onOpen: (event: React.UIEvent) => void; + /** + * Close the picker. + * @param {React.UIEvent} event The DOM event that triggered the change. + */ + onClose: (event: React.UIEvent) => void; + /** + * `true` if the picker is open, `false` otherwise. + */ + open: boolean; +} 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 b13a78700e27..bd77cbc4d0e9 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -12,7 +12,6 @@ import { UseDesktopPickerSlotProps, } from './useDesktopPicker.types'; import { usePicker } from '../usePicker'; -import { LocalizationProvider } from '../../../LocalizationProvider'; import { PickersLayout } from '../../../PickersLayout'; import { FieldSection, @@ -22,6 +21,7 @@ import { InferError, } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; +import { PickersProvider } from '../../components/PickersProvider'; /** * Hook managing all the single-date desktop pickers: @@ -80,6 +80,7 @@ export const useDesktopPicker = < renderCurrentView, shouldRestoreFocus, fieldProps: pickerFieldProps, + contextValue, } = usePicker({ ...pickerParams, props, @@ -212,7 +213,7 @@ export const useDesktopPicker = < const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( - + - + ); return { renderPicker }; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts b/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts index 40cf9cdb88dd..d811f4d649a1 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts @@ -1,4 +1,4 @@ -import { FieldSection, MuiPickersAdapter, PickersTimezone, PickerValidDate } from '../../../models'; +import { FieldSection, MuiPickersAdapter, PickerValidDate } from '../../../models'; import { PickersLocaleText } from '../../../locales'; import { applyLocalizedDigits, @@ -13,7 +13,6 @@ interface BuildSectionsFromFormatParams { format: string; formatDensity: 'dense' | 'spacious'; isRtl: boolean; - timezone: PickersTimezone; shouldRespectLeadingZeros: boolean; localeText: PickersLocaleText; localizedDigits: string[]; @@ -64,7 +63,6 @@ const getEscapedPartsFromFormat = ({ const getSectionPlaceholder = ( utils: MuiPickersAdapter, - timezone: PickersTimezone, localeText: PickersLocaleText, sectionConfig: Pick, sectionFormat: string, @@ -72,7 +70,7 @@ const getSectionPlaceholder = ( switch (sectionConfig.type) { case 'year': { return localeText.fieldYearPlaceholder({ - digitAmount: utils.formatByString(utils.date(undefined, timezone), sectionFormat).length, + digitAmount: utils.formatByString(utils.date(undefined, 'default'), sectionFormat).length, format: sectionFormat, }); } @@ -119,7 +117,6 @@ const getSectionPlaceholder = ( const createSection = ({ utils, - timezone, date, shouldRespectLeadingZeros, localeText, @@ -140,7 +137,6 @@ const createSection = ({ const hasLeadingZerosInFormat = doesSectionFormatHaveLeadingZeros( utils, - timezone, sectionConfig.contentType, sectionConfig.type, token, @@ -181,7 +177,7 @@ const createSection = ({ format: token, maxLength, value: sectionValue, - placeholder: getSectionPlaceholder(utils, timezone, localeText, sectionConfig, token), + placeholder: getSectionPlaceholder(utils, localeText, sectionConfig, token), hasLeadingZerosInFormat, hasLeadingZerosInInput, startSeparator, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 71b5465dce55..e56f7029886f 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -65,12 +65,11 @@ const getDeltaFromKeyCode = (keyCode: Omit( utils: MuiPickersAdapter, - timezone: PickersTimezone, format: string, ) => { const elements: TDate[] = []; - const now = utils.date(undefined, timezone); + const now = utils.date(undefined, 'default'); const startDate = utils.startOfWeek(now); const endDate = utils.endOfWeek(now); @@ -97,7 +96,7 @@ export const getLetterEditingOptions = ( } case 'weekDay': { - return getDaysInWeekStr(utils, timezone, format); + return getDaysInWeekStr(utils, format); } case 'meridiem': { @@ -393,13 +392,11 @@ export const changeSectionValueFormat = ( const isFourDigitYearFormat = ( utils: MuiPickersAdapter, - timezone: PickersTimezone, format: string, -) => utils.formatByString(utils.date(undefined, timezone), format).length === 4; +) => utils.formatByString(utils.date(undefined, 'system'), format).length === 4; export const doesSectionFormatHaveLeadingZeros = ( utils: MuiPickersAdapter, - timezone: PickersTimezone, contentType: FieldSectionContentType, sectionType: FieldSectionType, format: string, @@ -408,12 +405,12 @@ export const doesSectionFormatHaveLeadingZeros = return false; } - const now = utils.date(undefined, timezone); + const now = utils.date(undefined, 'default'); switch (sectionType) { // We can't use `changeSectionValueFormat`, because `utils.parse('1', 'YYYY')` returns `1971` instead of `1`. case 'year': { - if (isFourDigitYearFormat(utils, timezone, format)) { + if (isFourDigitYearFormat(utils, format)) { const formatted0001 = utils.formatByString(utils.setYear(now, 1), format); return formatted0001 === '0001'; } @@ -547,7 +544,7 @@ export const getSectionsBoundaries = ( return { year: ({ format }) => ({ minimum: 0, - maximum: isFourDigitYearFormat(utils, timezone, format) ? 9999 : 99, + maximum: isFourDigitYearFormat(utils, format) ? 9999 : 99, }), month: () => ({ minimum: 1, @@ -564,7 +561,7 @@ export const getSectionsBoundaries = ( }), weekDay: ({ format, contentType }) => { if (contentType === 'digit') { - const daysInWeek = getDaysInWeekStr(utils, timezone, format).map(Number); + const daysInWeek = getDaysInWeekStr(utils, format).map(Number); return { minimum: Math.min(...daysInWeek), maximum: Math.max(...daysInWeek), @@ -653,7 +650,6 @@ export const validateSections = ( const transferDateSectionValue = ( utils: MuiPickersAdapter, - timezone: PickersTimezone, section: FieldSection, dateToTransferFrom: TDate, dateToTransferTo: TDate, @@ -668,7 +664,7 @@ const transferDateSectionValue = ( } case 'weekDay': { - const formattedDaysInWeek = getDaysInWeekStr(utils, timezone, section.format); + const formattedDaysInWeek = getDaysInWeekStr(utils, section.format); const dayInWeekStrOfActiveDate = utils.formatByString(dateToTransferFrom, section.format); const dayInWeekOfActiveDate = formattedDaysInWeek.indexOf(dayInWeekStrOfActiveDate); const dayInWeekOfNewSectionValue = formattedDaysInWeek.indexOf(section.value); @@ -728,7 +724,6 @@ const reliableSectionModificationOrder: Record = { export const mergeDateIntoReferenceDate = ( utils: MuiPickersAdapter, - timezone: PickersTimezone, dateToTransferFrom: TDate, sections: FieldSection[], referenceDate: TDate, @@ -741,7 +736,7 @@ export const mergeDateIntoReferenceDate = ( ) .reduce((mergedDate, section) => { if (!shouldLimitToEditedSections || section.modified) { - return transferDateSectionValue(utils, timezone, section, dateToTransferFrom, mergedDate); + return transferDateSectionValue(utils, section, dateToTransferFrom, mergedDate); } return mergedDate; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldCharacterEditing.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldCharacterEditing.ts index e028983db3f2..cb2d63d0a16e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldCharacterEditing.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldCharacterEditing.ts @@ -341,7 +341,6 @@ export const useFieldCharacterEditing = < if (activeSection.type === 'month') { const hasLeadingZerosInFormat = doesSectionFormatHaveLeadingZeros( utils, - timezone, 'digit', 'month', 'MM', @@ -381,7 +380,7 @@ export const useFieldCharacterEditing = < return response; } - const formattedValue = getDaysInWeekStr(utils, timezone, activeSection.format)[ + const formattedValue = getDaysInWeekStr(utils, activeSection.format)[ Number(response.sectionValue) - 1 ]; return { diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts index 846ab4be676d..0e87524ab21e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts @@ -136,7 +136,6 @@ export const useFieldState = < fieldValueManager.getSectionsFromValue(utils, value, fallbackSections, (date) => buildSectionsFromFormat({ utils, - timezone, localeText: translations, localizedDigits, format, @@ -156,7 +155,6 @@ export const useFieldState = < shouldRespectLeadingZeros, utils, formatDensity, - timezone, enableAccessibleFieldDOMStructure, ], ); @@ -286,7 +284,6 @@ export const useFieldState = < const sections = buildSectionsFromFormat({ utils, - timezone, localeText: translations, localizedDigits, format, @@ -296,7 +293,7 @@ export const useFieldState = < enableAccessibleFieldDOMStructure, isRtl, }); - return mergeDateIntoReferenceDate(utils, timezone, date, sections, referenceDate, false); + return mergeDateIntoReferenceDate(utils, date, sections, referenceDate, false); }; const newValue = fieldValueManager.parseValueStr(valueStr, state.referenceValue, parseDateStr); @@ -345,7 +342,6 @@ export const useFieldState = < if (newActiveDate != null && utils.isValid(newActiveDate)) { const mergedDate = mergeDateIntoReferenceDate( utils, - timezone, newActiveDate, newActiveDateSections, activeDateManager.referenceDate, 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 6145d21537ae..89108c2cb621 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -10,7 +10,6 @@ import { } from './useMobilePicker.types'; import { usePicker } from '../usePicker'; import { onSpaceOrEnter } from '../../utils/utils'; -import { LocalizationProvider } from '../../../LocalizationProvider'; import { PickersLayout } from '../../../PickersLayout'; import { FieldSection, @@ -20,6 +19,7 @@ import { InferError, } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; +import { PickersProvider } from '../../components/PickersProvider'; /** * Hook managing all the single-date mobile pickers: @@ -73,6 +73,7 @@ export const useMobilePicker = < layoutProps, renderCurrentView, fieldProps: pickerFieldProps, + contextValue, } = usePicker({ ...pickerParams, props, @@ -159,7 +160,7 @@ export const useMobilePicker = < const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( - + - + ); return { renderPicker }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 8c74daccc437..3d290f0ded8c 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -84,5 +84,8 @@ export const usePicker = < // Picker layout layoutProps: pickerLayoutResponse.layoutProps, + + // Picker context + contextValue: pickerValueResponse.contextValue, }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts index 8d6255b5f562..ae7553494e0a 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts @@ -28,6 +28,7 @@ import { PickerValueUpdaterParams, } from './usePickerValue.types'; import { useValueWithTimezone } from '../useValueWithTimezone'; +import { PickersContextValue } from '../../components/PickersProvider'; /** * Decide if the new value should be published @@ -457,11 +458,21 @@ export const usePickerValue = < isValid, }; + const contextValue = React.useMemo( + () => ({ + onOpen: handleOpen, + onClose: handleClose, + open: isOpen, + }), + [isOpen, handleClose, handleOpen], + ); + return { open: isOpen, fieldProps: fieldResponse, viewProps: viewResponse, layoutProps: layoutResponse, actions, + contextValue, }; }; 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 a2882f62f57f..5b5952a1d3d0 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 @@ -17,6 +17,7 @@ import { PickerShortcutChangeImportance, PickersShortcutsItemContext, } from '../../../PickersShortcuts'; +import { PickersContextValue } from '../../components/PickersProvider'; export interface PickerValueManager { /** @@ -327,4 +328,5 @@ export interface UsePickerValueResponse; fieldProps: UsePickerValueFieldResponse; layoutProps: UsePickerValueLayoutResponse; + contextValue: PickersContextValue; } diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 6bd9dab4cd19..010b5eda898d 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -4,6 +4,7 @@ export type { PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps, } from './components/PickersArrowSwitcher'; +export { PickersProvider } from './components/PickersProvider'; export { PickersModalDialog } from './components/PickersModalDialog'; export type { PickersModalDialogSlots, diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index efd9594f7858..c0c3e0d39b25 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -419,7 +419,9 @@ { "name": "UseMultiInputDateTimeRangeFieldProps", "kind": "Interface" }, { "name": "UseMultiInputTimeRangeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseMultiInputTimeRangeFieldProps", "kind": "Interface" }, + { "name": "useParsedFormat", "kind": "Variable" }, { "name": "usePickerLayout", "kind": "ExportAssignment" }, + { "name": "usePickersContext", "kind": "Variable" }, { "name": "usePickersTranslations", "kind": "Variable" }, { "name": "UseSingleInputDateRangeFieldProps", "kind": "Interface" }, { "name": "UseSingleInputDateTimeRangeFieldProps", "kind": "Interface" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index 8581ad347351..e432b56a60af 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -304,7 +304,9 @@ { "name": "UseDateFieldProps", "kind": "Interface" }, { "name": "UseDateTimeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, + { "name": "useParsedFormat", "kind": "Variable" }, { "name": "usePickerLayout", "kind": "ExportAssignment" }, + { "name": "usePickersContext", "kind": "Variable" }, { "name": "usePickersTranslations", "kind": "Variable" }, { "name": "useSplitFieldProps", "kind": "Variable" }, { "name": "UseTimeFieldComponentProps", "kind": "TypeAlias" },