diff --git a/docs/data/date-pickers/date-picker/LocalizedDatePicker.js b/docs/data/date-pickers/date-picker/LocalizedDatePicker.js index 51890786072eb..14a23fc638a12 100644 --- a/docs/data/date-pickers/date-picker/LocalizedDatePicker.js +++ b/docs/data/date-pickers/date-picker/LocalizedDatePicker.js @@ -17,13 +17,6 @@ const localeMap = { de: deLocale, }; -const maskMap = { - fr: '__/__/____', - en: '__/__/____', - ru: '__.__.____', - de: '__.__.____', -}; - export default function LocalizedDatePicker() { const [locale, setLocale] = React.useState('ru'); const [value, setValue] = React.useState(new Date()); @@ -50,7 +43,6 @@ export default function LocalizedDatePicker() { ))} setValue(newValue)} renderInput={(params) => } diff --git a/docs/data/date-pickers/date-picker/LocalizedDatePicker.tsx b/docs/data/date-pickers/date-picker/LocalizedDatePicker.tsx index 798bb16a1989b..006c3d29896b4 100644 --- a/docs/data/date-pickers/date-picker/LocalizedDatePicker.tsx +++ b/docs/data/date-pickers/date-picker/LocalizedDatePicker.tsx @@ -17,15 +17,8 @@ const localeMap = { de: deLocale, }; -const maskMap = { - fr: '__/__/____', - en: '__/__/____', - ru: '__.__.____', - de: '__.__.____', -}; - export default function LocalizedDatePicker() { - const [locale, setLocale] = React.useState('ru'); + const [locale, setLocale] = React.useState('ru'); const [value, setValue] = React.useState(new Date()); const selectLocale = (newLocale: any) => { @@ -50,7 +43,6 @@ export default function LocalizedDatePicker() { ))} setValue(newValue)} renderInput={(params) => } diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts index 89b7acac0f0d3..cc668e825fc8e 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts +++ b/packages/x-date-pickers-pro/src/DateRangePicker/shared.ts @@ -61,10 +61,7 @@ export function useDateRangePickerDefaultizedProps< name: string, ): DefaultizedProps & Required< - Pick< - BaseDateRangePickerProps, - 'calendars' | 'mask' | 'startText' | 'endText' - > + Pick, 'calendars' | 'startText' | 'endText'> > { const utils = useUtils(); const defaultDates = useDefaultDates(); @@ -88,7 +85,6 @@ export function useDateRangePickerDefaultizedProps< return { calendars: 2, - mask: '__/__/____', inputFormat: utils.formats.keyboardDate, minDate: defaultDates.minDate, maxDate: defaultDates.maxDate, diff --git a/packages/x-date-pickers/src/DatePicker/shared.ts b/packages/x-date-pickers/src/DatePicker/shared.ts index aade45fc02f76..fa6de376bdcc0 100644 --- a/packages/x-date-pickers/src/DatePicker/shared.ts +++ b/packages/x-date-pickers/src/DatePicker/shared.ts @@ -65,7 +65,6 @@ const getFormatAndMaskByViews = ( ): { disableMaskedInput?: boolean; inputFormat: string; mask?: string } => { if (isYearOnlyView(views)) { return { - mask: '____', inputFormat: utils.formats.year, }; } @@ -78,7 +77,6 @@ const getFormatAndMaskByViews = ( } return { - mask: '__/__/____', inputFormat: utils.formats.keyboardDate, }; }; diff --git a/packages/x-date-pickers/src/DateTimePicker/shared.ts b/packages/x-date-pickers/src/DateTimePicker/shared.ts index 0abae39282de2..12fc4cb8a80c9 100644 --- a/packages/x-date-pickers/src/DateTimePicker/shared.ts +++ b/packages/x-date-pickers/src/DateTimePicker/shared.ts @@ -109,7 +109,6 @@ export function useDateTimePickerDefaultizedProps< openTo: 'day', views: ['year', 'day', 'hours', 'minutes'], ampmInClock: true, - mask: ampm ? '__/__/____ __:__ _m' : '__/__/____ __:__', acceptRegex: ampm ? /[\dap]/gi : /\d/gi, disableMaskedInput: false, inputFormat: ampm ? utils.formats.keyboardDateTime12h : utils.formats.keyboardDateTime24h, diff --git a/packages/x-date-pickers/src/TimePicker/shared.ts b/packages/x-date-pickers/src/TimePicker/shared.ts index 7281e641d6e06..6852d4d657d18 100644 --- a/packages/x-date-pickers/src/TimePicker/shared.ts +++ b/packages/x-date-pickers/src/TimePicker/shared.ts @@ -74,7 +74,6 @@ export function useTimePickerDefaultizedProps< openTo: 'hours', views: ['hours', 'minutes'], acceptRegex: ampm ? /[\dapAP]/gi : /\d/gi, - mask: ampm ? '__:__ _m' : '__:__', disableMaskedInput: false, getOpenDialogAriaText, inputFormat: ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h, diff --git a/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx b/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx index c3b1b2d69277c..7fdf8a36aae2f 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx @@ -6,6 +6,7 @@ import { maskedDateFormatter, getDisplayDate, checkMaskIsValidForCurrentFormat, + getMaskFromCurrentFormat, } from '../utils/text-field-helper'; type MaskedInputProps = Omit< @@ -41,19 +42,30 @@ export const useMaskedInput = ({ const formatHelperText = utils.getFormatHelperText(inputFormat); - const shouldUseMaskedInput = React.useMemo(() => { + const { shouldUseMaskedInput, maskToUse } = React.useMemo(() => { // formatting of dates is a quite slow thing, so do not make useless .format calls - if (!mask || disableMaskedInput) { - return false; + if (disableMaskedInput) { + return { shouldUseMaskedInput: false, maskToUse: '' }; } + const computedMaskToUse = getMaskFromCurrentFormat(mask, inputFormat, acceptRegex, utils); - return checkMaskIsValidForCurrentFormat(mask, inputFormat, acceptRegex, utils); + return { + shouldUseMaskedInput: checkMaskIsValidForCurrentFormat( + computedMaskToUse, + inputFormat, + acceptRegex, + utils, + ), + maskToUse: computedMaskToUse, + }; }, [acceptRegex, disableMaskedInput, inputFormat, mask, utils]); const formatter = React.useMemo( () => - shouldUseMaskedInput && mask ? maskedDateFormatter(mask, acceptRegex) : (st: string) => st, - [acceptRegex, mask, shouldUseMaskedInput], + shouldUseMaskedInput && maskToUse + ? maskedDateFormatter(maskToUse, acceptRegex) + : (st: string) => st, + [acceptRegex, maskToUse, shouldUseMaskedInput], ); // TODO: Implement with controlled vs uncontrolled `rawValue` diff --git a/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts b/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts index ce6656b70bd3f..51b3dc3ccce11 100644 --- a/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts +++ b/packages/x-date-pickers/src/internals/utils/text-field-helper.test.ts @@ -70,12 +70,7 @@ describe('text-field-helper', () => { if (isValid) { expect(runMaskValidation()).to.be.equal(true); } else { - expect(runMaskValidation).toWarnDev( - [ - `The mask "${mask}" you passed is not valid for the format used ${format}.`, - `Falling down to uncontrolled no-mask input.`, - ].join('\n'), - ); + expect(runMaskValidation).toWarnDev('Falling down to uncontrolled no-mask input.'); } }); }); diff --git a/packages/x-date-pickers/src/internals/utils/text-field-helper.ts b/packages/x-date-pickers/src/internals/utils/text-field-helper.ts index 00dff3b55d8df..af35986d45890 100644 --- a/packages/x-date-pickers/src/internals/utils/text-field-helper.ts +++ b/packages/x-date-pickers/src/internals/utils/text-field-helper.ts @@ -27,12 +27,55 @@ const MASK_USER_INPUT_SYMBOL = '_'; const staticDateWith2DigitTokens = '2019-11-21T22:30:00.000'; const staticDateWith1DigitTokens = '2019-01-01T09:00:00.000'; +export function getMaskFromCurrentFormat( + mask: string | undefined, + format: string, + acceptRegex: RegExp, + utils: MuiPickersAdapter, +) { + if (mask) { + return mask; + } + + const formattedDateWith1Digit = utils.formatByString( + utils.date(staticDateWith1DigitTokens)!, + format, + ); + const inferredFormatPatternWith1Digits = formattedDateWith1Digit.replace( + acceptRegex, + MASK_USER_INPUT_SYMBOL, + ); + + const inferredFormatPatternWith2Digits = utils + .formatByString(utils.date(staticDateWith2DigitTokens)!, format) + .replace(acceptRegex, '_'); + + if (inferredFormatPatternWith1Digits === inferredFormatPatternWith2Digits) { + return inferredFormatPatternWith1Digits; + } + + if (process.env.NODE_ENV !== 'production') { + console.warn( + [ + `Mask does not support numbers with variable length such as 'M'.`, + `Either use numbers with fix length or disable mask feature with 'disableMaskedInput' prop`, + `Falling down to uncontrolled no-mask input.`, + ].join('\n'), + ); + } + return ''; +} + export function checkMaskIsValidForCurrentFormat( mask: string, format: string, acceptRegex: RegExp, utils: MuiPickersAdapter, ) { + if (!mask) { + return false; + } + const formattedDateWith1Digit = utils.formatByString( utils.date(staticDateWith1DigitTokens)!, format, @@ -47,35 +90,36 @@ export function checkMaskIsValidForCurrentFormat( .replace(acceptRegex, '_'); const isMaskValid = - inferredFormatPatternWith2Digits === mask && inferredFormatPatternWith1Digits === mask; + inferredFormatPatternWith2Digits === inferredFormatPatternWith1Digits && + mask === inferredFormatPatternWith2Digits; if (!isMaskValid && utils.lib !== 'luxon' && process.env.NODE_ENV !== 'production') { - const defaultWarning = [ - `The mask "${mask}" you passed is not valid for the format used ${format}.`, - `Falling down to uncontrolled no-mask input.`, - ]; - if (format.includes('MMM')) { console.warn( [ - ...defaultWarning, `Mask does not support literals such as 'MMM'.`, `Either use numbers with fix length or disable mask feature with 'disableMaskedInput' prop`, + `Falling down to uncontrolled no-mask input.`, ].join('\n'), ); } else if ( - inferredFormatPatternWith2Digits !== mask && - inferredFormatPatternWith1Digits === mask + inferredFormatPatternWith2Digits && + inferredFormatPatternWith2Digits !== inferredFormatPatternWith1Digits ) { console.warn( [ - ...defaultWarning, `Mask does not support numbers with variable length such as 'M'.`, `Either use numbers with fix length or disable mask feature with 'disableMaskedInput' prop`, + `Falling down to uncontrolled no-mask input.`, + ].join('\n'), + ); + } else if (mask) { + console.warn( + [ + `The mask "${mask}" you passed is not valid for the format used ${format}.`, + `Falling down to uncontrolled no-mask input.`, ].join('\n'), ); - } else { - console.warn(defaultWarning.join('\n')); } }