Skip to content

Commit

Permalink
fix: textfield masked value to show phone number
Browse files Browse the repository at this point in the history
affects: @medly-components/core, @medly-components/forms
  • Loading branch information
gmukul01 committed Sep 22, 2024
1 parent f295b47 commit 759fea7
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 75 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ const Component: FC<DatePickerProps> = memo(
onChange={onTextChange}
validator={inputValidator}
onKeyPress={onKeyPress}
maxLength={mask!.length + 1}
maxLength={mask!.length}
{...{ ...restProps, onBlur, onFocus, minWidth, onInvalid }}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const DateRangeTextFields: FC<Props> = memo(props => {
value={startDateText}
name="START_DATE"
isPrefixPresent
maxLength={mask.length + 1}
maxLength={mask.length}
dateMaskLabel={startDateMaskLabel}
label={startDateLabel}
onKeyPress={onKeyPress}
Expand All @@ -140,7 +140,7 @@ export const DateRangeTextFields: FC<Props> = memo(props => {
value={endDateText}
name="END_DATE"
dateMaskLabel={endDateMaskLabel}
maxLength={mask.length + 1}
maxLength={mask.length}
label={endDateLabel}
onKeyPress={onKeyPress}
{...commonTextProps}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2334,7 +2334,7 @@ exports[`DateRangePicker Custom date range options should render properly with c
<input
class="c6"
id="contract-startDate-input"
maxlength="15"
maxlength="14"
name="START_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -2362,7 +2362,7 @@ exports[`DateRangePicker Custom date range options should render properly with c
<input
class="c6"
id="contract-endDate-input"
maxlength="15"
maxlength="14"
name="END_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -25023,7 +25023,7 @@ exports[`DateRangePicker should render properly 1`] = `
<input
class="c6"
id="contract-startDate-input"
maxlength="15"
maxlength="14"
name="START_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -25051,7 +25051,7 @@ exports[`DateRangePicker should render properly 1`] = `
<input
class="c6"
id="contract-endDate-input"
maxlength="15"
maxlength="14"
name="END_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -27444,7 +27444,7 @@ exports[`DateRangePicker should render properly with single month 1`] = `
<input
class="c7"
id="contract-startDate-input"
maxlength="15"
maxlength="14"
name="START_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -27472,7 +27472,7 @@ exports[`DateRangePicker should render properly with single month 1`] = `
<input
class="c7"
id="contract-endDate-input"
maxlength="15"
maxlength="14"
name="END_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -29376,7 +29376,7 @@ exports[`DateRangePicker should render properly with single month for mobile scr
<input
class="c7"
id="contract-startDate-input"
maxlength="15"
maxlength="14"
name="START_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down Expand Up @@ -29404,7 +29404,7 @@ exports[`DateRangePicker should render properly with single month for mobile scr
<input
class="c7"
id="contract-endDate-input"
maxlength="15"
maxlength="14"
name="END_DATE"
pattern="\\\\d{2} \\\\/ \\\\d{2} \\\\/ \\\\d{4}"
placeholder="MM / DD / YYYY"
Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/components/TextField/TextField.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { TextField } from './TextField.tsx';
import { AddCircleIcon, AddCircleOutlineIcon, CheckCircleIcon } from '@medly-components/icons';
import { Meta, Preview, Props, Story } from '@storybook/addon-docs/blocks';
import { boolean, color, number, select, text, withKnobs } from '@storybook/addon-knobs';
import { useCallback, useState } from 'react';
import Text from '../Text';
import { AddCircleOutlineIcon, AddCircleIcon, CheckCircleIcon } from '@medly-components/icons';
import { withKnobs, text, color, select, boolean, number } from '@storybook/addon-knobs';
import { Preview, Story, Meta, Props } from '@storybook/addon-docs/blocks';
import { ThemeInterface, variants, sizes, Custom } from './TextField.stories.tsx';
import { useState, useCallback } from 'react';
import { Custom, sizes, ThemeInterface, variants } from './TextField.stories.tsx';
import { TextField } from './TextField.tsx';

<Meta title="Core" component={TextField} parameters={{ jest: ['TextField.test.tsx'] }} />

Expand Down Expand Up @@ -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 '';
}}
/>
);
}}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/components/TextField/getMaskedValue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
43 changes: 29 additions & 14 deletions packages/core/src/components/TextField/getMaskedValue.ts
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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<HTMLInputElement>, mask: string) => {
let maskedValue;

Expand All @@ -41,35 +45,46 @@ export const getMaskedValue = (event: React.ChangeEvent<HTMLInputElement>, 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
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,14 @@ const Component: FC<TimePickerTextFieldProps> = 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) => {
Expand All @@ -112,7 +107,7 @@ const Component: FC<TimePickerTextFieldProps> = 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();
}
Expand All @@ -125,14 +120,14 @@ const Component: FC<TimePickerTextFieldProps> = memo(
return (
<TextField
fullWidth
mask="HH : MM AM"
mask="HH : MM AM"
ref={inputRef}
suffix={TimeIcon}
onKeyPress={onKeyPress}
key={key.toString()}
maxLength={12}
maxLength={11}
autoComplete="off"
pattern={'[0-9]{2} : [0-9]{2} [AaPp][Mm]'}
pattern={'[0-9]{2} : [0-9]{2} [AaPp][Mm]'}
{...{ ...props, value: text, onBlur, validator, onChange }}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
<span
class="c7 c8"
>
01 : 11 PM
01 : 11 PM
</span>
<label
class="c9 c10"
Expand Down
Loading

0 comments on commit 759fea7

Please sign in to comment.