diff --git a/projects/cdk/utils/array/filter.ts b/projects/cdk/utils/array/filter.ts new file mode 100644 index 0000000000000..5f2345a5a52d0 --- /dev/null +++ b/projects/cdk/utils/array/filter.ts @@ -0,0 +1,22 @@ +/** + * Returns a new filtered array. The search is performed by the properties specified in the keys. + * + * @param list Array of objects + * @param search Search string + * @param keys Specified keys for search + * + * @returns Filtered array of objects + */ +export const tuiFilterList = ( + list: T[] = [], + search = '', + keys: string[] = [], +): T[] => { + search = search.toLowerCase(); + + return list.filter((item) => + keys.some((key: string) => + (item[key as keyof typeof item] as string).toLowerCase().includes(search), + ), + ); +}; diff --git a/projects/cdk/utils/array/index.ts b/projects/cdk/utils/array/index.ts new file mode 100644 index 0000000000000..009bf076365d2 --- /dev/null +++ b/projects/cdk/utils/array/index.ts @@ -0,0 +1 @@ +export * from './filter'; diff --git a/projects/cdk/utils/index.ts b/projects/cdk/utils/index.ts index e18d9206335d9..89b659d73f6c8 100644 --- a/projects/cdk/utils/index.ts +++ b/projects/cdk/utils/index.ts @@ -1,3 +1,4 @@ +export * from '@taiga-ui/cdk/utils/array'; export * from '@taiga-ui/cdk/utils/browser'; export * from '@taiga-ui/cdk/utils/color'; export * from '@taiga-ui/cdk/utils/dom'; diff --git a/projects/core/components/data-list/data-list.component.ts b/projects/core/components/data-list/data-list.component.ts index c4764754b4e97..a7d6ccf4b1680 100644 --- a/projects/core/components/data-list/data-list.component.ts +++ b/projects/core/components/data-list/data-list.component.ts @@ -2,6 +2,7 @@ import {NgIf} from '@angular/common'; import type {AfterContentChecked, QueryList} from '@angular/core'; import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, ContentChildren, forwardRef, @@ -55,6 +56,7 @@ export class TuiDataListComponent private origin?: HTMLElement; private readonly el = tuiInjectElement(); + private readonly cdr = inject(ChangeDetectorRef); protected readonly fallback = toSignal(inject(TUI_NOTHING_FOUND_MESSAGE)); protected empty = true; @@ -86,6 +88,7 @@ export class TuiDataListComponent public ngAfterContentChecked(): void { // TODO: Refactor to :has after Safari support bumped to 15 this.empty = !this.el.querySelector('[tuiOption]'); + this.cdr.markForCheck(); } public getOptions(includeDisabled = false): readonly T[] { diff --git a/projects/demo/src/modules/components/input-phone-international/examples/2/index.html b/projects/demo/src/modules/components/input-phone-international/examples/2/index.html index 4d3f099ad6793..e4fa32128457b 100644 --- a/projects/demo/src/modules/components/input-phone-international/examples/2/index.html +++ b/projects/demo/src/modules/components/input-phone-international/examples/2/index.html @@ -1,5 +1,6 @@ diff --git a/projects/i18n/types/country-type.ts b/projects/i18n/types/country-type.ts new file mode 100644 index 0000000000000..43823287ef769 --- /dev/null +++ b/projects/i18n/types/country-type.ts @@ -0,0 +1,11 @@ +import type {TuiCountryIsoCode} from './country-iso-code'; + +/** + * Full information about the country for international phone component + */ +export interface TuiCountry { + isoCode: TuiCountryIsoCode; + flag: string; + name: string; + code: string; +} diff --git a/projects/i18n/types/index.ts b/projects/i18n/types/index.ts index c3f5474913c57..06cce8fcd4363 100644 --- a/projects/i18n/types/index.ts +++ b/projects/i18n/types/index.ts @@ -1,4 +1,5 @@ export * from './country-iso-code'; +export * from './country-type'; export * from './language'; export * from './language-loader'; export * from './language-names'; diff --git a/projects/kit/components/input-phone-international/input-phone-international.component.ts b/projects/kit/components/input-phone-international/input-phone-international.component.ts index d30b51579dbfa..dd1dbc4335c70 100644 --- a/projects/kit/components/input-phone-international/input-phone-international.component.ts +++ b/projects/kit/components/input-phone-international/input-phone-international.component.ts @@ -40,7 +40,7 @@ import { } from '@taiga-ui/core/directives/dropdown'; import {TuiGroup} from '@taiga-ui/core/directives/group'; import {TuiFlagPipe} from '@taiga-ui/core/pipes/flag'; -import type {TuiCountryIsoCode} from '@taiga-ui/i18n/types'; +import type {TuiCountry, TuiCountryIsoCode} from '@taiga-ui/i18n/types'; import {TuiChevron} from '@taiga-ui/kit/directives'; import {TUI_COUNTRIES} from '@taiga-ui/kit/tokens'; import type {PolymorpheusContent} from '@taiga-ui/polymorpheus'; @@ -51,6 +51,8 @@ import {from, skip} from 'rxjs'; import {TuiGetCountryCallingCodePipe} from './get-country-calling-code.pipe'; import {TUI_INPUT_PHONE_INTERNATIONAL_OPTIONS} from './input-phone-international.options'; +import {TuiInputModule} from '@taiga-ui/legacy'; +import {tuiFilterList} from '@taiga-ui/cdk'; const NOT_FORM_CONTROL_SYMBOLS = /[^+\d]/g; @@ -68,6 +70,7 @@ const NOT_FORM_CONTROL_SYMBOLS = /[^+\d]/g; TuiGetCountryCallingCodePipe, TuiGroup, TuiTextfield, + TuiInputModule, ], templateUrl: './input-phone-international.template.html', styleUrls: ['./input-phone-international.style.less'], @@ -80,6 +83,8 @@ const NOT_FORM_CONTROL_SYMBOLS = /[^+\d]/g; limitWidth: 'fixed', align: 'right', }), + TuiFlagPipe, + TuiGetCountryCallingCodePipe, ], hostDirectives: [TuiGroup, TuiDropdownDirective, TuiWithDropdownOpen], host: { @@ -90,6 +95,11 @@ export class TuiInputPhoneInternational extends TuiControl { @ViewChild(MaskitoDirective, {read: ElementRef}) private readonly input?: ElementRef; + protected readonly tuiFlagPipe = inject(TuiFlagPipe); + protected readonly tuiGetCountryCallingCodePipe = inject( + TuiGetCountryCallingCodePipe, + ); + protected readonly dropdown = tuiDropdown(null); protected readonly options = inject(TUI_INPUT_PHONE_INTERNATIONAL_OPTIONS); protected readonly size = inject(TUI_TEXTFIELD_OPTIONS).size; @@ -97,6 +107,20 @@ export class TuiInputPhoneInternational extends TuiControl { protected readonly names = toSignal(inject(TUI_COUNTRIES)); protected readonly metadata = toSignal(from(this.options.metadata)); protected readonly countryIsoCode = signal(this.options.countryIsoCode); + + // Full list of countries + protected readonly countriesSignal = computed(() => + this.getTuiCountriesFromIsoCodes(this.countries), + ); + + // Countries list after filtering + protected readonly countriesFilteredSignal = computed(() => + this.filterList(this.countriesSignal(), this.searchStr(), [ + 'name', + 'code', + ]), + ); + protected readonly mask = computed(() => this.computeMask(this.countryIsoCode(), this.metadata()), ); @@ -106,11 +130,20 @@ export class TuiInputPhoneInternational extends TuiControl { @Input() public countries = this.options.countries; + @Input() + public tuiCountrySearch = false; + @Output() public readonly countryIsoCodeChange = toObservable(this.countryIsoCode).pipe( skip(1), ); + // Countries filter function + public filterList = tuiFilterList; + + // User's search string + public searchStr = signal(''); + @Input('countryIsoCode') public set isoCode(code: TuiCountryIsoCode) { this.countryIsoCode.set(code); @@ -183,6 +216,29 @@ export class TuiInputPhoneInternational extends TuiControl { this.onChange(unmaskedValue === countryCallingCode ? '' : unmaskedValue); } + /** + * Returns countries full information + * from ISO codes + */ + private getTuiCountriesFromIsoCodes( + isoCodes: readonly TuiCountryIsoCode[], + ): TuiCountry[] { + return isoCodes.map((isoCode) => this.getTuiCountryFromIsoCode(isoCode)); + } + + /** + * Returns country full information + * from ISO code + */ + private getTuiCountryFromIsoCode(isoCode: TuiCountryIsoCode): TuiCountry { + return { + isoCode, + code: this.tuiGetCountryCallingCodePipe.transform(isoCode, this.metadata()), + flag: this.tuiFlagPipe.transform(isoCode), + name: this.names()?.[isoCode] || '', + }; + } + private computeMask( countryIsoCode: TuiCountryIsoCode, metadata?: MetadataJson, diff --git a/projects/kit/components/input-phone-international/input-phone-international.style.less b/projects/kit/components/input-phone-international/input-phone-international.style.less index 7fdb8a692a333..99fce115e7148 100644 --- a/projects/kit/components/input-phone-international/input-phone-international.style.less +++ b/projects/kit/components/input-phone-international/input-phone-international.style.less @@ -35,3 +35,9 @@ color: var(--tui-text-secondary); margin-inline-end: 0.25rem; } + +.t-countries-search { + position: sticky; + top: 0.25rem; + margin: 0.25rem; +} diff --git a/projects/kit/components/input-phone-international/input-phone-international.template.html b/projects/kit/components/input-phone-international/input-phone-international.template.html index d2b530fbe16a8..6bb002b42d396 100644 --- a/projects/kit/components/input-phone-international/input-phone-international.template.html +++ b/projects/kit/components/input-phone-international/input-phone-international.template.html @@ -40,20 +40,35 @@ - - - + + + + + +