diff --git a/projects/demo-integrations/src/support/commands/index.ts b/projects/demo-integrations/src/support/commands/index.ts
index fac181423..93d101729 100644
--- a/projects/demo-integrations/src/support/commands/index.ts
+++ b/projects/demo-integrations/src/support/commands/index.ts
@@ -1,5 +1,6 @@
///
+import {paste} from './paste';
import {smartTick} from './smart-tick';
declare global {
@@ -9,6 +10,8 @@ declare global {
durationMs: number,
options?: Parameters[2],
): Chainable;
+
+ paste(value: string): Chainable;
}
}
}
@@ -18,3 +21,4 @@ Cypress.Commands.add(
{prevSubject: ['optional', 'element', 'window', 'document']},
smartTick,
);
+Cypress.Commands.add('paste', {prevSubject: 'element'}, paste);
diff --git a/projects/demo-integrations/src/support/commands/paste.ts b/projects/demo-integrations/src/support/commands/paste.ts
new file mode 100644
index 000000000..1f2164c47
--- /dev/null
+++ b/projects/demo-integrations/src/support/commands/paste.ts
@@ -0,0 +1,45 @@
+/**
+ * Cypress does not have built-in support for pasting from clipboard.
+ * This utility is VERY approximate alternative for it.
+ *
+ * @see https://github.com/cypress-io/cypress/issues/28861
+ */
+export function paste(
+ $subject: T,
+ data: string,
+): ReturnType> {
+ const inputType = 'insertFromPaste';
+ const element = Cypress.dom.unwrap($subject)[0] as
+ | HTMLInputElement
+ | HTMLTextAreaElement;
+ const {value, selectionStart, selectionEnd} = element;
+
+ Cypress.log({
+ displayName: 'paste',
+ message: data,
+ consoleProps() {
+ return {
+ value,
+ selectionStart,
+ selectionEnd,
+ };
+ },
+ });
+
+ return cy
+ .wrap($subject, {log: false})
+ .trigger('beforeinput', {
+ inputType,
+ data,
+ log: false,
+ })
+ .invoke(
+ 'val',
+ value.slice(0, selectionStart ?? 0) + data + value.slice(selectionEnd ?? 0),
+ )
+ .trigger('input', {
+ inputType,
+ data,
+ log: false,
+ });
+}
diff --git a/projects/demo-integrations/src/tests/kit/date-time/date-time-basic.cy.ts b/projects/demo-integrations/src/tests/kit/date-time/date-time-basic.cy.ts
index 42b2982a8..c8a111a69 100644
--- a/projects/demo-integrations/src/tests/kit/date-time/date-time-basic.cy.ts
+++ b/projects/demo-integrations/src/tests/kit/date-time/date-time-basic.cy.ts
@@ -353,4 +353,38 @@ describe('DateTime | Basic', () => {
);
});
});
+
+ describe('Paste', () => {
+ it('value without segment separators', () => {
+ cy.get('@input')
+ .paste('02112018, 16:20')
+ .should('have.value', '02.11.2018, 16:20')
+ .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)
+ .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);
+ });
+
+ it('value without separator between date and time', () => {
+ cy.get('@input')
+ .paste('02.11.201816:20')
+ .should('have.value', '02.11.2018, 16:20')
+ .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)
+ .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);
+ });
+
+ it('value with incomplete separator between date and time', () => {
+ cy.get('@input')
+ .paste('02.11.2018,16:20')
+ .should('have.value', '02.11.2018, 16:20')
+ .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)
+ .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);
+ });
+
+ it('value without any separators', () => {
+ cy.get('@input')
+ .paste('021120181620')
+ .should('have.value', '02.11.2018, 16:20')
+ .should('have.prop', 'selectionStart', '02.11.2018, 16:20'.length)
+ .should('have.prop', 'selectionEnd', '02.11.2018, 16:20'.length);
+ });
+ });
});
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 cdf253ad6..6bb36c33e 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
@@ -87,10 +87,10 @@ export function maskitoDateTimeOptionsGenerator({
timeSegmentMinValues,
timeSegmentMaxValues,
parseValue: (x) => {
- const [dateString, timeString] = parseDateTimeString(x, {
+ const [dateString, timeString] = parseDateTimeString(
+ x,
dateModeTemplate,
- dateTimeSeparator,
- });
+ );
return {timeString, restValue: dateString + dateTimeSeparator};
},
@@ -109,10 +109,10 @@ export function maskitoDateTimeOptionsGenerator({
dateModeTemplate,
dateSegmentSeparator: dateSeparator,
splitFn: (value) => {
- const [dateString, timeString] = parseDateTimeString(value, {
+ const [dateString, timeString] = parseDateTimeString(
+ value,
dateModeTemplate,
- dateTimeSeparator,
- });
+ );
return {dateStrings: [dateString], restPart: timeString};
},
diff --git a/projects/kit/src/lib/masks/date-time/postprocessors/min-max-date-time-postprocessor.ts b/projects/kit/src/lib/masks/date-time/postprocessors/min-max-date-time-postprocessor.ts
index cf82026c2..23b0b3e2c 100644
--- a/projects/kit/src/lib/masks/date-time/postprocessors/min-max-date-time-postprocessor.ts
+++ b/projects/kit/src/lib/masks/date-time/postprocessors/min-max-date-time-postprocessor.ts
@@ -28,10 +28,7 @@ export function createMinMaxDateTimePostprocessor({
dateTimeSeparator: string;
}): MaskitoPostprocessor {
return ({value, selection}) => {
- const [dateString, timeString] = parseDateTimeString(value, {
- dateModeTemplate,
- dateTimeSeparator,
- });
+ const [dateString, timeString] = parseDateTimeString(value, dateModeTemplate);
const parsedDate = parseDateString(dateString, dateModeTemplate);
const parsedTime = parseTimeString(timeString, timeMode);
diff --git a/projects/kit/src/lib/masks/date-time/preprocessors/valid-date-time-preprocessor.ts b/projects/kit/src/lib/masks/date-time/preprocessors/valid-date-time-preprocessor.ts
index cd15e1d22..5c11c8b80 100644
--- a/projects/kit/src/lib/masks/date-time/preprocessors/valid-date-time-preprocessor.ts
+++ b/projects/kit/src/lib/masks/date-time/preprocessors/valid-date-time-preprocessor.ts
@@ -38,10 +38,10 @@ export function createValidDateTimePreprocessor({
let to = rawTo + data.length;
const newPossibleValue = value.slice(0, from) + newCharacters + value.slice(to);
- const [dateString, timeString] = parseDateTimeString(newPossibleValue, {
+ const [dateString, timeString] = parseDateTimeString(
+ newPossibleValue,
dateModeTemplate,
- dateTimeSeparator,
- });
+ );
let validatedValue = '';
const hasDateTimeSeparator = newPossibleValue.includes(dateTimeSeparator);
diff --git a/projects/kit/src/lib/masks/date-time/utils/parse-date-time-string.ts b/projects/kit/src/lib/masks/date-time/utils/parse-date-time-string.ts
index 8981e86b6..6b34dc4a6 100644
--- a/projects/kit/src/lib/masks/date-time/utils/parse-date-time-string.ts
+++ b/projects/kit/src/lib/masks/date-time/utils/parse-date-time-string.ts
@@ -1,21 +1,18 @@
+const NON_DIGIT_PLACEHOLDER_RE = /[^dmy]/g;
+const LEADING_NON_DIGIT_RE = /^\D*/;
+
export function parseDateTimeString(
dateTime: string,
- {
- dateModeTemplate,
- dateTimeSeparator,
- }: {
- dateModeTemplate: string;
- dateTimeSeparator: string;
- },
+ dateModeTemplate: string,
): [date: string, time: string] {
- const hasSeparator = dateTime.includes(dateTimeSeparator);
+ const dateDigitsCount = dateModeTemplate.replaceAll(
+ NON_DIGIT_PLACEHOLDER_RE,
+ '',
+ ).length;
+ const [date = ''] =
+ new RegExp(`(\\d[^\\d]*){0,${dateDigitsCount - 1}}\\d?`).exec(dateTime) || [];
+ const [dateTimeSeparator = ''] =
+ LEADING_NON_DIGIT_RE.exec(dateTime.slice(date.length)) || [];
- return [
- dateTime.slice(0, dateModeTemplate.length),
- dateTime.slice(
- hasSeparator
- ? dateModeTemplate.length + dateTimeSeparator.length
- : dateModeTemplate.length,
- ),
- ];
+ return [date, dateTime.slice(date.length + dateTimeSeparator.length)];
}
diff --git a/projects/kit/src/lib/masks/date-time/utils/tests/parse-date-time-string.spec.ts b/projects/kit/src/lib/masks/date-time/utils/tests/parse-date-time-string.spec.ts
new file mode 100644
index 000000000..683272592
--- /dev/null
+++ b/projects/kit/src/lib/masks/date-time/utils/tests/parse-date-time-string.spec.ts
@@ -0,0 +1,63 @@
+import {parseDateTimeString} from '../parse-date-time-string';
+
+describe('parseDateTimeString', () => {
+ describe('dd.mm.yyyy', () => {
+ const parse = (value: string): [string, string] =>
+ parseDateTimeString(value, 'dd.mm.yyyy');
+
+ (
+ [
+ {input: '', output: ['', '']},
+ {input: '02', output: ['02', '']},
+ {input: '02.', output: ['02.', '']},
+ {input: '0211', output: ['0211', '']},
+ {input: '0211.', output: ['0211.', '']},
+ {input: '02.112018', output: ['02.112018', '']},
+ {input: '02.112018,', output: ['02.112018', '']},
+ {input: '02.112018, ', output: ['02.112018', '']},
+ {input: '02.11.2018, ', output: ['02.11.2018', '']},
+ {input: '021120181620', output: ['02112018', '1620']},
+ {input: '02112018,1620', output: ['02112018', '1620']},
+ {input: '02112018, 1620', output: ['02112018', '1620']},
+ {input: '02112018, 16:20', output: ['02112018', '16:20']},
+ {input: '02112018,16:20', output: ['02112018', '16:20']},
+ {input: '02.11.2018,1620', output: ['02.11.2018', '1620']},
+ {input: '02.11.2018, 1620', output: ['02.11.2018', '1620']},
+ {input: '02.11.2018, 16:20', output: ['02.11.2018', '16:20']},
+ {input: '02.11.2018,16:20', output: ['02.11.2018', '16:20']},
+ ] as const
+ ).forEach(({input, output}) => {
+ it(`${input} -> ${JSON.stringify(output)}`, () => {
+ expect(parse(input)).toEqual(output);
+ });
+ });
+ });
+
+ describe('dd. mm. yyyy (date segment separator consists of space and dot)', () => {
+ const parse = (value: string): [string, string] =>
+ parseDateTimeString(value, 'dd. mm. yyyy');
+
+ (
+ [
+ {input: '', output: ['', '']},
+ {input: '02', output: ['02', '']},
+ {input: '02.', output: ['02.', '']},
+ {input: '02. ', output: ['02. ', '']},
+ {input: '0211', output: ['0211', '']},
+ {input: '0211. ', output: ['0211. ', '']},
+ {input: '02. 112018', output: ['02. 112018', '']},
+ {input: '02. 112018,', output: ['02. 112018', '']},
+ {input: '02. 112018, ', output: ['02. 112018', '']},
+ {input: '02. 11. 2018, ', output: ['02. 11. 2018', '']},
+ {input: '02. 11. 2018,1620', output: ['02. 11. 2018', '1620']},
+ {input: '02. 11. 2018, 1620', output: ['02. 11. 2018', '1620']},
+ {input: '02. 11. 2018, 16:20', output: ['02. 11. 2018', '16:20']},
+ {input: '02. 11. 2018,16:20', output: ['02. 11. 2018', '16:20']},
+ ] as const
+ ).forEach(({input, output}) => {
+ it(`${input} -> ${JSON.stringify(output)}`, () => {
+ expect(parse(input)).toEqual(output);
+ });
+ });
+ });
+});