diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/index.ts b/projects/addon-mobile/components/mobile-calendar-dialog/index.ts index 0ffa05f02e24..86a4f5d4ff47 100644 --- a/projects/addon-mobile/components/mobile-calendar-dialog/index.ts +++ b/projects/addon-mobile/components/mobile-calendar-dialog/index.ts @@ -1,2 +1,3 @@ export * from './mobile-calendar-dialog.component'; export * from './mobile-calendar-dialog.module'; +export * from './mobile-calendar-dropdown.component'; diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.module.ts b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.module.ts index 4b901e645e6c..fb32e690a2ac 100644 --- a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.module.ts +++ b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.module.ts @@ -1,16 +1,18 @@ import {NgModule} from '@angular/core'; import {TuiMobileCalendarModule} from '@taiga-ui/addon-mobile/components/mobile-calendar'; +import {TuiActiveZoneModule} from '@taiga-ui/cdk'; import {TUI_MOBILE_CALENDAR} from '@taiga-ui/kit'; import {TuiMobileCalendarDialogComponent} from './mobile-calendar-dialog.component'; +import {TuiMobileCalendarDropdownComponent} from './mobile-calendar-dropdown.component'; @NgModule({ - imports: [TuiMobileCalendarModule], - declarations: [TuiMobileCalendarDialogComponent], + imports: [TuiMobileCalendarModule, TuiActiveZoneModule], + declarations: [TuiMobileCalendarDialogComponent, TuiMobileCalendarDropdownComponent], providers: [ { provide: TUI_MOBILE_CALENDAR, - useValue: TuiMobileCalendarDialogComponent, + useValue: TuiMobileCalendarDropdownComponent, }, ], exports: [TuiMobileCalendarDialogComponent], diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.component.ts b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.component.ts new file mode 100644 index 000000000000..8848c8283e41 --- /dev/null +++ b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.component.ts @@ -0,0 +1,108 @@ +import { + ChangeDetectionStrategy, + Component, + HostBinding, + Inject, + Optional, +} from '@angular/core'; +import {TuiKeyboardService} from '@taiga-ui/addon-mobile/services'; +import { + ALWAYS_FALSE_HANDLER, + TUI_FIRST_DAY, + TUI_LAST_DAY, + TuiActiveZoneDirective, +} from '@taiga-ui/cdk'; +import { + TUI_ANIMATIONS_DURATION, + tuiFadeIn, + TuiHostedDropdownComponent, + tuiSlideInTop, +} from '@taiga-ui/core'; +import { + TuiInputDateComponent, + TuiInputDateMultiComponent, + TuiInputDateRangeComponent, +} from '@taiga-ui/kit'; + +@Component({ + selector: 'tui-mobile-calendar-dropdown', + templateUrl: './mobile-calendar-dropdown.template.html', + styleUrls: ['./mobile-calendar-dropdown.style.less'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [tuiSlideInTop, tuiFadeIn], +}) +export class TuiMobileCalendarDropdownComponent { + @HostBinding('@tuiSlideInTop') + @HostBinding('@tuiFadeIn') + readonly animation = { + value: '', + params: { + start: '100vh', + duration: this.duration, + }, + } as const; + + readonly min = + this.single?.min || + this.multi?.min || + this.range?.maxLengthMapper( + this.range.computedMin, + this.range.value, + this.range.maxLength, + true, + ) || + TUI_FIRST_DAY; + + readonly max = + this.single?.max || + this.multi?.max || + this.range?.maxLengthMapper( + this.range.computedMax, + this.range.value, + this.range.maxLength, + false, + ) || + TUI_LAST_DAY; + + readonly disabledItemHandler = + this.single?.disabledItemHandler || + this.multi?.disabledItemHandler || + this.range?.disabledItemHandler || + ALWAYS_FALSE_HANDLER; + + constructor( + @Inject(TuiActiveZoneDirective) readonly zone: TuiActiveZoneDirective, + @Inject(TUI_ANIMATIONS_DURATION) private readonly duration: number, + @Optional() + @Inject(TuiInputDateComponent) + readonly single: TuiInputDateComponent | null, + @Optional() + @Inject(TuiInputDateMultiComponent) + readonly multi: TuiInputDateMultiComponent | null, + @Optional() + @Inject(TuiInputDateRangeComponent) + private readonly range: TuiInputDateRangeComponent | null, + @Inject(TuiHostedDropdownComponent) + private readonly dropdown: TuiHostedDropdownComponent, + @Inject(TuiKeyboardService) + private readonly keyboard: TuiKeyboardService, + ) { + this.keyboard.hide(); + } + + close(): void { + this.dropdown.computedHost.focus(); + this.dropdown.updateOpen(false); + this.keyboard.show(); + } + + confirm(value: any): void { + const control = this.single || this.multi || this.range; + + if (control) { + control.value = value; + } + + this.close(); + } +} diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.style.less b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.style.less new file mode 100644 index 000000000000..8c9b17060db1 --- /dev/null +++ b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.style.less @@ -0,0 +1,10 @@ +@import '@taiga-ui/core/styles/taiga-ui-local'; + +/* stylelint-disable */ +:host { + .fullsize(fixed); + background: var(--tui-elevation-01); + box-shadow: + 0 10rem var(--tui-elevation-01), + 0 -90vh 1rem 2rem rgba(0, 0, 0, 0.75); +} diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.template.html b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.template.html new file mode 100644 index 000000000000..7a9f0c4e5f20 --- /dev/null +++ b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dropdown.template.html @@ -0,0 +1,10 @@ + diff --git a/projects/core/animations/animations.ts b/projects/core/animations/animations.ts index 3c19e29d4cab..da2bb594f0a6 100644 --- a/projects/core/animations/animations.ts +++ b/projects/core/animations/animations.ts @@ -404,7 +404,7 @@ export const tuiSlideInTop = trigger('tuiSlideInTop', [ transition( ':leave', [ - style({transform: 'translate3d(0,{{end}},0)'}), + style({transform: 'translate3d(0,{{end}},0)', pointerEvents: 'none'}), animate(TRANSITION, style({transform: 'translate3d(0,{{start}},0)'})), ], {params: {end: 0, start: '100%', duration: 300}}, diff --git a/projects/core/styles/mixins/mixins.less b/projects/core/styles/mixins/mixins.less index 3a6bcb1f62e0..35882ecfd00b 100644 --- a/projects/core/styles/mixins/mixins.less +++ b/projects/core/styles/mixins/mixins.less @@ -1,3 +1,5 @@ +@import 'browsers'; + .interactive(@ruleset) { // TODO switch to :is after Safari 14 and FF 78 support &:-webkit-any(a, button, select, textarea, input, label) { @@ -132,6 +134,12 @@ appearance: none; word-break: keep-all; -webkit-text-fill-color: currentColor; // for Safari + + .ios-only({ + &:active { + font-size: 1rem; + } + }); } .visually-hidden() { diff --git a/projects/core/styles/mixins/mixins.scss b/projects/core/styles/mixins/mixins.scss index ad388b617589..8b329553d900 100644 --- a/projects/core/styles/mixins/mixins.scss +++ b/projects/core/styles/mixins/mixins.scss @@ -1,3 +1,5 @@ +@import 'browsers'; + @mixin interactive { // TODO switch to :is after Safari 14 and FF 78 support &:-webkit-any(a, button, select, textarea, input) { @@ -132,6 +134,12 @@ appearance: none; word-break: keep-all; -webkit-text-fill-color: currentColor; // for Safari + + @include ios-only { + &:active { + font-size: 1rem; + } + } } @mixin visually-hidden() { diff --git a/projects/core/styles/mixins/textfield.less b/projects/core/styles/mixins/textfield.less index eba3826e924c..6892a88dfa3c 100644 --- a/projects/core/styles/mixins/textfield.less +++ b/projects/core/styles/mixins/textfield.less @@ -60,6 +60,10 @@ text-transform: inherit; resize: none; + &[inputMode='none'] { + caret-color: transparent; + } + /* stylelint-disable */ /* Safari autofill icons */ //noinspection CssInvalidPseudoSelector diff --git a/projects/core/styles/mixins/textfield.scss b/projects/core/styles/mixins/textfield.scss index b5eb51659561..733eb7586b55 100644 --- a/projects/core/styles/mixins/textfield.scss +++ b/projects/core/styles/mixins/textfield.scss @@ -61,6 +61,10 @@ $line-height-l: 1.25rem; text-transform: inherit; resize: none; + &[inputMode='none'] { + caret-color: transparent; + } + /* stylelint-disable */ /* Safari autofill icons */ //noinspection CssInvalidPseudoSelector diff --git a/projects/demo-playwright/tests/addon-mobile/mobile-calendar/input-date.spec.ts b/projects/demo-playwright/tests/addon-mobile/mobile-calendar/input-date.spec.ts index 032bf997f400..925cc010ed07 100644 --- a/projects/demo-playwright/tests/addon-mobile/mobile-calendar/input-date.spec.ts +++ b/projects/demo-playwright/tests/addon-mobile/mobile-calendar/input-date.spec.ts @@ -19,7 +19,7 @@ test.describe('InputDate and mobile user agent', () => { .locator('tui-input-date-range .t-icons') .click(); - await page.waitForSelector('tui-dialog', {state: 'visible'}); + await page.waitForSelector('tui-mobile-calendar-dropdown', {state: 'visible'}); await expect(page).toHaveScreenshot('01-input-date-range-mobile-1.png'); await page.locator(november).nth(0).click(); @@ -42,7 +42,7 @@ test.describe('InputDate and mobile user agent', () => { .locator('tui-input-date[multiple] .t-icons') .click(); - await page.waitForSelector('tui-dialog', {state: 'visible'}); + await page.waitForSelector('tui-mobile-calendar-dropdown', {state: 'visible'}); await expect(page).toHaveScreenshot('02-input-date-range-mobile-1.png'); await page.locator(november).nth(0).click(); @@ -72,7 +72,7 @@ test.describe('InputDate and mobile user agent', () => { .locator('tui-input-date .t-icons') .click(); - await page.waitForSelector('tui-dialog', {state: 'visible'}); + await page.waitForSelector('tui-mobile-calendar-dropdown', {state: 'visible'}); await expect(page).toHaveScreenshot('03-input-date-range-mobile-1.png'); await page.locator(november).nth(0).click(); diff --git a/projects/demo/src/modules/components/input-date/examples/2/index.html b/projects/demo/src/modules/components/input-date/examples/2/index.html index a4b0c8c88411..8379b6642101 100644 --- a/projects/demo/src/modules/components/input-date/examples/2/index.html +++ b/projects/demo/src/modules/components/input-date/examples/2/index.html @@ -22,4 +22,21 @@ > Choose a date +

+ + + + + Choose a date + + + +

diff --git a/projects/demo/src/modules/components/input-date/input-date.module.ts b/projects/demo/src/modules/components/input-date/input-date.module.ts index a9fc4e7278f1..ade32f9982ce 100644 --- a/projects/demo/src/modules/components/input-date/input-date.module.ts +++ b/projects/demo/src/modules/components/input-date/input-date.module.ts @@ -4,11 +4,13 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; import {TuiMobileCalendarDialogModule} from '@taiga-ui/addon-mobile'; +import {TuiActiveZoneModule} from '@taiga-ui/cdk'; import { TuiButtonModule, TuiDropdownModule, TuiErrorModule, TuiHintModule, + TuiHostedDropdownModule, TuiLinkModule, TuiNotificationModule, TuiTextfieldControllerModule, @@ -50,6 +52,8 @@ import {ExampleTuiInputDateComponent} from './input-date.component'; TuiNotificationModule, RouterModule.forChild(tuiGenerateRoutes(ExampleTuiInputDateComponent)), TuiDropdownModule, + TuiHostedDropdownModule, + TuiActiveZoneModule, ], declarations: [ ExampleTuiInputDateComponent, diff --git a/projects/kit/components/accordion/accordion.style.less b/projects/kit/components/accordion/accordion.style.less index fa06adb78d7a..2d84575e9906 100644 --- a/projects/kit/components/accordion/accordion.style.less +++ b/projects/kit/components/accordion/accordion.style.less @@ -4,4 +4,5 @@ .t-group { display: flex; + align-items: stretch; } diff --git a/projects/kit/components/input-date-multi/input-date-multi.component.ts b/projects/kit/components/input-date-multi/input-date-multi.component.ts index f7d4b6b3fc87..70a40ba00516 100644 --- a/projects/kit/components/input-date-multi/input-date-multi.component.ts +++ b/projects/kit/components/input-date-multi/input-date-multi.component.ts @@ -5,7 +5,8 @@ import { HostBinding, HostListener, Inject, - Injector, + inject, + InjectFlags, Input, Optional, Self, @@ -37,8 +38,8 @@ import { } from '@taiga-ui/cdk'; import { TUI_DEFAULT_MARKER_HANDLER, + TUI_DROPDOWN_COMPONENT, TUI_TEXTFIELD_SIZE, - TuiDialogService, TuiMarkerHandler, TuiPrimitiveTextfieldComponent, TuiSizeL, @@ -58,9 +59,8 @@ import { TuiInputDateOptions, } from '@taiga-ui/kit/tokens'; import {tuiImmutableUpdateInputDateMulti} from '@taiga-ui/kit/utils'; -import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; import {Observable} from 'rxjs'; -import {map, takeUntil} from 'rxjs/operators'; +import {map} from 'rxjs/operators'; @Component({ selector: 'tui-input-date[multiple]', @@ -71,6 +71,13 @@ import {map, takeUntil} from 'rxjs/operators'; tuiAsFocusableItemAccessor(TuiInputDateMultiComponent), tuiAsControl(TuiInputDateMultiComponent), tuiDateStreamWithTransformer(TUI_DATE_VALUE_TRANSFORMER), + { + provide: TUI_DROPDOWN_COMPONENT, + useFactory: () => + (inject(TUI_IS_MOBILE) && + inject(TUI_MOBILE_CALENDAR, InjectFlags.Optional)) || + inject(TUI_DROPDOWN_COMPONENT, InjectFlags.SkipSelf), + }, ], }) export class TuiInputDateMultiComponent @@ -136,10 +143,7 @@ export class TuiInputDateMultiComponent @Inject(NgControl) control: NgControl | null, @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef, - @Inject(Injector) private readonly injector: Injector, @Inject(TUI_IS_MOBILE) readonly isMobile: boolean, - @Inject(TuiDialogService) - private readonly dialogs: TuiDialogService, @Optional() @Inject(TUI_MOBILE_CALENDAR) private readonly mobileCalendar: Type> | null, @@ -177,7 +181,7 @@ export class TuiInputDateMultiComponent @HostListener('click') onClick(): void { - if (!this.isMobile) { + if (!this.isMobile && this.interactive) { this.open = !this.open; } } @@ -234,33 +238,10 @@ export class TuiInputDateMultiComponent ); } - get canOpen(): boolean { - return this.interactive && !this.computedMobile; - } - onIconClick(): void { - if (!this.computedMobile || !this.mobileCalendar) { - return; + if (this.isMobile && this.interactive) { + this.open = true; } - - this.dialogs - .open( - new PolymorpheusComponent(this.mobileCalendar, this.injector), - { - size: 'fullscreen', - closeable: false, - data: { - multi: true, - min: this.min, - max: this.max, - disabledItemHandler: this.disabledItemHandler, - }, - }, - ) - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.value = value; - }); } onEnter(search: string): void { @@ -283,7 +264,7 @@ export class TuiInputDateMultiComponent onValueChange(value: ReadonlyArray>): void { this.control?.updateValueAndValidity({emitEvent: false}); - if (!value.length) { + if (!value.length && !this.mobileCalendar) { this.onOpenChange(true); } diff --git a/projects/kit/components/input-date-multi/input-date-multi.template.html b/projects/kit/components/input-date-multi/input-date-multi.template.html index 4b03152a01b6..9a270e425248 100644 --- a/projects/kit/components/input-date-multi/input-date-multi.template.html +++ b/projects/kit/components/input-date-multi/input-date-multi.template.html @@ -1,8 +1,8 @@ + (inject(TUI_IS_MOBILE) && + inject(TUI_MOBILE_CALENDAR, InjectFlags.Optional)) || + inject(TUI_DROPDOWN_COMPONENT, InjectFlags.SkipSelf), + }, ], }) export class TuiInputDateRangeComponent @@ -131,10 +139,7 @@ export class TuiInputDateRangeComponent @Inject(NgControl) control: NgControl | null, @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef, - @Inject(Injector) private readonly injector: Injector, @Inject(TUI_IS_MOBILE) private readonly isMobile: boolean, - @Inject(TuiDialogService) - private readonly dialogs: TuiDialogService, @Optional() @Inject(TUI_MOBILE_CALENDAR) private readonly mobileCalendar: Type> | null, @@ -181,10 +186,6 @@ export class TuiInputDateRangeComponent return this.options.icon; } - get canOpen(): boolean { - return this.interactive && !this.computedMobile; - } - get computedExampleText(): string { return this.items.length ? this.textfield?.nativeFocusableElement?.placeholder || '' @@ -270,8 +271,8 @@ export class TuiInputDateRangeComponent @HostListener('click') onClick(): void { - if (!this.isMobile) { - this.toggle(); + if (!this.isMobile && this.interactive) { + this.open = !this.open; } } @@ -280,37 +281,9 @@ export class TuiInputDateRangeComponent } onIconClick(): void { - if (!this.computedMobile || !this.mobileCalendar) { - return; + if (this.isMobile && this.interactive) { + this.open = true; } - - this.dialogs - .open( - new PolymorpheusComponent(this.mobileCalendar, this.injector), - { - size: 'fullscreen', - closeable: false, - data: { - min: this.maxLengthMapper( - this.computedMin, - this.value, - this.maxLength, - true, - ), - max: this.maxLengthMapper( - this.computedMax, - this.value, - this.maxLength, - false, - ), - disabledItemHandler: this.disabledItemHandler, - }, - }, - ) - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.value = value; - }); } onOpenChange(open: boolean): void { @@ -322,7 +295,7 @@ export class TuiInputDateRangeComponent this.control.updateValueAndValidity({emitEvent: false}); } - if (!value) { + if (!value && !this.mobileCalendar) { this.onOpenChange(true); } diff --git a/projects/kit/components/input-date-range/input-date-range.template.html b/projects/kit/components/input-date-range/input-date-range.template.html index f191e8b4931f..3ea74797d5a7 100644 --- a/projects/kit/components/input-date-range/input-date-range.template.html +++ b/projects/kit/components/input-date-range/input-date-range.template.html @@ -1,8 +1,8 @@ diff --git a/projects/kit/components/input-date/input-date.component.ts b/projects/kit/components/input-date/input-date.component.ts index 8312ea3387eb..8b90789a2376 100644 --- a/projects/kit/components/input-date/input-date.component.ts +++ b/projects/kit/components/input-date/input-date.component.ts @@ -5,7 +5,8 @@ import { HostBinding, HostListener, Inject, - Injector, + inject, + InjectFlags, Input, Optional, Self, @@ -39,8 +40,8 @@ import { } from '@taiga-ui/cdk'; import { TUI_DEFAULT_MARKER_HANDLER, + TUI_DROPDOWN_COMPONENT, TUI_TEXTFIELD_SIZE, - TuiDialogService, TuiMarkerHandler, TuiPrimitiveTextfieldComponent, TuiSizeL, @@ -58,9 +59,8 @@ import { tuiDateStreamWithTransformer, TuiInputDateOptions, } from '@taiga-ui/kit/tokens'; -import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; import {Observable} from 'rxjs'; -import {map, takeUntil} from 'rxjs/operators'; +import {map} from 'rxjs/operators'; @Component({ selector: 'tui-input-date', @@ -71,6 +71,13 @@ import {map, takeUntil} from 'rxjs/operators'; tuiAsFocusableItemAccessor(TuiInputDateComponent), tuiAsControl(TuiInputDateComponent), tuiDateStreamWithTransformer(TUI_DATE_VALUE_TRANSFORMER), + { + provide: TUI_DROPDOWN_COMPONENT, + useFactory: () => + (inject(TUI_IS_MOBILE) && + inject(TUI_MOBILE_CALENDAR, InjectFlags.Optional)) || + inject(TUI_DROPDOWN_COMPONENT, InjectFlags.SkipSelf), + }, ], }) export class TuiInputDateComponent @@ -116,10 +123,7 @@ export class TuiInputDateComponent @Inject(NgControl) control: NgControl | null, @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef, - @Inject(Injector) private readonly injector: Injector, @Inject(TUI_IS_MOBILE) readonly isMobile: boolean, - @Inject(TuiDialogService) - private readonly dialogs: TuiDialogService, @Optional() @Inject(TUI_MOBILE_CALENDAR) private readonly mobileCalendar: Type> | null, @@ -158,6 +162,9 @@ export class TuiInputDateComponent return !!this.textfield?.focused; } + /** + * @deprecated + */ get computedMobile(): boolean { return this.isMobile && (!!this.mobileCalendar || this.nativePicker); } @@ -197,15 +204,9 @@ export class TuiInputDateComponent } set nativeValue(value: string) { - if (!this.nativeFocusableElement) { - return; + if (this.nativeFocusableElement) { + this.nativeFocusableElement.value = value; } - - this.nativeFocusableElement.value = value; - } - - get canOpen(): boolean { - return this.interactive && !this.computedMobile; } get computedMask(): MaskitoOptions { @@ -227,7 +228,7 @@ export class TuiInputDateComponent @HostListener('click') onClick(): void { - if (!this.isMobile) { + if (!this.isMobile && this.interactive) { this.open = !this.open; } } @@ -246,25 +247,9 @@ export class TuiInputDateComponent } onIconClick(): void { - if (!this.computedMobile || !this.mobileCalendar || this.readOnly) { - return; + if (this.isMobile && this.interactive) { + this.open = true; } - - this.dialogs - .open(new PolymorpheusComponent(this.mobileCalendar, this.injector), { - size: 'fullscreen', - closeable: false, - data: { - single: true, - min: this.min, - max: this.max, - disabledItemHandler: this.disabledItemHandler, - }, - }) - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.value = value; - }); } onValueChange(value: string): void { @@ -272,7 +257,7 @@ export class TuiInputDateComponent this.control.updateValueAndValidity({emitEvent: false}); } - if (!value) { + if (!value && !this.mobileCalendar) { this.onOpenChange(true); } diff --git a/projects/kit/components/input-date/input-date.template.html b/projects/kit/components/input-date/input-date.template.html index 85b4c66d529d..6c6b8e951ccb 100644 --- a/projects/kit/components/input-date/input-date.template.html +++ b/projects/kit/components/input-date/input-date.template.html @@ -1,8 +1,8 @@