Skip to content

Commit

Permalink
fix(cdk): support autofill values when trigger autofocus on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
splincode committed Apr 8, 2023
1 parent faa35a4 commit a75ed21
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 13 deletions.
51 changes: 43 additions & 8 deletions projects/cdk/directives/auto-focus/handlers/ios.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import {WINDOW} from '@ng-web-apis/common';
import {TuiFocusableElementAccessor} from '@taiga-ui/cdk/interfaces';
import {TUI_FOCUSABLE_ITEM_ACCESSOR} from '@taiga-ui/cdk/tokens';
import {tuiPx} from '@taiga-ui/cdk/utils';
import {tuiIsPresent, tuiPx} from '@taiga-ui/cdk/utils';

// TODO: find the best way for prevent cycle
// eslint-disable-next-line import/no-cycle
Expand Down Expand Up @@ -79,26 +79,34 @@ export class TuiIosAutofocusHandler extends AbstractTuiAutofocusHandler {
* @note:
* emulate textfield position in layout with cursor
* before focus to real textfield element
*
* required note:
* [fakeInput.readOnly = true] ~
* don't use {readOnly: true} value, it's doesn't work for emulate autofill
*
* [fakeInput.style.opacity = 0] ~
* don't use {opacity: 0}, sometimes it's doesn't work for emulate real input
*
* [fakeInput.style.fontSize = 16px] ~
* disable possible auto zoom
*
* [fakeInput.style.top/left] ~
* emulate position cursor before focus to real textfield element
*/
private makeFakeInput(): HTMLInputElement {
const fakeInput: HTMLInputElement = this.renderer.createElement(`input`);
const rect: DOMRect = this.element.getBoundingClientRect();

fakeInput.setAttribute(`maxlength`, `0`);
this.patchFakeInputFromFocusableElement(fakeInput);

// @note: don't use opacity: 0,
// sometimes it's doesn't work for emulate real input
fakeInput.style.height = tuiPx(rect.height);
fakeInput.style.width = tuiPx(rect.width / 2);
fakeInput.style.position = `fixed`;
fakeInput.style.zIndex = `-99999999`;
fakeInput.style.caretColor = `transparent`;
fakeInput.style.color = `transparent`;
fakeInput.style.cursor = `none`;
fakeInput.style.fontSize = tuiPx(16); // disable possible auto zoom
fakeInput.readOnly = true; // prevent keyboard for fake input

// @note: emulate position cursor before focus to real textfield element
fakeInput.style.fontSize = tuiPx(16);
fakeInput.style.top = tuiPx(rect.top);
fakeInput.style.left = tuiPx(rect.left);

Expand Down Expand Up @@ -142,4 +150,31 @@ export class TuiIosAutofocusHandler extends AbstractTuiAutofocusHandler {
element.style.setProperty(`height`, `100%`);
}
}

/**
* @note:
* inherit basic attributes values from real input
* for help iOS detect what do you want see on keyboard,
* for example [inputMode=numeric, autocomplete=cc-number]
*/
private patchFakeInputFromFocusableElement(fakeInput: HTMLInputElement): void {
for (const property of [
`type`,
`inputMode`,
`autocomplete`,
`accept`,
`min`,
`max`,
`step`,
`pattern`,
`size`,
`maxlength`,
] as const) {
const value = this.element.getAttribute(property);

if (tuiIsPresent(value)) {
fakeInput.setAttribute(property, value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,26 @@ <h1 class="title">Pay by card</h1>
</ng-container>

<div class="form-block">
<ng-container *tuiLet="(loading$ | async) ?? false as loading">
<p *ngIf="loading">Please wait, we are loading fake cards...</p>

<!-- hack: open keyboard on iOS before focus target input -->
<ng-container *ngIf="iOS">
<!-- hack: open keyboard on iOS before focus target card input -->
<!-- hack: use autocomplete/inputmode attrs if you want autofill in keyboard -->
<input
tuiAutoFocus
maxlength="0"
inputmode="numeric"
autocomplete="cc-number"
class="fake-input"
/>
<!-- required:
You don't need to use this hack for iOS if your input-card elements
already exist in DOM, because you don't need to wait for them asynchronously.
In our example we wait loading$ state before focus real card number / card cvc.
-->
</ng-container>

<ng-container *tuiLet="(loading$ | async) ?? false as loading">
<p *ngIf="loading">Please wait, we are loading fake cards...</p>

<tui-loader
[overlay]="true"
Expand All @@ -51,6 +62,7 @@ <h1 class="title">Pay by card</h1>
<tui-input-card-grouped
#cardGroupedInput
formControlName="card"
[autocompleteEnabled]="true"
[cardValidator]="cardValidator"
[class.without-date]="paymentMode === PAYMENT_MODE.BySavedCard"
[tuiTextfieldCleaner]="paymentMode === PAYMENT_MODE.ByNewCard"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
tuiDefaultCardValidator,
TuiInputCardGroupedComponent,
} from '@taiga-ui/addon-commerce';
import {TuiDestroyService} from '@taiga-ui/cdk';
import {TUI_IS_IOS, TuiDestroyService} from '@taiga-ui/cdk';
import {TuiDialogContext} from '@taiga-ui/core';
import {POLYMORPHEUS_CONTEXT} from '@tinkoff/ng-polymorpheus';
import {BehaviorSubject} from 'rxjs';
Expand Down Expand Up @@ -56,6 +56,7 @@ export class PayModalComponent implements OnInit {
constructor(
@Inject(POLYMORPHEUS_CONTEXT)
readonly context: TuiDialogContext<void, DataForPayCardModal>,
@Inject(TUI_IS_IOS) readonly iOS: boolean,
@Inject(PayService) private readonly payService: PayService,
@Self() @Inject(TuiDestroyService) private readonly destroy$: TuiDestroyService,
) {}
Expand Down

0 comments on commit a75ed21

Please sign in to comment.