Skip to content

Commit

Permalink
feat(cdk): support provide custom query selector for auto focus direc…
Browse files Browse the repository at this point in the history
…tive (#9062)
  • Loading branch information
splincode authored Sep 19, 2024
1 parent cff240f commit ae149f5
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 39 deletions.
45 changes: 21 additions & 24 deletions projects/cdk/directives/auto-focus/autofocus.options.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type {Provider} from '@angular/core';
import {ElementRef, InjectionToken, NgZone, Renderer2} from '@angular/core';
import {ElementRef, NgZone, Renderer2} from '@angular/core';
import {WA_ANIMATION_FRAME, WA_WINDOW} from '@ng-web-apis/common';
import {TUI_IS_IOS} from '@taiga-ui/cdk/tokens';
import {tuiCreateToken, tuiProvideOptions} from '@taiga-ui/cdk/utils/miscellaneous';
import {tuiCreateOptions, tuiCreateToken} from '@taiga-ui/cdk/utils';
import type {Observable} from 'rxjs';

import {TuiDefaultAutofocusHandler} from './handlers/default.handler';
Expand All @@ -14,42 +13,40 @@ export interface TuiAutofocusHandler {

export interface TuiAutofocusOptions {
readonly delay: number;
readonly query: string;
}

export const TUI_AUTOFOCUS_DEFAULT_OPTIONS: TuiAutofocusOptions = {
delay: NaN, // NaN = no delay/sync
};
export const [TUI_AUTOFOCUS_OPTIONS, tuiAutoFocusOptionsProvider] =
tuiCreateOptions<TuiAutofocusOptions>({
delay: NaN, // NaN = no delay/sync
query: 'input, textarea, select, [contenteditable]',
});

export const TUI_AUTOFOCUS_OPTIONS = tuiCreateToken(TUI_AUTOFOCUS_DEFAULT_OPTIONS);

export function tuiAutoFocusOptionsProvider(
options: Partial<TuiAutofocusOptions>,
): Provider {
return tuiProvideOptions(
TUI_AUTOFOCUS_OPTIONS,
options,
TUI_AUTOFOCUS_DEFAULT_OPTIONS,
);
}

export const TUI_AUTOFOCUS_HANDLER = new InjectionToken<TuiAutofocusHandler>(
'[TUI_AUTOFOCUS_HANDLER]',
);
export const TUI_AUTOFOCUS_HANDLER = tuiCreateToken<TuiAutofocusHandler>();

export const TUI_AUTOFOCUS_PROVIDERS = [
{
provide: TUI_AUTOFOCUS_HANDLER,
deps: [
ElementRef,
WA_ANIMATION_FRAME,
Renderer2,
NgZone,
WA_WINDOW,
TUI_IS_IOS,
TUI_AUTOFOCUS_OPTIONS,
],
useFactory: (
el: ElementRef<HTMLElement>,
animationFrame$: Observable<number>,
renderer: Renderer2,
zone: NgZone,
win: Window,
isIos: boolean,
options: TuiAutofocusOptions,
) =>
isIos
? new TuiIosAutofocusHandler(el, renderer, zone, win)
: new TuiDefaultAutofocusHandler(el, animationFrame$, zone),
deps: [ElementRef, WA_ANIMATION_FRAME, Renderer2, NgZone, WA_WINDOW, TUI_IS_IOS],
? new TuiIosAutofocusHandler(el, renderer, zone, win, options)
: new TuiDefaultAutofocusHandler(el, animationFrame$, zone, options),
},
];
11 changes: 7 additions & 4 deletions projects/cdk/directives/auto-focus/handlers/abstract.handler.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import type {ElementRef} from '@angular/core';

import type {TuiAutofocusHandler} from '../autofocus.options';
import type {TuiAutofocusHandler, TuiAutofocusOptions} from '../autofocus.options';

export abstract class AbstractTuiAutofocusHandler implements TuiAutofocusHandler {
constructor(protected readonly el: ElementRef<HTMLElement>) {}
constructor(
protected readonly el: ElementRef<HTMLElement>,
protected readonly options: TuiAutofocusOptions,
) {}

public abstract setFocus(): void;

protected get element(): HTMLElement {
// TODO: Remove when legacy controls are dropped
const el = this.el.nativeElement.tagName.includes('-')
? this.el.nativeElement.querySelector<HTMLElement>('input,textarea')
? this.el.nativeElement.querySelector<HTMLElement>(this.options.query)
: this.el.nativeElement;

return el || this.el.nativeElement;
}

protected get isTextFieldElement(): boolean {
return this.element.matches('input, textarea, [contenteditable]');
return this.element.matches(this.options.query);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {tuiZonefreeScheduler} from '@taiga-ui/cdk/observables';
import type {Observable} from 'rxjs';
import {map, race, skipWhile, take, throttleTime, timer} from 'rxjs';

import type {TuiAutofocusOptions} from '../autofocus.options';
import {AbstractTuiAutofocusHandler} from './abstract.handler';

const TIMEOUT = 1000;
Expand All @@ -13,14 +14,15 @@ export class TuiDefaultAutofocusHandler extends AbstractTuiAutofocusHandler {
el: ElementRef<HTMLElement>,
private readonly animationFrame$: Observable<number>,
private readonly zone: NgZone,
options: TuiAutofocusOptions,
) {
super(el);
super(el, options);
}

public setFocus(): void {
if (this.isTextFieldElement) {
race(
timer(TIMEOUT),
timer(this.options.delay || TIMEOUT),
this.animationFrame$.pipe(
throttleTime(100, tuiZonefreeScheduler(this.zone)),
map(() => this.element.closest(NG_ANIMATION_SELECTOR)),
Expand Down
4 changes: 3 additions & 1 deletion projects/cdk/directives/auto-focus/handlers/ios.handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {ElementRef, NgZone, Renderer2} from '@angular/core';
import {tuiIsPresent, tuiPx} from '@taiga-ui/cdk/utils';

import type {TuiAutofocusOptions} from '../autofocus.options';
import {AbstractTuiAutofocusHandler} from './abstract.handler';

const TEXTFIELD_ATTRS = [
Expand All @@ -22,8 +23,9 @@ export class TuiIosAutofocusHandler extends AbstractTuiAutofocusHandler {
private readonly renderer: Renderer2,
private readonly zone: NgZone,
private readonly win: Window,
options: TuiAutofocusOptions,
) {
super(el);
super(el, options);
this.patchCssStyles();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
import type {ComponentFixture} from '@angular/core/testing';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {WA_WINDOW} from '@ng-web-apis/common';
import type {TuiAutofocusOptions} from '@taiga-ui/cdk';
import {
TUI_AUTOFOCUS_HANDLER,
TUI_AUTOFOCUS_OPTIONS,
TuiAutoFocus,
TuiIosAutofocusHandler,
tuiIsNativeFocused,
Expand Down Expand Up @@ -81,13 +83,20 @@ describe('TuiAutoFocus directive', () => {
{
provide: TUI_AUTOFOCUS_HANDLER,
useClass: TuiIosAutofocusHandler,
deps: [
ElementRef,
Renderer2,
NgZone,
WA_WINDOW,
TUI_AUTOFOCUS_OPTIONS,
],
useFactory: (
el: ElementRef<HTMLElement>,
renderer: Renderer2,
zone: NgZone,
win: Window,
) => new TuiIosAutofocusHandler(el, renderer, zone, win),
deps: [ElementRef, Renderer2, NgZone, WA_WINDOW],
options: TuiAutofocusOptions,
) => new TuiIosAutofocusHandler(el, renderer, zone, win, options),
},
],
});
Expand Down
5 changes: 2 additions & 3 deletions projects/cdk/utils/di/create-options.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type {InjectionToken, Provider} from '@angular/core';
import type {TuiHandler} from '@taiga-ui/cdk/types';
import type {FactoryProvider, InjectionToken} from '@angular/core';
import {tuiCreateToken, tuiProvideOptions} from '@taiga-ui/cdk/utils/miscellaneous';

export function tuiCreateOptions<T>(
defaults: T,
): [token: InjectionToken<T>, provider: TuiHandler<Partial<T>, Provider>] {
): [token: InjectionToken<T>, provider: (item: Partial<T>) => FactoryProvider] {
const token = tuiCreateToken(defaults);

return [token, (options) => tuiProvideOptions(token, options, defaults)];
Expand Down
6 changes: 3 additions & 3 deletions projects/cdk/utils/miscellaneous/create-token.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {InjectionToken} from '@angular/core';

export function tuiCreateToken<T>(defaults: T): InjectionToken<T> {
export function tuiCreateToken<T>(defaults?: T): InjectionToken<T> {
return tuiCreateTokenFromFactory(() => defaults);
}

export function tuiCreateTokenFromFactory<T>(factory: () => T): InjectionToken<T> {
return new InjectionToken<T>('', {factory});
export function tuiCreateTokenFromFactory<T>(factory?: () => T): InjectionToken<T> {
return factory ? new InjectionToken<T>('', {factory}) : new InjectionToken<T>('');
}

0 comments on commit ae149f5

Please sign in to comment.