diff --git a/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts b/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
index 116a663964d9..a9694c2556e4 100644
--- a/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
+++ b/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
@@ -291,5 +291,38 @@ test.describe('InputDateRange', () => {
'08-calendar-correct-selected-period-after-close-open.png',
);
});
+
+ test.describe('with `input[tuiTextfieldLegacy]` inside', () => {
+ test('filler has no change detection problems', async () => {
+ const example = documentationPage.getExample('#base');
+ const inputDateRange = new TuiInputDateRangePO(
+ example.locator('tui-input-date-range'),
+ );
+
+ /**
+ * To ensure that example is not changed and
+ * still contains InputDateRange with projected
+ */
+ await expect(
+ inputDateRange.host.locator('input[tuiTextfieldLegacy]'),
+ ).toBeAttached();
+
+ await inputDateRange.textfield.focus();
+
+ await expect(inputDateRange.host).toHaveScreenshot(
+ '12-backspace-pressed-0-times.png',
+ );
+
+ for (let i = 1; i <= 16; i++) {
+ await inputDateRange.textfield.press('Backspace');
+
+ await expect(inputDateRange.host).toHaveScreenshot(
+ `12-backspace-pressed-${i}-times.png`,
+ );
+ }
+
+ await expect(inputDateRange.textfield).toHaveValue('');
+ });
+ });
});
});
diff --git a/projects/demo-playwright/tests/legacy/input-date-time/input-date-time.pw.spec.ts b/projects/demo-playwright/tests/legacy/input-date-time/input-date-time.pw.spec.ts
index 5cee26a4346f..7bc36f70be59 100644
--- a/projects/demo-playwright/tests/legacy/input-date-time/input-date-time.pw.spec.ts
+++ b/projects/demo-playwright/tests/legacy/input-date-time/input-date-time.pw.spec.ts
@@ -312,24 +312,17 @@ test.describe('InputDateTime', () => {
});
test.describe('Examples', () => {
- let example!: Locator;
let documentationPage!: TuiDocumentationPagePO;
- let inputDateTime!: TuiInputDateTimePO;
test.beforeEach(async ({page}) => {
await tuiGoto(page, DemoRoute.InputDateTime);
documentationPage = new TuiDocumentationPagePO(page);
- example = documentationPage.apiPageExample;
-
- inputDateTime = new TuiInputDateTimePO(
- example.locator('tui-input-date-time'),
- );
});
test('With validator: enter incomplete date -> validator error', async () => {
- example = documentationPage.getExample('#with-validator');
- inputDateTime = new TuiInputDateTimePO(
+ const example = documentationPage.getExample('#with-validator');
+ const inputDateTime = new TuiInputDateTimePO(
example.locator('tui-input-date-time'),
);
@@ -343,5 +336,38 @@ test.describe('InputDateTime', () => {
{animations: 'allow'},
);
});
+
+ test.describe('with `input[tuiTextfieldLegacy]` inside', () => {
+ test('filler has no change detection problems', async () => {
+ const example = documentationPage.getExample('#base');
+ const inputDateTime = new TuiInputDateTimePO(
+ example.locator('tui-input-date-time'),
+ );
+
+ /**
+ * To ensure that example is not changed and
+ * still contains InputDateTime with projected
+ */
+ await expect(
+ inputDateTime.host.locator('input[tuiTextfieldLegacy]'),
+ ).toBeAttached();
+
+ await inputDateTime.textfield.focus();
+
+ await expect(inputDateTime.host).toHaveScreenshot(
+ '05-backspace-pressed-0-times.png',
+ );
+
+ for (let i = 1; i <= 8; i++) {
+ await inputDateTime.textfield.press('Backspace');
+
+ await expect(inputDateTime.host).toHaveScreenshot(
+ `05-backspace-pressed-${i}-times.png`,
+ );
+ }
+
+ await expect(inputDateTime.textfield).toHaveValue('');
+ });
+ });
});
});
diff --git a/projects/demo-playwright/tests/legacy/input-date/input-date.pw.spec.ts b/projects/demo-playwright/tests/legacy/input-date/input-date.pw.spec.ts
index 7be105ec7b6d..53ffa86804a1 100644
--- a/projects/demo-playwright/tests/legacy/input-date/input-date.pw.spec.ts
+++ b/projects/demo-playwright/tests/legacy/input-date/input-date.pw.spec.ts
@@ -12,6 +12,8 @@ import {TUI_PLAYWRIGHT_MOBILE_USER_AGENT} from '../../../playwright.options';
test.describe('InputDate', () => {
test.describe('Examples', () => {
+ let documentationPage!: TuiDocumentationPagePO;
+
test.use({
viewport: {
width: 450,
@@ -19,13 +21,16 @@ test.describe('InputDate', () => {
},
});
- test('correct filler display for size', async ({page}) => {
+ test.beforeEach(async ({page}) => {
await tuiGoto(page, DemoRoute.InputDate);
- const api = new TuiDocumentationPagePO(page);
- const example = api.getExample('#sizes');
+ documentationPage = new TuiDocumentationPagePO(page);
+ });
- await api.prepareBeforeScreenshot();
+ test('correct filler display for size', async ({page}) => {
+ const example = documentationPage.getExample('#sizes');
+
+ await documentationPage.prepareBeforeScreenshot();
for (const size of ['s', 'm', 'l']) {
const input = example
@@ -52,6 +57,37 @@ test.describe('InputDate', () => {
await expect(page).toHaveScreenshot(`01-04-input-date-${size}.png`);
}
});
+
+ test.describe('with `input[tuiTextfieldLegacy]` inside', () => {
+ test('filler has no change detection problems', async () => {
+ const example = documentationPage.getExample('#date-localization');
+ const inputDate = new TuiInputDatePO(example.locator('tui-input-date'));
+
+ /**
+ * To ensure that example is not changed and
+ * still contains InputDate with projected
+ */
+ await expect(
+ inputDate.host.locator('input[tuiTextfieldLegacy]'),
+ ).toBeAttached();
+
+ await inputDate.textfield.focus();
+
+ await expect(inputDate.host).toHaveScreenshot(
+ '14-backspace-pressed-0-times.png',
+ );
+
+ for (let i = 1; i <= 8; i++) {
+ await inputDate.textfield.press('Backspace');
+
+ await expect(inputDate.host).toHaveScreenshot(
+ `14-backspace-pressed-${i}-times.png`,
+ );
+ }
+
+ await expect(inputDate.textfield).toHaveValue('');
+ });
+ });
});
test.describe('API', () => {
diff --git a/projects/demo-playwright/utils/page-objects/input-date-range.po.ts b/projects/demo-playwright/utils/page-objects/input-date-range.po.ts
index 865ac46cbd87..d83b22a35f7a 100644
--- a/projects/demo-playwright/utils/page-objects/input-date-range.po.ts
+++ b/projects/demo-playwright/utils/page-objects/input-date-range.po.ts
@@ -15,7 +15,7 @@ export class TuiInputDateRangePO {
'[automation-id="tui-calendar-range__menu"]',
);
- constructor(private readonly host: Locator) {}
+ constructor(public readonly host: Locator) {}
public async getItems(): Promise {
const dataList = this.calendar.locator(
diff --git a/projects/demo-playwright/utils/page-objects/input-date.po.ts b/projects/demo-playwright/utils/page-objects/input-date.po.ts
index df59f891c556..f49a49b6ce0e 100644
--- a/projects/demo-playwright/utils/page-objects/input-date.po.ts
+++ b/projects/demo-playwright/utils/page-objects/input-date.po.ts
@@ -4,5 +4,5 @@ export class TuiInputDatePO {
public readonly textfield: Locator = this.host.getByRole('textbox');
public readonly calendar: Locator = this.host.page().locator('tui-calendar');
- constructor(private readonly host: Locator) {}
+ constructor(public readonly host: Locator) {}
}
diff --git a/projects/legacy/components/input-date-range/input-date-range.component.ts b/projects/legacy/components/input-date-range/input-date-range.component.ts
index 386d3568e5a2..582e9d067221 100644
--- a/projects/legacy/components/input-date-range/input-date-range.component.ts
+++ b/projects/legacy/components/input-date-range/input-date-range.component.ts
@@ -3,6 +3,7 @@ import {
Component,
inject,
Input,
+ signal,
ViewChild,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -86,6 +87,7 @@ export class TuiInputDateRangeComponent
private readonly mobileCalendar = inject(TUI_MOBILE_CALENDAR, {optional: true});
private readonly options = inject(TUI_INPUT_DATE_OPTIONS);
private readonly textfieldSize = inject(TUI_TEXTFIELD_SIZE);
+ private readonly nativeValue = signal('');
protected readonly dateTexts$ = inject(TUI_DATE_TEXTS);
protected override readonly valueTransformer = inject(
@@ -160,7 +162,7 @@ export class TuiInputDateRangeComponent
return value
? value.getFormattedDayRange(this.dateFormat.mode, this.dateFormat.separator)
- : nativeValue;
+ : nativeValue();
}
public get size(): TuiSizeL | TuiSizeS {
@@ -174,6 +176,8 @@ export class TuiInputDateRangeComponent
}
public onValueChange(value: string): void {
+ this.nativeValue.set(value);
+
if (this.control) {
this.control.updateValueAndValidity({emitEvent: false});
}
@@ -183,7 +187,7 @@ export class TuiInputDateRangeComponent
}
if (this.activePeriod) {
- this.nativeValue = '';
+ this.nativeValue.set('');
}
this.value =
@@ -201,7 +205,7 @@ export class TuiInputDateRangeComponent
this.focusInput();
if (!range) {
- this.nativeValue = '';
+ this.nativeValue.set('');
}
this.value = range;
@@ -209,7 +213,7 @@ export class TuiInputDateRangeComponent
public override writeValue(value: TuiDayRange | null): void {
super.writeValue(value);
- this.nativeValue = value ? this.computedValue : '';
+ this.nativeValue.set(value ? this.computedValue : '');
}
protected get computedMobile(): boolean {
@@ -269,16 +273,6 @@ export class TuiInputDateRangeComponent
return null;
}
- protected get nativeValue(): string {
- return this.nativeFocusableElement?.value || '';
- }
-
- protected set nativeValue(value: string) {
- if (this.nativeFocusableElement) {
- this.nativeFocusableElement.value = value;
- }
- }
-
protected getComputedRangeFiller(dateFiller: string): string {
return this.activePeriod ? '' : this.getDateRangeFiller(dateFiller);
}
@@ -299,12 +293,12 @@ export class TuiInputDateRangeComponent
if (
!focused &&
!this.itemSelected &&
- (this.nativeValue.length === DATE_FILLER_LENGTH ||
- this.nativeValue.length ===
+ (this.nativeValue().length === DATE_FILLER_LENGTH ||
+ this.nativeValue().length ===
DATE_FILLER_LENGTH + RANGE_SEPARATOR_CHAR.length)
) {
this.value = TuiDayRange.normalizeParse(
- this.nativeValue,
+ this.nativeValue(),
this.dateFormat.mode,
);
}
@@ -318,7 +312,7 @@ export class TuiInputDateRangeComponent
}
private get itemSelected(): boolean {
- return this.items.findIndex((item) => String(item) === this.nativeValue) !== -1;
+ return this.items.findIndex((item) => String(item) === this.nativeValue()) !== -1;
}
@tuiPure
diff --git a/projects/legacy/components/input-date-range/test/input-date-range.component.spec.ts b/projects/legacy/components/input-date-range/test/input-date-range.component.spec.ts
index 244f4d63fbac..48b149df4c9c 100644
--- a/projects/legacy/components/input-date-range/test/input-date-range.component.spec.ts
+++ b/projects/legacy/components/input-date-range/test/input-date-range.component.spec.ts
@@ -300,7 +300,17 @@ describe('InputDateRangeComponent', () => {
});
it('correctly sets stringify selected range via calendar', async () => {
- inputPO.sendTextAndBlur('12/01/2021-02/14/2022');
+ inputPO.sendText('12/01/2021-02/14/2022');
+ /**
+ * TODO
+ * Uncomment me to see [TypeError: Cannot read properties of undefined (reading 'addEventListener')]
+ * ___
+ * Stacktrace says that error happens inside `TUI_ACTIVE_ELEMENT`.
+ * Utility `tuiGetDocumentOrShadowRoot` returns `undefined`.
+ */
+ // inputPO.blur();
+
+ await fixture.whenStable();
clickOnTextfield();
@@ -535,17 +545,19 @@ describe('InputDateRangeComponent', () => {
expect(inputPO.value).toBe('12.09.2021 – 18.10.2021');
});
- it('transforms value which was programmatically patched', () => {
- testComponent.control.patchValue([
- new Date(1922, 11, 30),
- new Date(1991, 11, 26),
- ]);
+ it('transforms value which was programmatically patched', async () => {
+ const newDateRange = [new Date(1922, 11, 30), new Date(1991, 11, 26)] as [
+ Date,
+ Date,
+ ];
+
+ testComponent.control.patchValue(newDateRange);
+
+ fixture.detectChanges();
+ await fixture.whenStable();
expect(inputPO.value).toBe('30.12.1922 – 26.12.1991');
- expect(testComponent.control.value).toEqual([
- new Date(1922, 11, 30),
- new Date(1991, 11, 26),
- ]);
+ expect(testComponent.control.value).toEqual(newDateRange);
});
});
diff --git a/projects/legacy/components/input-date-time/input-date-time.component.ts b/projects/legacy/components/input-date-time/input-date-time.component.ts
index a432b5cf89ef..810f20f71c06 100644
--- a/projects/legacy/components/input-date-time/input-date-time.component.ts
+++ b/projects/legacy/components/input-date-time/input-date-time.component.ts
@@ -3,6 +3,7 @@ import {
Component,
inject,
Input,
+ signal,
ViewChild,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -79,6 +80,7 @@ export class TuiInputDateTimeComponent
private readonly textfieldSize = inject(TUI_TEXTFIELD_SIZE);
private month: TuiMonth | null = null;
private readonly timeMode$ = new BehaviorSubject('HH:MM');
+ private readonly nativeValue = signal('');
protected readonly timeTexts$ = inject(TUI_TIME_TEXTS);
protected readonly dateTexts$ = inject(TUI_DATE_TEXTS);
@@ -147,10 +149,10 @@ export class TuiInputDateTimeComponent
public get computedValue(): string {
const {value, nativeValue, timeMode} = this;
const [date, time] = value;
- const hasTimeInputChars = nativeValue.length > DATE_FILLER_LENGTH;
+ const hasTimeInputChars = nativeValue().length > DATE_FILLER_LENGTH;
if (!date || (!time && hasTimeInputChars)) {
- return nativeValue;
+ return nativeValue();
}
return this.getDateTimeString(date, time, timeMode);
@@ -164,11 +166,14 @@ export class TuiInputDateTimeComponent
public override writeValue(value: [TuiDay | null, TuiTime | null] | null): void {
super.writeValue(value);
- this.nativeValue =
- this.value && (this.value[0] || this.value[1]) ? this.computedValue : '';
+ this.nativeValue.set(
+ this.value && (this.value[0] || this.value[1]) ? this.computedValue : '',
+ );
}
public onValueChange(value: string): void {
+ this.nativeValue.set(value);
+
if (this.control) {
this.control.updateValueAndValidity({emitEvent: false});
}
@@ -258,18 +263,6 @@ export class TuiInputDateTimeComponent
);
}
- protected get nativeValue(): string {
- return this.nativeFocusableElement?.value || '';
- }
-
- protected set nativeValue(value: string) {
- if (!this.nativeFocusableElement) {
- return;
- }
-
- this.nativeFocusableElement.value = value;
- }
-
protected onClick(): void {
this.open = !this.open;
}
@@ -279,8 +272,12 @@ export class TuiInputDateTimeComponent
const newCaretIndex = DATE_FILLER_LENGTH + DATE_TIME_SEPARATOR.length;
this.value = [day, modifiedTime];
- this.updateNativeValue(day);
- this.nativeFocusableElement?.setSelectionRange(newCaretIndex, newCaretIndex);
+ this.nativeValue.update((x) =>
+ this.getDateTimeString(day, x.split(DATE_TIME_SEPARATOR)[1] || ''),
+ );
+ setTimeout(() =>
+ this.nativeFocusableElement?.setSelectionRange(newCaretIndex, newCaretIndex),
+ );
this.open = false;
}
@@ -302,19 +299,19 @@ export class TuiInputDateTimeComponent
timer(0)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
- this.nativeValue = this.trimTrailingSeparator(this.nativeValue);
+ this.nativeValue.update((x) => this.trimTrailingSeparator(x));
});
if (
this.value[0] === null ||
this.value[1] !== null ||
- this.nativeValue.length === this.fillerLength ||
+ this.nativeValue().length === this.fillerLength ||
this.timeMode === 'HH:MM'
) {
return;
}
- const [, time] = this.nativeValue.split(DATE_TIME_SEPARATOR);
+ const [, time] = this.nativeValue().split(DATE_TIME_SEPARATOR);
if (!time) {
return;
@@ -391,12 +388,6 @@ export class TuiInputDateTimeComponent
: dateString;
}
- private updateNativeValue(day: TuiDay): void {
- const time = this.nativeValue.split(DATE_TIME_SEPARATOR)[1] || '';
-
- this.nativeValue = this.getDateTimeString(day, time);
- }
-
private clampTime(time: TuiTime, day: TuiDay): TuiTime {
const {computedMin, computedMax} = this;
diff --git a/projects/legacy/components/input-date-time/test/input-date-time.component.spec.ts b/projects/legacy/components/input-date-time/test/input-date-time.component.spec.ts
index ad6908ab82b5..67b026ac5f9e 100644
--- a/projects/legacy/components/input-date-time/test/input-date-time.component.spec.ts
+++ b/projects/legacy/components/input-date-time/test/input-date-time.component.spec.ts
@@ -421,9 +421,11 @@ describe('InputDateTime', () => {
expect(inputPO.value).toBe('17.03.2022, 12:11');
});
- it('transforms value which was programmatically patched', () => {
+ it('transforms value which was programmatically patched', async () => {
component.control.patchValue('09.05.1945, 00:43');
+ await fixture.whenStable();
+
expect(inputPO.value).toBe('09.05.1945, 00:43');
expect(component.control.value).toBe('09.05.1945, 00:43');
});
diff --git a/projects/legacy/components/input-date/input-date.component.ts b/projects/legacy/components/input-date/input-date.component.ts
index 5e59f8224de8..11ad73c07490 100644
--- a/projects/legacy/components/input-date/input-date.component.ts
+++ b/projects/legacy/components/input-date/input-date.component.ts
@@ -4,6 +4,7 @@ import {
Component,
inject,
Input,
+ signal,
ViewChild,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -83,6 +84,7 @@ export class TuiInputDateComponent
private readonly textfieldSize = inject(TUI_TEXTFIELD_SIZE);
private readonly mobileCalendar = inject(TUI_MOBILE_CALENDAR, {optional: true});
private month: TuiMonth | null = null;
+ private readonly nativeValue = signal('');
@Input()
public min: TuiDay | null = this.options.min;
@@ -142,16 +144,6 @@ export class TuiInputDateComponent
return !!this.textfield?.focused;
}
- public get nativeValue(): string {
- return this.nativeFocusableElement?.value || '';
- }
-
- public set nativeValue(value: string) {
- if (this.nativeFocusableElement) {
- this.nativeFocusableElement.value = value;
- }
- }
-
public get computedValue(): string {
const {value, nativeValue, activeItem} = this;
@@ -161,10 +153,12 @@ export class TuiInputDateComponent
return value
? value.toString(this.dateFormat.mode, this.dateFormat.separator)
- : nativeValue;
+ : nativeValue();
}
public onValueChange(value: string): void {
+ this.nativeValue.set(value);
+
if (this.control) {
this.control.updateValueAndValidity({emitEvent: false});
}
@@ -174,7 +168,7 @@ export class TuiInputDateComponent
}
if (this.activeItem) {
- this.nativeValue = '';
+ this.nativeValue.set('');
}
this.value =
@@ -190,7 +184,7 @@ export class TuiInputDateComponent
public override writeValue(value: TuiDay | null): void {
super.writeValue(value);
- this.nativeValue = value ? this.computedValue : '';
+ this.nativeValue.set(value ? this.computedValue : '');
}
protected get size(): TuiSizeL | TuiSizeS {
diff --git a/projects/legacy/components/input-date/input-date.directive.ts b/projects/legacy/components/input-date/input-date.directive.ts
index 1e2403b0d57c..6d1509b278ef 100644
--- a/projects/legacy/components/input-date/input-date.directive.ts
+++ b/projects/legacy/components/input-date/input-date.directive.ts
@@ -29,10 +29,6 @@ export class TuiInputDateDirective extends AbstractTuiTextfieldHost {
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Test {
+ @Input()
+ public value: TuiDay | null = new TuiDay(2017, 2, 1);
+
@ViewChild(TuiInputDateComponent)
public readonly component!: TuiInputDateComponent;
@@ -62,8 +65,6 @@ describe('InputDate', () => {
public items: TuiNamedDay[] = [];
- public value: TuiDay | null = new TuiDay(2017, 2, 1);
-
public size: TuiSizeL | TuiSizeS = 'm';
public hintContent: string | null = 'prompt';
@@ -192,10 +193,10 @@ describe('InputDate', () => {
});
describe('With items', () => {
- beforeEach(() => {
+ beforeEach(async () => {
testComponent.items = [
new TuiNamedDay(
- new TuiDay(2017, 2, 1),
+ new TuiDay(2017, 2, 5),
'Current',
TuiDay.currentLocal(),
),
@@ -205,11 +206,13 @@ describe('InputDate', () => {
TuiDay.currentLocal(),
),
];
+
+ fixture.detectChanges();
+ await fixture.whenStable();
});
it('when entering item date, input shows named date', async () => {
- inputPO.sendText('01.02.2017');
-
+ inputPO.sendText('05.03.2017');
await fixture.whenStable();
expect(inputPO.value).toBe('Current');
@@ -225,7 +228,7 @@ describe('InputDate', () => {
});
it('when ngModel value updated with item date, input shows named date', async () => {
- testComponent.value = TUI_LAST_DAY.append({year: -1});
+ fixture.componentRef.setInput('value', TUI_LAST_DAY.append({year: -1}));
fixture.detectChanges();
await fixture.whenStable();
@@ -238,7 +241,7 @@ describe('InputDate', () => {
expect(getCalendar()).not.toBeNull();
- const calendarCell = getCalendarCell(1);
+ const calendarCell = getCalendarCell(5);
calendarCell?.nativeElement.click();
@@ -479,9 +482,12 @@ describe('InputDate', () => {
expect(testComponent.control.value).toEqual(new Date(2022, 0, 20));
});
- it('transforms value which was programmatically patched', () => {
+ it('transforms value which was programmatically patched', async () => {
testComponent.control.patchValue(new Date(1991, 11, 26));
+ fixture.detectChanges();
+ await fixture.whenStable();
+
expect(inputPO.value).toBe('26.12.1991');
expect(testComponent.control.value).toEqual(new Date(1991, 11, 26));
});