From 8079b6a9381d552150fc9d254f4040ae692139fe Mon Sep 17 00:00:00 2001 From: reverie3 <37984867+reverie3@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:00:08 +0300 Subject: [PATCH] feat(kit): create TUI_TIME_VALUE_TRANSFORMER for tui-input-time component --- projects/core/tokens/data-list-host.ts | 2 +- .../input-time/examples/6/index.html | 46 ++---- .../components/input-time/examples/6/index.ts | 36 +++-- .../input-time/examples/7/index.html | 39 +++++ .../components/input-time/examples/7/index.ts | 28 ++++ .../input-time/input-time.component.ts | 5 + .../input-time/input-time.module.ts | 2 + .../input-time/input-time.template.html | 36 ++++- .../input-time/input-time.component.ts | 16 +- .../test/input-time.component.spec.ts | 143 +++++++++++++++--- .../tokens/date-inputs-value-transformers.ts | 7 + 11 files changed, 281 insertions(+), 79 deletions(-) create mode 100644 projects/demo/src/modules/components/input-time/examples/7/index.html create mode 100644 projects/demo/src/modules/components/input-time/examples/7/index.ts diff --git a/projects/core/tokens/data-list-host.ts b/projects/core/tokens/data-list-host.ts index 3237b4cd3acc..74f8878e0752 100644 --- a/projects/core/tokens/data-list-host.ts +++ b/projects/core/tokens/data-list-host.ts @@ -8,7 +8,7 @@ export const TUI_DATA_LIST_HOST = new InjectionToken>( '[TUI_DATA_LIST_HOST]', ); -export function tuiAsDataListHost(useExisting: Type>): Provider { +export function tuiAsDataListHost(useExisting: Type>): Provider { return { provide: TUI_DATA_LIST_HOST, useExisting, diff --git a/projects/demo/src/modules/components/input-time/examples/6/index.html b/projects/demo/src/modules/components/input-time/examples/6/index.html index 50c9a8681edb..96865373e900 100644 --- a/projects/demo/src/modules/components/input-time/examples/6/index.html +++ b/projects/demo/src/modules/components/input-time/examples/6/index.html @@ -1,39 +1,11 @@ -
- - Choose a time - + Input time + - - Choose a time - - - - Choose a time - - - - Choose a time - -
+

Stringified control value:

+

+ {{ control.value }} +

diff --git a/projects/demo/src/modules/components/input-time/examples/6/index.ts b/projects/demo/src/modules/components/input-time/examples/6/index.ts index 82104db7c914..30d37751b24e 100644 --- a/projects/demo/src/modules/components/input-time/examples/6/index.ts +++ b/projects/demo/src/modules/components/input-time/examples/6/index.ts @@ -1,9 +1,22 @@ import {Component} from '@angular/core'; -import {FormControl, FormGroup} from '@angular/forms'; +import {FormControl} from '@angular/forms'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; -import {TuiTime} from '@taiga-ui/cdk'; -import {tuiCreateTimePeriods, tuiInputTimeOptionsProvider} from '@taiga-ui/kit'; +import {AbstractTuiValueTransformer, TuiTime} from '@taiga-ui/cdk'; +import {TUI_TIME_VALUE_TRANSFORMER, tuiCreateTimePeriods} from '@taiga-ui/kit'; + +class ExampleTimeTransformer extends AbstractTuiValueTransformer< + TuiTime | null, + string | null +> { + fromControlValue(controlValue: string): TuiTime | null { + return controlValue ? TuiTime.fromString(controlValue) : null; + } + + toControlValue(time: TuiTime | null): string { + return time ? time.toString() : ''; + } +} @Component({ selector: 'tui-input-time-example-6', @@ -11,18 +24,13 @@ import {tuiCreateTimePeriods, tuiInputTimeOptionsProvider} from '@taiga-ui/kit'; encapsulation, changeDetection, providers: [ - tuiInputTimeOptionsProvider({ - nativePicker: true, - }), + { + provide: TUI_TIME_VALUE_TRANSFORMER, + useClass: ExampleTimeTransformer, + }, ], }) export class TuiInputTimeExample6 { - readonly testForm = new FormGroup({ - testValue: new FormControl(new TuiTime(10, 30)), - testValue2: new FormControl(new TuiTime(10, 30, 0)), - testValue3: new FormControl(new TuiTime(14, 30)), - testValue4: new FormControl(new TuiTime(10, 30, 0)), - }); - - readonly items = tuiCreateTimePeriods(14, 16, [0, 30]); + readonly control = new FormControl(''); + readonly items = tuiCreateTimePeriods(); } diff --git a/projects/demo/src/modules/components/input-time/examples/7/index.html b/projects/demo/src/modules/components/input-time/examples/7/index.html new file mode 100644 index 000000000000..50c9a8681edb --- /dev/null +++ b/projects/demo/src/modules/components/input-time/examples/7/index.html @@ -0,0 +1,39 @@ +
+ + Choose a time + + + + Choose a time + + + + Choose a time + + + + Choose a time + +
diff --git a/projects/demo/src/modules/components/input-time/examples/7/index.ts b/projects/demo/src/modules/components/input-time/examples/7/index.ts new file mode 100644 index 000000000000..18b6be0f6e9d --- /dev/null +++ b/projects/demo/src/modules/components/input-time/examples/7/index.ts @@ -0,0 +1,28 @@ +import {Component} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; +import {TuiTime} from '@taiga-ui/cdk'; +import {tuiCreateTimePeriods, tuiInputTimeOptionsProvider} from '@taiga-ui/kit'; + +@Component({ + selector: 'tui-input-time-example-7', + templateUrl: './index.html', + encapsulation, + changeDetection, + providers: [ + tuiInputTimeOptionsProvider({ + nativePicker: true, + }), + ], +}) +export class TuiInputTimeExample7 { + readonly testForm = new FormGroup({ + testValue: new FormControl(new TuiTime(10, 30)), + testValue2: new FormControl(new TuiTime(10, 30, 0)), + testValue3: new FormControl(new TuiTime(14, 30)), + testValue4: new FormControl(new TuiTime(10, 30, 0)), + }); + + readonly items = tuiCreateTimePeriods(14, 16, [0, 30]); +} diff --git a/projects/demo/src/modules/components/input-time/input-time.component.ts b/projects/demo/src/modules/components/input-time/input-time.component.ts index 7974089f2508..15ccd25e4b32 100644 --- a/projects/demo/src/modules/components/input-time/input-time.component.ts +++ b/projects/demo/src/modules/components/input-time/input-time.component.ts @@ -65,6 +65,11 @@ export class ExampleTuiInputTimeComponent extends AbstractExampleTuiControl { HTML: import('./examples/6/index.html?raw'), }; + readonly example7: TuiDocExample = { + TypeScript: import('./examples/7/index.ts?raw'), + HTML: import('./examples/7/index.html?raw'), + }; + override cleaner = false; control = new FormControl(new TuiTime(15, 30), Validators.required); diff --git a/projects/demo/src/modules/components/input-time/input-time.module.ts b/projects/demo/src/modules/components/input-time/input-time.module.ts index 4d91799f14ca..e97f0d9dc4b6 100644 --- a/projects/demo/src/modules/components/input-time/input-time.module.ts +++ b/projects/demo/src/modules/components/input-time/input-time.module.ts @@ -24,6 +24,7 @@ import {TuiInputTimeExample3} from './examples/3'; import {TuiInputTimeExample4} from './examples/4'; import {TuiInputTimeExample5} from './examples/5'; import {TuiInputTimeExample6} from './examples/6'; +import {TuiInputTimeExample7} from './examples/7'; import {ExampleTuiInputTimeComponent} from './input-time.component'; @NgModule({ @@ -52,6 +53,7 @@ import {ExampleTuiInputTimeComponent} from './input-time.component'; TuiInputTimeExample4, TuiInputTimeExample5, TuiInputTimeExample6, + TuiInputTimeExample7, ], exports: [ExampleTuiInputTimeComponent], }) diff --git a/projects/demo/src/modules/components/input-time/input-time.template.html b/projects/demo/src/modules/components/input-time/input-time.template.html index f012e10d041b..d3bf45291307 100644 --- a/projects/demo/src/modules/components/input-time/input-time.template.html +++ b/projects/demo/src/modules/components/input-time/input-time.template.html @@ -4,10 +4,32 @@ type="components" > -

+

InputTime allows input time in HH:MM format -

+
+ +

DI-tokens for input-configurations:

+
+
+ TUI_TIME_VALUE_TRANSFORMER +
+
+ custom format of control output ( + TuiTime | null + is default). +

+ + See example + + with string as control's output. +

+
+
+ + + + Please note that iOS Safari doesn't support native picker in modes other than HH:MM diff --git a/projects/kit/components/input-time/input-time.component.ts b/projects/kit/components/input-time/input-time.component.ts index a39a2976523c..0b25561be901 100644 --- a/projects/kit/components/input-time/input-time.component.ts +++ b/projects/kit/components/input-time/input-time.component.ts @@ -15,6 +15,7 @@ import {MaskitoOptions} from '@maskito/core'; import {maskitoTimeOptionsGenerator} from '@maskito/kit'; import { AbstractTuiNullableControl, + AbstractTuiValueTransformer, ALWAYS_FALSE_HANDLER, TUI_IS_IOS, TUI_IS_MOBILE, @@ -23,6 +24,7 @@ import { tuiAsFocusableItemAccessor, TuiBooleanHandler, TuiFocusableElementAccessor, + TuiIdentityMatcher, tuiIsElement, tuiIsInput, tuiIsNativeFocused, @@ -43,7 +45,7 @@ import { } from '@taiga-ui/core'; import {TUI_SELECT_OPTION} from '@taiga-ui/kit/components/select-option'; import {FIXED_DROPDOWN_CONTROLLER_PROVIDER} from '@taiga-ui/kit/providers'; -import {TUI_TIME_TEXTS} from '@taiga-ui/kit/tokens'; +import {TUI_TIME_TEXTS, TUI_TIME_VALUE_TRANSFORMER} from '@taiga-ui/kit/tokens'; import {Observable, timer} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; @@ -104,8 +106,11 @@ export class TuiInputTimeComponent @Inject(TUI_IS_IOS) private readonly isIos: boolean, @Inject(TUI_TEXTFIELD_SIZE) private readonly textfieldSize: TuiTextfieldSizeDirective, + @Optional() + @Inject(TUI_TIME_VALUE_TRANSFORMER) + override readonly valueTransformer: AbstractTuiValueTransformer | null, ) { - super(control, cdr); + super(control, cdr, valueTransformer); } @HostBinding('attr.data-size') @@ -190,6 +195,13 @@ export class TuiInputTimeComponent this.open = !this.open; } + readonly identityMatcher: TuiIdentityMatcher = ( + controlValue: TuiTime, + dropdownValue: TuiTime, + ) => + controlValue instanceof TuiTime && + controlValue.valueOf() === dropdownValue.valueOf(); + onValueChange(value: string): void { this.open = !!this.items.length; diff --git a/projects/kit/components/input-time/test/input-time.component.spec.ts b/projects/kit/components/input-time/test/input-time.component.spec.ts index c96e7d7bf606..62a861d80532 100644 --- a/projects/kit/components/input-time/test/input-time.component.spec.ts +++ b/projects/kit/components/input-time/test/input-time.component.spec.ts @@ -1,9 +1,9 @@ -import {Component, DebugElement, ViewChild} from '@angular/core'; +import {Component, DebugElement, Type, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {TuiTime} from '@taiga-ui/cdk'; +import {AbstractTuiValueTransformer, TuiTime} from '@taiga-ui/cdk'; import { TuiHintModule, TuiRootModule, @@ -12,6 +12,7 @@ import { TuiTextfieldControllerModule, } from '@taiga-ui/core'; import {TuiInputTimeComponent, TuiInputTimeModule} from '@taiga-ui/kit'; +import {TUI_TIME_VALUE_TRANSFORMER} from '@taiga-ui/kit/tokens'; import {tuiCreateKeyboardEvent, TuiNativeInputPO, TuiPageObject} from '@taiga-ui/testing'; const TIMES = [ @@ -66,20 +67,22 @@ describe('InputTime', () => { return pageObject.getByAutomationId('tui-input-time__dropdown'); } - beforeEach(async () => { - TestBed.configureTestingModule({ - imports: [ - TuiRootModule, - TuiInputTimeModule, - ReactiveFormsModule, - NoopAnimationsModule, - TuiTextfieldControllerModule, - TuiHintModule, - ], - declarations: [TestComponent], - }); - await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestComponent); + const meta = { + imports: [ + TuiRootModule, + TuiInputTimeModule, + ReactiveFormsModule, + NoopAnimationsModule, + TuiTextfieldControllerModule, + TuiHintModule, + ], + declarations: [TestComponent], + }; + + const initializeEnvironment = async ( + componentType: Type = TestComponent, + ): Promise => { + fixture = TestBed.createComponent(componentType); pageObject = new TuiPageObject(fixture); testComponent = fixture.componentInstance; fixture.detectChanges(); @@ -88,22 +91,31 @@ describe('InputTime', () => { input = fixture.debugElement.query(By.css('input')).nativeElement; await fixture.whenStable(); fixture.detectChanges(); - }); + }; describe('Initial value', () => { + beforeEach(async () => { + TestBed.configureTestingModule(meta); + await TestBed.compileComponents(); + await initializeEnvironment(); + }); + it('The value in the field is formatted by mask', async () => { await fixture.whenStable(); expect(input.value).toBe('12:30'); }); it('The initial value in the formControl is issued as an object with the hours and minutes properties', () => { - expect(testComponent.control.value.hours).toBe(12); - expect(testComponent.control.value.minutes).toBe(30); + expect(testComponent.control.value).toEqual(new TuiTime(12, 30)); }); }); describe('The value in the formControl changes outside', () => { - beforeEach(() => { + beforeEach(async () => { + TestBed.configureTestingModule(meta); + await TestBed.compileComponents(); + await initializeEnvironment(); + testComponent.control.setValue(new TuiTime(22, 30)); fixture.detectChanges(); }); @@ -122,6 +134,12 @@ describe('InputTime', () => { }); describe('Short time input (less than 5 characters, including colon)', () => { + beforeEach(async () => { + TestBed.configureTestingModule(meta); + await TestBed.compileComponents(); + await initializeEnvironment(); + }); + it('The value of formControl is passed null', () => { component.onValueChange('11:1'); fixture.detectChanges(); @@ -130,7 +148,13 @@ describe('InputTime', () => { }); describe('Keyboard control', () => { - beforeEach(async () => fixture.whenStable()); + beforeEach(async () => { + TestBed.configureTestingModule(meta); + await TestBed.compileComponents(); + await initializeEnvironment(); + + await fixture.whenStable(); + }); it('If the cursor is at position 0, then pressing UP increases the hour by 1', () => { input.focus(); @@ -188,7 +212,11 @@ describe('InputTime', () => { }); describe('Drop-down list', () => { - beforeEach(() => { + beforeEach(async () => { + TestBed.configureTestingModule(meta); + await TestBed.compileComponents(); + await initializeEnvironment(); + testComponent.items = TIMES; fixture.detectChanges(); @@ -277,4 +305,75 @@ describe('InputTime', () => { }); }); }); + + describe('InputTime + TUI_TIME_VALUE_TRANSFORMER', () => { + class TestTransformer extends AbstractTuiValueTransformer< + TuiTime | null, + string + > { + fromControlValue(controlValue: string): TuiTime | null { + return controlValue ? TuiTime.fromString(controlValue) : null; + } + + toControlValue(componentValue: TuiTime | null): string { + return componentValue ? componentValue.toString() : ''; + } + } + + class TransformerTestComponent extends TestComponent { + override control = new FormControl('12:30'); + } + + beforeEach(async () => { + TestBed.configureTestingModule({ + ...meta, + providers: [ + {provide: TUI_TIME_VALUE_TRANSFORMER, useClass: TestTransformer}, + ], + declarations: [TransformerTestComponent], + }); + await TestBed.compileComponents(); + await initializeEnvironment(TransformerTestComponent); + + testComponent.items = TIMES; + fixture.detectChanges(); + }); + + it('correctly transforms initial value', () => { + expect(inputPO.value).toBe('12:30'); + expect(testComponent.control.value).toBe('12:30'); + }); + + it('transforms typed value', () => { + inputPO.sendText('12:00'); + fixture.detectChanges(); + + expect(inputPO.value).toBe('12:00'); + expect(testComponent.control.value).toBe('12:00'); + }); + + it('transforms empty value', () => { + inputPO.sendText(''); + fixture.detectChanges(); + + expect(testComponent.control.value).toBe(''); + }); + + it('transforms selected value', () => { + inputPO.sendText('03'); + fixture.detectChanges(); + pageObject.getByAutomationId('tui-input-time__item')!.nativeElement.click(); + + expect(testComponent.control.value).toBe('03:00'); + }); + + it('selected item has check mark', () => { + inputPO.sendText('03:00'); + fixture.detectChanges(); + + expect( + pageObject.getByAutomationId('tui-select-option__checkmark'), + ).toBeTruthy(); + }); + }); }); diff --git a/projects/kit/tokens/date-inputs-value-transformers.ts b/projects/kit/tokens/date-inputs-value-transformers.ts index a47fbfe35978..f3d1599bcf3f 100644 --- a/projects/kit/tokens/date-inputs-value-transformers.ts +++ b/projects/kit/tokens/date-inputs-value-transformers.ts @@ -23,3 +23,10 @@ export const TUI_DATE_RANGE_VALUE_TRANSFORMER = new InjectionToken< export const TUI_DATE_TIME_VALUE_TRANSFORMER = new InjectionToken< TuiControlValueTransformer<[TuiDay | null, TuiTime | null]> >('[TUI_DATE_TIME_VALUE_TRANSFORMER]'); + +/** + * Control value transformer for InputTime component + */ +export const TUI_TIME_VALUE_TRANSFORMER = new InjectionToken< + TuiControlValueTransformer +>('[TUI_TIME_VALUE_TRANSFORMER]');