From 759fea727c71222c35b5969010e33de8940c67ff Mon Sep 17 00:00:00 2001 From: Mukul Bansal Date: Sun, 22 Sep 2024 19:29:17 +0530 Subject: [PATCH] fix: textfield masked value to show phone number affects: @medly-components/core, @medly-components/forms --- .../src/components/DatePicker/DatePicker.tsx | 2 +- .../__snapshots__/DatePicker.test.tsx.snap | 10 ++--- .../DateRangeTextFields.tsx | 4 +- .../useDateRangeTextFieldsHandlers.ts | 3 -- .../DateRangePicker.test.tsx.snap | 16 +++---- .../TextField/TextField.stories.mdx | 22 +++++++--- .../TextField/getMaskedValue.test.ts | 4 +- .../components/TextField/getMaskedValue.ts | 43 +++++++++++++------ .../TimePickerTextField.tsx | 21 ++++----- .../__snapshots__/TimePicker.test.tsx.snap | 10 ++--- .../Form/__snapshots__/Form.test.tsx.snap | 30 ++++++------- 11 files changed, 90 insertions(+), 75 deletions(-) diff --git a/packages/core/src/components/DatePicker/DatePicker.tsx b/packages/core/src/components/DatePicker/DatePicker.tsx index 8b510b1de..752f4dae3 100644 --- a/packages/core/src/components/DatePicker/DatePicker.tsx +++ b/packages/core/src/components/DatePicker/DatePicker.tsx @@ -212,7 +212,7 @@ const Component: FC = memo( onChange={onTextChange} validator={inputValidator} onKeyPress={onKeyPress} - maxLength={mask!.length + 1} + maxLength={mask!.length} {...{ ...restProps, onBlur, onFocus, minWidth, onInvalid }} /> diff --git a/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap b/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap index e9151d6cc..9d89b624e 100644 --- a/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap @@ -376,7 +376,7 @@ exports[`DatePicker component calendar icon should show calendar icon displayed aria-describedby="dob-helper-text" class="c8" id="dob-input" - maxlength="15" + maxlength="14" pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}" placeholder="MM / DD / YYYY" type="text" @@ -757,7 +757,7 @@ exports[`DatePicker component calendar icon should show calendar icon displayed aria-describedby="dob-helper-text" class="c6" id="dob-input" - maxlength="15" + maxlength="14" pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}" placeholder="MM / DD / YYYY" type="text" @@ -13657,7 +13657,7 @@ exports[`DatePicker component should render properly when hideInput prop is pass aria-describedby="startdate-helper-text" class="c7" id="startdate-input" - maxlength="15" + maxlength="14" pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}" placeholder="MM / DD / YYYY" type="text" @@ -14039,7 +14039,7 @@ exports[`DatePicker component should render properly when value is of date type class="c6" disabled="" id="startdate-input" - maxlength="15" + maxlength="14" pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}" placeholder="MM / DD / YYYY" type="text" @@ -14420,7 +14420,7 @@ exports[`DatePicker component should render properly when value is of string typ class="c6" disabled="" id="medly-datepicker-input" - maxlength="15" + maxlength="14" pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}" placeholder="MM / DD / YYYY" type="text" diff --git a/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx b/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx index 81cb40f22..dc509e10c 100644 --- a/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx +++ b/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx @@ -127,7 +127,7 @@ export const DateRangeTextFields: FC = memo(props => { value={startDateText} name="START_DATE" isPrefixPresent - maxLength={mask.length + 1} + maxLength={mask.length} dateMaskLabel={startDateMaskLabel} label={startDateLabel} onKeyPress={onKeyPress} @@ -140,7 +140,7 @@ export const DateRangeTextFields: FC = memo(props => { value={endDateText} name="END_DATE" dateMaskLabel={endDateMaskLabel} - maxLength={mask.length + 1} + maxLength={mask.length} label={endDateLabel} onKeyPress={onKeyPress} {...commonTextProps} diff --git a/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts b/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts index d440d2c18..285403468 100644 --- a/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts +++ b/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts @@ -81,10 +81,7 @@ export const useDateRangeTextFieldsHandlers = (props: Props) => { event.target.value = maskedValue; event.target.setSelectionRange(selectionStart, selectionStart); if (getFormattedDate(inputValue, displayFormat)) { - event.target.maxLength = mask!.length; errorMessage && setErrorMessage(errorMessage); - } else { - event.target.maxLength = mask!.length + 1; } if (event.target.name === 'START_DATE') { diff --git a/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap b/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap index 8fb0be14b..37b2983e9 100644 --- a/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap +++ b/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap @@ -2334,7 +2334,7 @@ exports[`DateRangePicker Custom date range options should render properly with c @@ -36,7 +36,15 @@ You can use the basic `TextField` component to get user input. It is often used multiline={boolean('Multiline', false)} minRows={number('Rows', 3)} withCharacterCount={boolean('Character Count', false)} - maxLength={number('Max Length', null)} + mask="(XXX) XXX-XXXX" + maxLength={'(XXX) XXX-XXXX'.length} + validator={value => { + if (value === '') return 'This field is required'; + if (value.length < '(XXX) XXX-XXXX'.length) return 'Please enter valid number in format (XXX) XXX-XXXX'; + if (value.split('').find((char, index) => isNaN(char) && '(XXX) XXX-XXXX'[index] === 'X')) + return 'Please enter valid number in format (XXX) XXX-XXXX'; + return ''; + }} /> ); }} diff --git a/packages/core/src/components/TextField/getMaskedValue.test.ts b/packages/core/src/components/TextField/getMaskedValue.test.ts index 9de16254b..979b4fb28 100644 --- a/packages/core/src/components/TextField/getMaskedValue.test.ts +++ b/packages/core/src/components/TextField/getMaskedValue.test.ts @@ -6,8 +6,8 @@ describe('getMaskedValue function', () => { // @ts-expect-error getMaskedValue({ target: { value, selectionStart }, nativeEvent: { data } }, 'DD / MM / YYYY'); - it('should add blank space on deleting any non special character and move cursor after the blank space', () => { - expect(maskedValue('11 / 1 / 1111', 5, null)).toEqual({ maskedValue: '11 / 1 / 1111', selectionStart: 6 }); + it('should move cursor one space before on deleting any non special character', () => { + expect(maskedValue('11 / 1 / 1111', 5, null)).toEqual({ maskedValue: '11 / 1 / 1111', selectionStart: 5 }); }); it('should not move cursor on deleting any special character', () => { diff --git a/packages/core/src/components/TextField/getMaskedValue.ts b/packages/core/src/components/TextField/getMaskedValue.ts index 8b1806914..4f01807b1 100644 --- a/packages/core/src/components/TextField/getMaskedValue.ts +++ b/packages/core/src/components/TextField/getMaskedValue.ts @@ -1,7 +1,5 @@ const applyMasking = (value: string, mask: string, selectionStart: number, data: string | null): string => { const { length } = value, - lastChar = value.charAt(length - 1), - alphaRegex = /[a-zA-Z]/, //NOSONAR alphaNumericRegex = /[a-zA-Z0-9]/, //NOSONAR specialCharsRegex = /[^a-zA-Z0-9]/; //NOSONAR @@ -19,15 +17,21 @@ const applyMasking = (value: string, mask: string, selectionStart: number, data: // if user deletes the last special character newValue = value; } else { - const remainingMask = mask.substr(length), + const initialMask = mask.match(alphaNumericRegex)?.index, + remainingMask = mask.substr(length), numberIndex = remainingMask.match(alphaNumericRegex)?.index; - newValue = value + remainingMask.substr(0, numberIndex); + newValue = + initialMask && !specialCharsRegex.test(value[0]) + ? mask.slice(0, initialMask) + value + remainingMask.substr(0, numberIndex) + : value + remainingMask.substr(0, numberIndex); } return newValue; }; +const fixSpaces = (value: string): string => value.replace(/\s{3}/g, ' ').replace(/(\d)\s(\d)/g, '$1$2'); + export const getMaskedValue = (event: React.ChangeEvent, mask: string) => { let maskedValue; @@ -41,35 +45,46 @@ export const getMaskedValue = (event: React.ChangeEvent, mask: if (selectionStart === 2 && data !== null && value[0] === ' ') { return { - maskedValue: value.trim().replace(/\s{3}/g, ' '), + maskedValue: value + .trim() + .replace(/\s{3}/g, ' ') + .replace(/(\d)\s(\d)/g, '$1$2'), selectionStart: selectionStart - 1 }; } if (selectionStart === 0 && data === null) { return { - maskedValue: `${value.slice(0, selectionStart)} ${value.slice(selectionStart)}`.replace(/\s{3}/g, ' '), - selectionStart: selectionStart + 1 + maskedValue: fixSpaces(specialCharsRegex.test(mask.charAt(0)) ? `${mask.charAt(0)}${value.slice(selectionStart)}` : value), + selectionStart }; } else if (cursorText === ' ' && data === null) { const postCursorText = value[selectionStart]; - const updatedText = `${value.slice(0, selectionStart)} ${value.slice(selectionStart)}`; + const updatedText = fixSpaces( + specialCharsRegex.test(mask.charAt(selectionStart)) + ? `${value.slice(0, selectionStart)}${mask.charAt(selectionStart)}${value.slice(selectionStart)}` + : value + ); return { - maskedValue: postCursorText === ' ' ? updatedText.replace(/\s{3}/g, ' ') : updatedText, - selectionStart: postCursorText === ' ' ? selectionStart : selectionStart + 1 + maskedValue: postCursorText === ' ' ? fixSpaces(updatedText) : updatedText.replace(/(\d)\s(\d)/g, '$1$2'), + selectionStart }; } else if (cursorText === ' ' && data !== null) { return { - maskedValue: value.replace(/\s{3}/g, ' '), + maskedValue: fixSpaces(value), selectionStart }; } else if (cursorText !== ' ' && data === null) { return { - maskedValue: `${value.slice(0, selectionStart)} ${value.slice(selectionStart)}`.replace(/\s{3}/g, ' '), - selectionStart: selectionStart + maskedValue: fixSpaces( + specialCharsRegex.test(mask.charAt(selectionStart)) + ? `${value.slice(0, selectionStart)}${mask.charAt(selectionStart)}${value.slice(selectionStart)}` + : value + ), + selectionStart }; } else { return { - maskedValue: value.replace(/\s{3}/g, ' '), + maskedValue: fixSpaces(value), selectionStart }; } diff --git a/packages/core/src/components/TimePicker/TimePickerTextField/TimePickerTextField.tsx b/packages/core/src/components/TimePicker/TimePickerTextField/TimePickerTextField.tsx index b62d39231..252926fa7 100644 --- a/packages/core/src/components/TimePicker/TimePickerTextField/TimePickerTextField.tsx +++ b/packages/core/src/components/TimePicker/TimePickerTextField/TimePickerTextField.tsx @@ -82,19 +82,14 @@ const Component: FC = memo( } else { props.onChange?.(''); } - const updatedText = `${`0${hour}`.slice(-2)} : ${`0${minutes}`.slice(-2)} ${period}`; - const updatedCursor = - cursor - - (inputValue.slice(0, cursor) === updatedText.slice(0, cursor) ? 0 : inputValue.length - updatedText.length); + const updatedText = `${`0${hour}`.slice(-2)} : ${`0${minutes}`.slice(-2)} ${period}`; + if (inputValue!.length !== updatedText.length) { + runAfterUpdate(() => inputRef.current?.setSelectionRange(cursor, cursor)); + } setText(updatedText); - runAfterUpdate(() => inputRef.current?.setSelectionRange(updatedCursor, updatedCursor)); - inputRef.current.maxLength = 11; return; } } - if (inputRef.current) { - inputRef.current.maxLength = 12; - } }; const onKeyPress = (event: React.KeyboardEvent) => { @@ -112,7 +107,7 @@ const Component: FC = memo( const hour = Number(time[0]); const minutes = Number(time[1]); const period = hour < 12 ? 'AM' : 'PM'; - setText(`${`0${hour % 12 === 0 ? 12 : hour % 12}`.slice(-2)} : ${`0${minutes}`.slice(-2)} ${period}`); + setText(`${`0${hour % 12 === 0 ? 12 : hour % 12}`.slice(-2)} : ${`0${minutes}`.slice(-2)} ${period}`); inputRef.current?.setCustomValidity(''); inputRef.current?.blur(); } @@ -125,14 +120,14 @@ const Component: FC = memo( return ( ); diff --git a/packages/core/src/components/TimePicker/__snapshots__/TimePicker.test.tsx.snap b/packages/core/src/components/TimePicker/__snapshots__/TimePicker.test.tsx.snap index ef216fa21..d2d1b17b4 100644 --- a/packages/core/src/components/TimePicker/__snapshots__/TimePicker.test.tsx.snap +++ b/packages/core/src/components/TimePicker/__snapshots__/TimePicker.test.tsx.snap @@ -813,16 +813,16 @@ exports[`TimePicker should render properly 1`] = ` autocomplete="off" class="c6" id="time-input" - maxlength="12" - pattern="[0-9]{2} : [0-9]{2} [AaPp][Mm]" - placeholder="HH : MM AM" + maxlength="11" + pattern="[0-9]{2} : [0-9]{2} [AaPp][Mm]" + placeholder="HH : MM AM" type="text" - value="01 : 11 PM" + value="01 : 11 PM" /> - 01 : 11 PM + 01 : 11 PM