From e4931987c2fad37894ea07f658f08e35152040df Mon Sep 17 00:00:00 2001 From: Vladimir Potekhin <46284632+vladimirpotekhin@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:50:09 +0300 Subject: [PATCH] fix(kit): `Date` accept single character date segment during paste (#610) --- .../lib/masks/date-range/date-range-mask.ts | 6 + .../src/lib/masks/date-time/date-time-mask.ts | 9 +- projects/kit/src/lib/masks/date/date-mask.ts | 5 + projects/kit/src/lib/processors/index.ts | 1 + .../processors/normalize-date-preprocessor.ts | 65 +++++++++++ .../tests/normalize-date-preprocessor.spec.ts | 108 ++++++++++++++++++ 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 projects/kit/src/lib/processors/normalize-date-preprocessor.ts create mode 100644 projects/kit/src/lib/processors/tests/normalize-date-preprocessor.spec.ts diff --git a/projects/kit/src/lib/masks/date-range/date-range-mask.ts b/projects/kit/src/lib/masks/date-range/date-range-mask.ts index af0149502..6c5d4bb7a 100644 --- a/projects/kit/src/lib/masks/date-range/date-range-mask.ts +++ b/projects/kit/src/lib/masks/date-range/date-range-mask.ts @@ -5,6 +5,7 @@ import { createMinMaxDatePostprocessor, createValidDatePreprocessor, createZeroPlaceholdersPreprocessor, + normalizeDatePreprocessor, } from '../../processors'; import {MaskitoDateMode, MaskitoDateSegments} from '../../types'; import {createMinMaxRangeLengthPostprocessor} from './processors/min-max-range-length-postprocessor'; @@ -44,6 +45,11 @@ export function maskitoDateRangeOptionsGenerator({ overwriteMode: 'replace', preprocessors: [ createZeroPlaceholdersPreprocessor(), + normalizeDatePreprocessor({ + dateModeTemplate, + rangeSeparator, + dateSegmentsSeparator: dateSeparator, + }), createValidDatePreprocessor({ dateModeTemplate, rangeSeparator, diff --git a/projects/kit/src/lib/masks/date-time/date-time-mask.ts b/projects/kit/src/lib/masks/date-time/date-time-mask.ts index ff7634672..d36f51a64 100644 --- a/projects/kit/src/lib/masks/date-time/date-time-mask.ts +++ b/projects/kit/src/lib/masks/date-time/date-time-mask.ts @@ -1,7 +1,10 @@ import {MASKITO_DEFAULT_OPTIONS, MaskitoOptions} from '@maskito/core'; import {TIME_FIXED_CHARACTERS} from '../../constants'; -import {createZeroPlaceholdersPreprocessor} from '../../processors'; +import { + createZeroPlaceholdersPreprocessor, + normalizeDatePreprocessor, +} from '../../processors'; import {MaskitoDateMode, MaskitoTimeMode} from '../../types'; import {DATE_TIME_SEPARATOR} from './constants'; import {createMinMaxDateTimePostprocessor} from './postprocessors'; @@ -36,6 +39,10 @@ export function maskitoDateTimeOptionsGenerator({ overwriteMode: 'replace', preprocessors: [ createZeroPlaceholdersPreprocessor(), + normalizeDatePreprocessor({ + dateModeTemplate, + dateSegmentsSeparator: dateSeparator, + }), createValidDateTimePreprocessor({ dateModeTemplate, dateSegmentsSeparator: dateSeparator, diff --git a/projects/kit/src/lib/masks/date/date-mask.ts b/projects/kit/src/lib/masks/date/date-mask.ts index c89dc9f40..64b75a07b 100644 --- a/projects/kit/src/lib/masks/date/date-mask.ts +++ b/projects/kit/src/lib/masks/date/date-mask.ts @@ -4,6 +4,7 @@ import { createMinMaxDatePostprocessor, createValidDatePreprocessor, createZeroPlaceholdersPreprocessor, + normalizeDatePreprocessor, } from '../../processors'; import {MaskitoDateMode} from '../../types'; @@ -28,6 +29,10 @@ export function maskitoDateOptionsGenerator({ overwriteMode: 'replace', preprocessors: [ createZeroPlaceholdersPreprocessor(), + normalizeDatePreprocessor({ + dateModeTemplate, + dateSegmentsSeparator: separator, + }), createValidDatePreprocessor({ dateModeTemplate, dateSegmentsSeparator: separator, diff --git a/projects/kit/src/lib/processors/index.ts b/projects/kit/src/lib/processors/index.ts index c0916c57c..3c4f699e2 100644 --- a/projects/kit/src/lib/processors/index.ts +++ b/projects/kit/src/lib/processors/index.ts @@ -1,4 +1,5 @@ export {createMinMaxDatePostprocessor} from './min-max-date-postprocessor'; +export {normalizeDatePreprocessor} from './normalize-date-preprocessor'; export {maskitoPostfixPostprocessorGenerator} from './postfix-postprocessor'; export {maskitoPrefixPostprocessorGenerator} from './prefix-postprocessor'; export {createValidDatePreprocessor} from './valid-date-preprocessor'; diff --git a/projects/kit/src/lib/processors/normalize-date-preprocessor.ts b/projects/kit/src/lib/processors/normalize-date-preprocessor.ts new file mode 100644 index 000000000..a34e67a97 --- /dev/null +++ b/projects/kit/src/lib/processors/normalize-date-preprocessor.ts @@ -0,0 +1,65 @@ +import {MaskitoPreprocessor} from '@maskito/core'; + +import {DATE_TIME_SEPARATOR} from '../masks/date-time/constants'; + +export function normalizeDatePreprocessor({ + dateModeTemplate, + dateSegmentsSeparator, + rangeSeparator = '', +}: { + dateModeTemplate: string; + dateSegmentsSeparator: string; + rangeSeparator?: string; +}): MaskitoPreprocessor { + return ({elementState, data}) => { + const separator = rangeSeparator + ? new RegExp(`${rangeSeparator}|-`) + : DATE_TIME_SEPARATOR; + const possibleDates = data.split(separator); + + const dates = data.includes(DATE_TIME_SEPARATOR) + ? [possibleDates[0]] + : possibleDates; + + if ( + dates.every( + date => + date.trim().split(/\D/).length === + dateModeTemplate.split(dateSegmentsSeparator).length, + ) + ) { + const newData = dates + .map(date => + normalizeDateString(date, dateModeTemplate, dateSegmentsSeparator), + ) + .join(rangeSeparator); + + return { + elementState, + data: `${newData}${ + data.includes(DATE_TIME_SEPARATOR) + ? DATE_TIME_SEPARATOR + possibleDates[1] || '' + : '' + }`, + }; + } + + return {elementState, data}; + }; +} + +function normalizeDateString( + dateString: string, + template: string, + separator: string, +): string { + const dateSegments = dateString.split(/\D/); + const templateSegments = template.split(separator); + const normalizedSegments = dateSegments.map((segment, index) => + index === templateSegments.length - 1 + ? segment + : segment.padStart(templateSegments[index].length, '0'), + ); + + return normalizedSegments.join(separator); +} diff --git a/projects/kit/src/lib/processors/tests/normalize-date-preprocessor.spec.ts b/projects/kit/src/lib/processors/tests/normalize-date-preprocessor.spec.ts new file mode 100644 index 000000000..c315ec845 --- /dev/null +++ b/projects/kit/src/lib/processors/tests/normalize-date-preprocessor.spec.ts @@ -0,0 +1,108 @@ +import {MaskitoPreprocessor} from '@maskito/core'; + +import {normalizeDatePreprocessor} from '../normalize-date-preprocessor'; + +describe('normalizeDatePreprocessor', () => { + describe('Input-date-range', () => { + const preprocessor = normalizeDatePreprocessor({ + dateModeTemplate: 'dd.mm.yyyy', + dateSegmentsSeparator: '.', + rangeSeparator: ' – ', + }); + + const check = getCheckFunction(preprocessor); + + it('Empty input => 6.2.2023 – 7.2.2023', () => { + check('6.2.2023 – 7.2.2023', '06.02.2023 – 07.02.2023'); + }); + + it('Empty input => 6.2.2023 – 7.2.2023 (basic spaces)', () => { + check('6.2.2023 – 7.2.2023', '06.02.2023 – 07.02.2023'); + }); + + it('Empty input => 06.2.2023-07.2.2023', () => { + check('06.2.2023-07.2.2023', '06.02.2023 – 07.02.2023'); + }); + }); + + describe('Input-date long mode', () => { + const preprocessor = normalizeDatePreprocessor({ + dateModeTemplate: 'dd.mm.yyyy', + dateSegmentsSeparator: '.', + }); + + const check = getCheckFunction(preprocessor); + + it('Empty input => 6.2.2023', () => { + check('6.2.2023', '06.02.2023'); + }); + + it('Empty input => 06.2.2023', () => { + check('06.2.2023', '06.02.2023'); + }); + + it('Empty input => 06.2.20', () => { + check('06.2.20', '06.02.20'); + }); + }); + + describe('input-date short mode', () => { + const preprocessor = normalizeDatePreprocessor({ + dateModeTemplate: 'mm/yy', + dateSegmentsSeparator: '/', + }); + + const check = getCheckFunction(preprocessor); + + it('Empty input => 2/2/22', () => { + check('2/2', '02/2'); + }); + + it('Empty input => 1.1', () => { + check('1.1', '01/1'); + }); + + it('Empty input => 3.12', () => { + check('3.12', '03/12'); + }); + }); + + describe('input-date-time', () => { + const preprocessor = normalizeDatePreprocessor({ + dateModeTemplate: 'dd.mm.yyyy', + dateSegmentsSeparator: '.', + }); + const check = getCheckFunction(preprocessor); + + it('Empty input => 6.2.2023, 12:00', () => { + check('6.2.2023, 12:00', '06.02.2023, 12:00'); + }); + + it('Empty input => 6.2.2023, 15', () => { + check('6.2.2023, 15', '06.02.2023, 15'); + }); + + it('Empty input => 06.2.2023', () => { + check('06.2.2023', '06.02.2023'); + }); + + it('Empty input => 6.2.2023', () => { + check('6.2.2022, 15', '06.02.2022, 15'); + }); + }); +}); + +function getCheckFunction( + preprocessor: MaskitoPreprocessor, +): (actual: string, expected: string) => void { + return (insertedCharacters: string, expectedValue: string): void => { + const EMPTY_INPUT = {value: '', selection: [0, 0] as [number, number]}; + + const {data} = preprocessor( + {elementState: EMPTY_INPUT, data: insertedCharacters}, + 'insert', + ); + + expect(data).toEqual(expectedValue); + }; +}