diff --git a/projects/addon-commerce/components/input-card-group/input-card-group.component.ts b/projects/addon-commerce/components/input-card-group/input-card-group.component.ts index 79745a45c654..4ac1de5d8175 100644 --- a/projects/addon-commerce/components/input-card-group/input-card-group.component.ts +++ b/projects/addon-commerce/components/input-card-group/input-card-group.component.ts @@ -10,7 +10,7 @@ import { Output, ViewChild, } from '@angular/core'; -import {toSignal} from '@angular/core/rxjs-interop'; +import {takeUntilDestroyed, toSignal} from '@angular/core/rxjs-interop'; import {FormsModule} from '@angular/forms'; import {MaskitoDirective} from '@maskito/angular'; import {WaResizeObserver} from '@ng-web-apis/resize-observer'; @@ -30,6 +30,7 @@ import {TuiLet} from '@taiga-ui/cdk/directives/let'; import {tuiTypedFromEvent} from '@taiga-ui/cdk/observables'; import {TuiMapperPipe} from '@taiga-ui/cdk/pipes/mapper'; import {tuiInjectId} from '@taiga-ui/cdk/services'; +import {TUI_IS_WEBKIT} from '@taiga-ui/cdk/tokens'; import type {TuiBooleanHandler} from '@taiga-ui/cdk/types'; import {tuiInjectElement, tuiIsElement, tuiIsInput} from '@taiga-ui/cdk/utils/dom'; import {tuiIsNativeFocused, tuiIsNativeFocusedIn} from '@taiga-ui/cdk/utils/focus'; @@ -58,7 +59,7 @@ import {TUI_COMMON_ICONS} from '@taiga-ui/core/tokens'; import {TuiChevron} from '@taiga-ui/kit/directives/chevron'; import type {PolymorpheusContent} from '@taiga-ui/polymorpheus'; import {PolymorpheusOutlet, PolymorpheusTemplate} from '@taiga-ui/polymorpheus'; -import {map, merge} from 'rxjs'; +import {EMPTY, map, merge, Subject, switchMap, timer} from 'rxjs'; import {TUI_INPUT_CARD_GROUP_OPTIONS} from './input-card-group.options'; import {TUI_INPUT_CARD_GROUP_TEXTS} from './input-card-group.providers'; @@ -122,6 +123,7 @@ export class TuiInputCardGroup @ViewChild('inputCVC') private readonly inputCVC?: ElementRef; + private readonly focus$ = new Subject(); private expirePrefilled = false; private readonly paymentSystems = inject(TUI_PAYMENT_SYSTEM_ICONS); private readonly options = inject(TUI_INPUT_CARD_GROUP_OPTIONS); @@ -144,6 +146,14 @@ export class TuiInputCardGroup protected readonly icons = inject(TUI_COMMON_ICONS); protected readonly texts = toSignal(inject(TUI_INPUT_CARD_GROUP_TEXTS)); protected readonly open = tuiDropdownOpen(); + protected readonly $ = inject(TUI_IS_WEBKIT) + ? this.focus$ + .pipe( + switchMap(() => timer(100)), + takeUntilDestroyed(), + ) + .subscribe(() => this.focusExpire()) + : EMPTY; protected readonly m = tuiAppearanceMode(this.mode); protected readonly appearance = tuiAppearance( @@ -234,8 +244,14 @@ export class TuiInputCardGroup public clear(): void { this.expirePrefilled = false; + + [this.inputCVC, this.inputExpire, this.inputCard].forEach((e) => { + e?.nativeElement.focus(); + e?.nativeElement.select(); + e?.nativeElement.ownerDocument.execCommand('delete'); + }); + this.onChange(null); - this.focusCard(); } public onResize(): void { @@ -308,6 +324,8 @@ export class TuiInputCardGroup if (this.cardValidator(this.card) && !value()?.expire && this.inputExpire) { this.focusExpire(); + // Safari autofill focus jerk workaround + this.focus$.next(); } } diff --git a/projects/cdk/classes/control.ts b/projects/cdk/classes/control.ts index ba1fbbd19dbe..cfc8064c344c 100644 --- a/projects/cdk/classes/control.ts +++ b/projects/cdk/classes/control.ts @@ -2,7 +2,6 @@ import type {Provider, Type} from '@angular/core'; import { ChangeDetectorRef, computed, - DestroyRef, Directive, inject, Input, @@ -40,7 +39,6 @@ export abstract class TuiControl implements ControlValueAccessor { private readonly internal = signal(this.fallback); protected readonly control = inject(NgControl, {self: true}); - protected readonly destroyRef = inject(DestroyRef); protected readonly cdr = inject(ChangeDetectorRef); protected readonly transformer = inject(TuiValueTransformer, FLAGS); @@ -74,7 +72,7 @@ export abstract class TuiControl implements ControlValueAccessor { filter(Boolean), distinctUntilChanged(), switchMap((c) => merge(c.valueChanges, c.statusChanges)), - takeUntilDestroyed(this.destroyRef), + takeUntilDestroyed(), ) .subscribe(() => this.update()); } diff --git a/projects/core/components/textfield/textfield.directive.ts b/projects/core/components/textfield/textfield.directive.ts index f9ea94e9517b..aca8aeaf038b 100644 --- a/projects/core/components/textfield/textfield.directive.ts +++ b/projects/core/components/textfield/textfield.directive.ts @@ -67,8 +67,18 @@ export class TuiTextfieldBase implements OnChanges { } public setValue(value: T | null): void { - this.el.value = value == null ? '' : this.textfield.stringify(value); - this.el.dispatchEvent(new Event('input', {bubbles: true})); + this.el.focus(); + this.el.select(); + + if (value == null) { + this.el.ownerDocument.execCommand('delete'); + } else { + this.el.ownerDocument.execCommand( + 'insertText', + false, + this.textfield.stringify(value), + ); + } } } diff --git a/projects/core/styles/components/textfield.less b/projects/core/styles/components/textfield.less index 087f728b3090..1346b7d32b13 100644 --- a/projects/core/styles/components/textfield.less +++ b/projects/core/styles/components/textfield.less @@ -140,15 +140,13 @@ tui-textfield { &:has(label:not(:empty)) { .t-template, - input, - select { + input:defined, + select:defined { padding-top: calc(var(--t-height) / 3); - &:not(:-webkit-autofill)::placeholder, + &::placeholder, &._empty { - caret-color: var(--tui-text-primary); - color: transparent !important; - -webkit-text-fill-color: transparent !important; + color: transparent; } } } @@ -156,15 +154,13 @@ tui-textfield { // TODO: Fallback until Safari 15.4 &._with-label { .t-template, - input, - select { + input:defined, + select:defined { padding-top: calc(var(--t-height) / 3); - &:not(:-webkit-autofill)::placeholder, + &::placeholder, &._empty { - caret-color: var(--tui-text-primary); - color: transparent !important; - -webkit-text-fill-color: transparent !important; + color: transparent; } } } @@ -172,7 +168,7 @@ tui-textfield { .t-template, input:defined, select:defined { - .fullsize(absolute, inset); + .fullsize(); appearance: none; box-sizing: border-box; @@ -233,9 +229,7 @@ tui-textfield { .appearance-focus({ &::placeholder, &._empty { - caret-color: var(--tui-text-primary); - color: transparent !important; - -webkit-text-fill-color: var(--tui-text-tertiary) !important; + color: var(--tui-text-tertiary); } & ~ label { diff --git a/projects/core/styles/theme/appearance/textfield.less b/projects/core/styles/theme/appearance/textfield.less index eced93d5a530..232447effc6f 100644 --- a/projects/core/styles/theme/appearance/textfield.less +++ b/projects/core/styles/theme/appearance/textfield.less @@ -42,10 +42,6 @@ caret-color: var(--tui-text-primary) !important; box-shadow: 0 0 0 100rem var(--tui-service-autofill-background) inset !important; transition: background-color 600000s 0s; - - &::placeholder { - -webkit-text-fill-color: var(--tui-text-secondary); - } } }