From 936577cca74f5364452d92770bafa56df94f6d58 Mon Sep 17 00:00:00 2001 From: Mukul Bansal Date: Sat, 8 Jun 2024 11:35:57 +0530 Subject: [PATCH] fix: datepicker clear issue on deleting one char from the middle affects: @medly-components/core, @medly-components/forms --- .../components/DatePicker/DatePicker.test.tsx | 10 ++++++++++ .../src/components/DatePicker/DatePicker.tsx | 5 ++--- .../__snapshots__/DatePicker.test.tsx.snap | 5 +++++ .../useDateRangeTextFieldsHandlers.ts | 19 +++++++++++-------- .../DateRangePicker.test.tsx.snap | 3 +++ .../Styled/MaskPlaceholder.styled.tsx | 1 + .../src/components/TextField/TextField.tsx | 3 ++- .../TextField/getMaskedValue.test.ts | 6 +++--- .../components/TextField/getMaskedValue.ts | 6 +++--- .../Form/__snapshots__/Form.test.tsx.snap | 2 ++ 10 files changed, 42 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/DatePicker/DatePicker.test.tsx b/packages/core/src/components/DatePicker/DatePicker.test.tsx index 92600519a..71031f801 100644 --- a/packages/core/src/components/DatePicker/DatePicker.test.tsx +++ b/packages/core/src/components/DatePicker/DatePicker.test.tsx @@ -236,6 +236,16 @@ describe('DatePicker component', () => { fireEvent.change(screen.getByRole('textbox'), { target: { value: '01 / 02' } }); expect(mockOnChange).toHaveBeenCalledWith(null); }); + + it('should call onChange with null if we delete char from the middle', async () => { + const mockOnChange = jest.fn(), + dateToSelect = new Date(2020, 0, 2); + render(); + fireEvent.change(screen.getByRole('textbox'), { target: { value: '01 / 02 / 2020' } }); + expect(mockOnChange).toHaveBeenCalledWith(dateToSelect); + fireEvent.change(screen.getByRole('textbox'), { target: { value: '01 / 0 / 2020' } }); + expect(mockOnChange).toHaveBeenCalledWith(null); + }); }); describe('error messages', () => { diff --git a/packages/core/src/components/DatePicker/DatePicker.tsx b/packages/core/src/components/DatePicker/DatePicker.tsx index 0258791d9..0ec554c86 100644 --- a/packages/core/src/components/DatePicker/DatePicker.tsx +++ b/packages/core/src/components/DatePicker/DatePicker.tsx @@ -39,6 +39,7 @@ const Component: FC = memo( () => (value instanceof Date ? value : typeof value === 'string' ? parseToDate(value, displayFormat!) : null), [value, displayFormat] ); + const wrapperRef = useRef(null), inputRef = useCombinedRefs(ref, useRef(null)), [inputKey, setInputKey] = useState(0), @@ -49,9 +50,7 @@ const Component: FC = memo( isErrorPresent = useMemo(() => !!errorText || !!builtInErrorMessage, [errorText, builtInErrorMessage]); useEffect(() => { - if (date === null) { - setTextValue(''); - } else if (date) { + if (date) { setTextValue(format(date, displayFormat!).replace(new RegExp('\\/|\\-', 'g'), ' $& ')); } }, [date, displayFormat]); 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 cd2fa0d9e..0d5f00b61 100644 --- a/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/core/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap @@ -175,6 +175,7 @@ exports[`DatePicker component calendar icon should show calendar icon displayed -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); top: 50%; -webkit-transform: translateY(-50%); @@ -572,6 +573,7 @@ exports[`DatePicker component calendar icon should show calendar icon displayed -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); top: 50%; -webkit-transform: translateY(-50%); @@ -13428,6 +13430,7 @@ exports[`DatePicker component should render properly when hideInput prop is pass -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } @@ -13827,6 +13830,7 @@ exports[`DatePicker component should render properly when value is of date type -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } @@ -14207,6 +14211,7 @@ exports[`DatePicker component should render properly when value is of string typ -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); top: 50%; -webkit-transform: translateY(-50%); diff --git a/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts b/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts index b28f94010..0857c8d93 100644 --- a/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts +++ b/packages/core/src/components/DateRangePicker/DateRangeTextFields/useDateRangeTextFieldsHandlers.ts @@ -48,10 +48,10 @@ export const useDateRangeTextFieldsHandlers = (props: Props) => { }, []), handleTextChange = useCallback( (e: React.ChangeEvent) => { - const maskedValue = getMaskedValue(e, mask), + const { maskedValue, selectionStart } = getMaskedValue(e, mask), parsedDate = parseToDate(e.target.value, displayFormat), maskedLabel = `${maskedValue}${mask.substr(maskedValue.length)}`; - + e.target.setSelectionRange(selectionStart, selectionStart); if (e.target.name === 'START_DATE') { setStartDateText(maskedValue); setStartDateMaskLabel(maskedLabel); @@ -101,8 +101,11 @@ export const useDateRangeTextFieldsHandlers = (props: Props) => { validateOnWrapperBlur = useCallback( (event: ChangeEvent) => { const validatorMessage = (validator && validator(selectedDates, event)) || '', - customMessage = (required && !selectedDates.startDate && !selectedDates.endDate && 'Please fill in this field.') || '', - message = validator ? validatorMessage : customMessage; + customRequiredMessage = + required && (!selectedDates.startDate || !selectedDates.endDate) && 'Please fill in this field.', + customInvalidMessage = + (!isValidDate(selectedDates.startDate) || !isValidDate(selectedDates.endDate)) && 'Enter valid date', + message = validator ? validatorMessage : customRequiredMessage || customInvalidMessage || ''; setErrorMessage(message); if (validator) { startDateRef.current?.setCustomValidity(validatorMessage); @@ -115,10 +118,10 @@ export const useDateRangeTextFieldsHandlers = (props: Props) => { useEffect(() => { const formattedStartDate = selectedDates.startDate ? getFormattedDate(selectedDates.startDate, displayFormat) : '', formattedEndDate = selectedDates.endDate ? getFormattedDate(selectedDates.endDate, displayFormat) : ''; - setStartDateText(formattedStartDate); - setEndDateText(formattedEndDate); - setStartDateMaskLabel(formattedStartDate || mask); - setEndDateMaskLabel(formattedEndDate || mask); + formattedStartDate && setStartDateText(formattedStartDate); + formattedEndDate && setEndDateText(formattedEndDate); + formattedStartDate && setStartDateMaskLabel(formattedStartDate || mask); + formattedEndDate && setEndDateMaskLabel(formattedEndDate || mask); isValidDate(selectedDates.startDate) && isValidDate(selectedDates.endDate) && setErrorMessage(''); }, [isActive, selectedDates, displayFormat]); 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 fab0d8870..57896ad8f 100644 --- a/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap +++ b/packages/core/src/components/DateRangePicker/__snapshots__/DateRangePicker.test.tsx.snap @@ -1901,6 +1901,7 @@ exports[`DateRangePicker Custom date range options should render properly with c -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } @@ -24175,6 +24176,7 @@ exports[`DateRangePicker should render properly 1`] = ` -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } @@ -26424,6 +26426,7 @@ exports[`DateRangePicker should render properly with single month 1`] = ` -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } diff --git a/packages/core/src/components/TextField/Styled/MaskPlaceholder.styled.tsx b/packages/core/src/components/TextField/Styled/MaskPlaceholder.styled.tsx index 0388d0a03..0ef9f5d22 100644 --- a/packages/core/src/components/TextField/Styled/MaskPlaceholder.styled.tsx +++ b/packages/core/src/components/TextField/Styled/MaskPlaceholder.styled.tsx @@ -20,6 +20,7 @@ export const MaskPlaceholder = styled('span')<{ cursor: text; user-select: none; pointer-events: none; + white-space: pre-wrap; color: ${({ theme }) => theme.textField.filled.active.placeholderColor}; ${({ isLabelPresent, size }) => (isLabelPresent && size !== 'S' ? bottom : center)} `; diff --git a/packages/core/src/components/TextField/TextField.tsx b/packages/core/src/components/TextField/TextField.tsx index 146136531..684e7fbfb 100644 --- a/packages/core/src/components/TextField/TextField.tsx +++ b/packages/core/src/components/TextField/TextField.tsx @@ -91,8 +91,9 @@ const Component: FC = memo( setCharacterCountValue(valueString.length); if (mask) { - const maskedValue = getMaskedValue(e, mask); + const { maskedValue, selectionStart } = getMaskedValue(e, mask); e.target.value = maskedValue; + e.target.setSelectionRange(selectionStart, selectionStart); setMaskLabel(`${maskedValue}${mask.substr(maskedValue.length)}`); } props.onChange && props.onChange(e); diff --git a/packages/core/src/components/TextField/getMaskedValue.test.ts b/packages/core/src/components/TextField/getMaskedValue.test.ts index ee087fcca..b34f47c20 100644 --- a/packages/core/src/components/TextField/getMaskedValue.test.ts +++ b/packages/core/src/components/TextField/getMaskedValue.test.ts @@ -5,14 +5,14 @@ describe('getMaskedValue function', () => { const maskedValue = (value: string, selectionStart?: number) => getMaskedValue({ target: { value, selectionStart } }, 'DD / MM / YYYY'); it('should return truncated value if selectionStart value is less then value length', () => { - expect(maskedValue('11 / 11 / 1111', 6)).toEqual('11 / 1'); + expect(maskedValue('11 / 11 / 1111', 6)).toEqual({ maskedValue: '11 / 1 1 / 1111', selectionStart: 6 }); }); it('should not add extra char if value length is equal to mask length', () => { - expect(maskedValue('11 / 11 / 11111')).toEqual('11 / 11 / 1111'); + expect(maskedValue('11 / 11 / 11111')).toEqual({ maskedValue: '11 / 11 / 1111', selectionStart: 14 }); }); it('should add special character in between', () => { - expect(maskedValue('11 / 111')).toEqual('11 / 11 / 1'); + expect(maskedValue('11 / 111')).toEqual({ maskedValue: '11 / 11 / 1', selectionStart: 11 }); }); }); diff --git a/packages/core/src/components/TextField/getMaskedValue.ts b/packages/core/src/components/TextField/getMaskedValue.ts index 1eef3cc63..982e7ca57 100644 --- a/packages/core/src/components/TextField/getMaskedValue.ts +++ b/packages/core/src/components/TextField/getMaskedValue.ts @@ -35,15 +35,15 @@ export const getMaskedValue = (event: React.ChangeEvent, mask: //TODO: Need to remove this if, when we handle masking when user deletes from the middle of the text if (selectionStart && selectionStart < value.length) { - maskedValue = value.slice(0, selectionStart); + maskedValue = `${value.slice(0, selectionStart)} ${value.slice(selectionStart)}`; + return { maskedValue, selectionStart }; } else { maskedValue = value .replace(specialCharsRegex, '') .split('') .reduce((acc: string, c: string) => applyMasking(acc + c, mask, selectionStart ?? 0), ''); + return { maskedValue, selectionStart: maskedValue.length }; } - - return maskedValue; }; export default getMaskedValue; diff --git a/packages/forms/src/components/Form/__snapshots__/Form.test.tsx.snap b/packages/forms/src/components/Form/__snapshots__/Form.test.tsx.snap index f032f0a5a..b61171318 100644 --- a/packages/forms/src/components/Form/__snapshots__/Form.test.tsx.snap +++ b/packages/forms/src/components/Form/__snapshots__/Form.test.tsx.snap @@ -542,6 +542,7 @@ exports[`Form should render properly with initial state 1`] = ` -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; } @@ -2960,6 +2961,7 @@ exports[`Form should render properly without initial state 1`] = ` -ms-user-select: none; user-select: none; pointer-events: none; + white-space: pre-wrap; color: rgba(0,90,238,.2); bottom: 0.7rem; }