@taiga-ui/kit
Version:
Taiga UI Angular main components kit
201 lines (195 loc) • 22.4 kB
JavaScript
import { TuiLabel } from '@taiga-ui/core/components/label';
import * as i6 from '@taiga-ui/core/components/textfield';
import { TUI_TEXTFIELD_OPTIONS, TuiTextfieldContent, TuiTextfieldComponent, TuiTextfieldOptionsDirective } from '@taiga-ui/core/components/textfield';
import * as i7 from '@taiga-ui/core/portals/dropdown';
import { TuiDropdownOpen, tuiDropdownEnabled, TuiDropdownContent } from '@taiga-ui/core/portals/dropdown';
import { NgTemplateOutlet } from '@angular/common';
import * as i0 from '@angular/core';
import { viewChildren, ElementRef, inject, effect, signal, computed, input, model, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
import * as i4 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import * as i1 from '@maskito/angular';
import { MaskitoDirective } from '@maskito/angular';
import { maskitoTransform, MASKITO_DEFAULT_OPTIONS, maskitoInitialCalibrationPlugin } from '@maskito/core';
import { maskitoPhoneOptionsGenerator, maskitoGetCountryFromNumber } from '@maskito/phone';
import { WA_IS_IOS } from '@ng-web-apis/platform';
import { TuiControl, tuiAsControl } from '@taiga-ui/cdk/classes';
import { TUI_DEFAULT_MATCHER, CHAR_PLUS } from '@taiga-ui/cdk/constants';
import { TuiActiveZone } from '@taiga-ui/cdk/directives/active-zone';
import { TuiAutoFocus, tuiAutoFocusOptionsProvider } from '@taiga-ui/cdk/directives/auto-focus';
import { tuiFallbackValueProvider } from '@taiga-ui/cdk/tokens';
import { tuiInjectElement, tuiValue, tuiIsInputEvent } from '@taiga-ui/cdk/utils/dom';
import { TuiButton } from '@taiga-ui/core/components/button';
import * as i5 from '@taiga-ui/core/components/data-list';
import { TuiOption, TuiDataList } from '@taiga-ui/core/components/data-list';
import * as i2 from '@taiga-ui/core/components/input';
import { TuiWithInput, TuiInput } from '@taiga-ui/core/components/input';
import { TuiTitle } from '@taiga-ui/core/components/title';
import { TUI_COMMON_ICONS } from '@taiga-ui/core/tokens';
import * as i3 from '@taiga-ui/kit/directives/appearance-proxy';
import { TuiAppearanceProxy } from '@taiga-ui/kit/directives/appearance-proxy';
import { TuiChevron } from '@taiga-ui/kit/directives/chevron';
import { TuiFlagPipe } from '@taiga-ui/kit/pipes/flag';
import { TUI_INTERNATIONAL_SEARCH, TUI_COUNTRIES } from '@taiga-ui/kit/tokens';
import { tuiMaskito } from '@taiga-ui/kit/utils';
import { validatePhoneNumberLength, getCountryCallingCode } from 'libphonenumber-js/core';
import { of, from, filter } from 'rxjs';
import { tuiCreateOptions } from '@taiga-ui/cdk/utils/di';
const TUI_INPUT_PHONE_INTERNATIONAL_DEFAULT_OPTIONS = {
countries: [],
countrySearch: false,
countryIsoCode: 'RU',
metadata: of({
version: 4,
countries: {},
country_calling_codes: {},
nonGeographic: {},
}),
separator: '-',
};
/**
* Default parameters for input phone international component
*/
const [TUI_INPUT_PHONE_INTERNATIONAL_OPTIONS, tuiInputPhoneInternationalOptionsProvider,] = tuiCreateOptions(TUI_INPUT_PHONE_INTERNATIONAL_DEFAULT_OPTIONS);
const NOT_FORM_CONTROL_SYMBOLS = /[^+\d]/g;
class TuiInputPhoneInternationalComponent extends TuiControl {
constructor() {
super(...arguments);
this.list = viewChildren(TuiOption, { read: ElementRef });
this.el = tuiInjectElement();
this.ios = inject(WA_IS_IOS);
this.icons = inject(TUI_COMMON_ICONS);
this.options = inject(TUI_INPUT_PHONE_INTERNATIONAL_OPTIONS);
this.label = inject(TUI_INTERNATIONAL_SEARCH);
this.metadata = toSignal(from(this.options.metadata));
this.names = inject(TUI_COUNTRIES);
this.open = inject(TuiDropdownOpen).open;
this.dropdownEnabled = tuiDropdownEnabled(this.interactive);
this.change = effect(() => this.onChange(this.unmask(this.masked())));
this.search = signal('');
this.size = inject(TUI_TEXTFIELD_OPTIONS).size;
this.masked = tuiValue(this.el);
this.mask = tuiMaskito(computed(() => this.computeMask(this.countryIsoCode(), this.metadata())));
this.filtered = computed(() => this.countries()
.map((iso) => ({
iso,
name: this.names()?.[iso] || '',
code: getCallingCode(iso, this.metadata()),
}))
.filter(({ name, code }) => TUI_DEFAULT_MATCHER(`${name}${code}`, this.search())));
this.$ = inject(TuiActiveZone)
.tuiActiveZoneChange.pipe(filter(() => !this.readOnly()), takeUntilDestroyed())
.subscribe((active) => {
const prefix = `${getCallingCode(this.countryIsoCode(), this.metadata())} `;
this.search.set('');
this.masked.update((value) => {
const fallback = active ? value || prefix : value;
return value === prefix ? '' : fallback;
});
});
this.countrySearch = input(this.options.countrySearch);
this.countryIsoCode = model(this.options.countryIsoCode);
this.countries = input(this.options.countries);
}
writeValue(unmasked) {
const code = this.getCountryCode(unmasked ?? '');
if (code) {
this.countryIsoCode.set(code);
}
super.writeValue(unmasked);
this.masked.set(maskitoTransform(this.value() ?? '', this.mask() || MASKITO_DEFAULT_OPTIONS));
}
onPaste(event) {
const data = tuiIsInputEvent(event) && event.data;
if (!data ||
(!event.inputType.includes('Drop') && !event.inputType.includes('Paste'))) {
return;
}
const code = this.getCountryCode(data);
if (code) {
this.countryIsoCode.set(code);
}
}
onItemClick(code) {
this.el.focus();
this.open.set(false);
this.countryIsoCode.set(code);
this.search.set('');
this.masked.set(maskitoTransform(this.value() || getCallingCode(code, this.metadata()), this.mask() || MASKITO_DEFAULT_OPTIONS));
}
computeMask(countryIsoCode, metadata) {
if (!metadata) {
return MASKITO_DEFAULT_OPTIONS;
}
const { plugins, ...options } = maskitoPhoneOptionsGenerator({
countryIsoCode,
metadata,
separator: this.options.separator,
});
return {
...options,
plugins: [...plugins, maskitoInitialCalibrationPlugin()],
};
}
unmask(maskedValue) {
const value = maskedValue.replaceAll(NOT_FORM_CONTROL_SYMBOLS, '');
const code = getCallingCode(this.countryIsoCode(), this.metadata());
return value === code ? '' : value;
}
getCountryCode(value) {
const metadata = this.metadata();
const phone = value.startsWith(CHAR_PLUS) ? value : `${CHAR_PLUS}${value}`;
return metadata && validatePhoneNumberLength(phone, metadata) !== 'TOO_SHORT'
? (maskitoGetCountryFromNumber(phone, metadata) ?? null)
: null;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiInputPhoneInternationalComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TuiInputPhoneInternationalComponent, isStandalone: true, selector: "input[tuiInputPhoneInternational]", inputs: { countrySearch: { classPropertyName: "countrySearch", publicName: "countrySearch", isSignal: true, isRequired: false, transformFunction: null }, countryIsoCode: { classPropertyName: "countryIsoCode", publicName: "countryIsoCode", isSignal: true, isRequired: false, transformFunction: null }, countries: { classPropertyName: "countries", publicName: "countries", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { countryIsoCode: "countryIsoCodeChange" }, host: { attributes: { "ngSkipHydration": "true", "type": "tel" }, listeners: { "beforeinput.capture": "onPaste($event)", "click": "open.set(false)", "input": "masked.set($event.target.value)" }, properties: { "attr.inputmode": "!ios && open() ? \"none\" : null", "disabled": "disabled()" } }, providers: [
tuiAsControl(TuiInputPhoneInternationalComponent),
tuiFallbackValueProvider(''),
tuiAutoFocusOptionsProvider({ preventScroll: true }),
], viewQueries: [{ propertyName: "list", predicate: TuiOption, descendants: true, read: ElementRef, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: i1.MaskitoDirective }, { directive: i2.TuiWithInput }, { directive: i3.TuiAppearanceProxy }], ngImport: i0, template: "<ng-container *tuiTextfieldContent>\n <button\n appearance=\"textfield\"\n tabindex=\"-1\"\n tuiButton\n tuiChevron\n type=\"button\"\n class=\"t-ipi-select\"\n [attr.data-mode]=\"mode()\"\n [class.t-ipi-select_readonly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n [tuiAppearanceFocus]=\"open()\"\n (click.prevent)=\"interactive() && open.set(!open())\"\n (pointerdown.stop)=\"(0)\"\n >\n <img\n class=\"t-ipi-flag\"\n [alt]=\"names()[countryIsoCode()]\"\n [class.t-ipi-flag_small]=\"size() === 's'\"\n [src]=\"countryIsoCode() | tuiFlag\"\n />\n </button>\n</ng-container>\n<ng-template #filter>\n <div class=\"t-ipi-search\">\n <tui-textfield\n [iconStart]=\"icons.search\"\n [tuiTextfieldSize]=\"size() === 's' ? 's' : 'm'\"\n >\n <input\n autocomplete=\"off\"\n tuiInput\n type=\"text\"\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n [placeholder]=\"label()\"\n [tuiAutoFocus]=\"!ios\"\n (keydown.arrowDown)=\"list()[0]?.nativeElement?.focus()\"\n (ngModelChange)=\"search.set($event)\"\n />\n </tui-textfield>\n </div>\n</ng-template>\n<ng-container *tuiDropdown>\n <!-- To keep it under local injector -->\n @if (countrySearch()) {\n <ng-container *ngTemplateOutlet=\"filter\" />\n }\n <tui-data-list class=\"t-ipi-options\">\n @for (item of filtered(); track $index) {\n <button\n tuiOption\n type=\"button\"\n (click)=\"onItemClick(item.iso)\"\n >\n <img\n alt=\"\"\n class=\"t-ipi-flag\"\n [class.t-ipi-flag_small]=\"size() === 's'\"\n [src]=\"item.iso | tuiFlag\"\n />\n <span tuiTitle>{{ item.name }}</span>\n <span class=\"t-ipi-code\">{{ item.code }}</span>\n </button>\n }\n </tui-data-list>\n</ng-container>\n", styles: ["[tuiInputPhoneInternational][tuiInputPhoneInternational]:where(*[data-tui-version=\"5.8.0\"]){inset-inline-start:var(--t-offset);border-top-left-radius:0;border-bottom-left-radius:0;inline-size:calc(100% - var(--t-offset))}[tuiInputPhoneInternational][tuiInputPhoneInternational]:where(*[data-tui-version=\"5.8.0\"])+[tuiLabel][tuiLabel]{padding-inline-start:var(--t-offset)}tui-textfield[data-size=s]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 4.125rem}tui-textfield[data-size=s]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 .1875rem}tui-textfield[data-size=m]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 4.875rem}tui-textfield[data-size=m]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 -.1875rem}tui-textfield[data-size=l]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 5.25rem}tui-textfield[data-size=l]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 -.1875rem}[data-tui-version=\"5.8.0\"] .t-ipi-select{position:absolute;inset-inline-start:0;border-radius:inherit;border-top-right-radius:0;border-bottom-right-radius:0}[data-tui-version=\"5.8.0\"] .t-ipi-select_readonly{pointer-events:none}[data-tui-version=\"5.8.0\"] .t-ipi-flag{inline-size:1.75rem;block-size:1.75rem;border-radius:100%}[data-tui-version=\"5.8.0\"] .t-ipi-flag_small{inline-size:1.25rem;block-size:1.25rem}[data-tui-version=\"5.8.0\"] .t-ipi-code{color:var(--tui-text-secondary)}[data-tui-version=\"5.8.0\"] .t-ipi-search{position:sticky;z-index:1;inset-block-start:0;background:var(--tui-background-elevation-3);padding:.375rem .375rem 0}@supports (-webkit-touch-callout: none){[data-tui-version=\"5.8.0\"] .t-ipi-search input:focus{animation:tuiPreventIOSScroll 1ms}}@keyframes tuiPreventIOSScroll{0%{opacity:0}to{opacity:1}}[data-tui-version=\"5.8.0\"] tui-dropdown-mobile .t-ipi-search,[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-search{background:var(--tui-background-elevation-1)}[data-tui-version=\"5.8.0\"] tui-dropdown-mobile .t-ipi-options:not(:first-child),[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-options:not(:first-child){min-block-size:calc(100 * var(--tui-viewport-vh) - 8.75rem)}[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-search{inset-block-start:4.5rem;padding-inline-start:0;padding-inline-end:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: TuiAutoFocus, selector: "[tuiAutoFocus]", inputs: ["tuiAutoFocus"] }, { kind: "directive", type: TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: TuiChevron, selector: "[tuiChevron]", inputs: ["tuiChevron"] }, { kind: "component", type: i5.TuiDataListComponent, selector: "tui-data-list", inputs: ["emptyContent", "size"] }, { kind: "directive", type: i5.TuiOption, selector: "button[tuiOption], a[tuiOption], label[tuiOption]", inputs: ["disabled"] }, { kind: "pipe", type: TuiFlagPipe, name: "tuiFlag" }, { kind: "component", type: i6.TuiTextfieldComponent, selector: "tui-textfield:not([multi])", inputs: ["content", "filler"] }, { kind: "directive", type: i6.TuiTextfieldOptionsDirective, selector: "[tuiTextfieldAppearance],[tuiTextfieldSize],[tuiTextfieldCleaner]", inputs: ["tuiTextfieldAppearance", "tuiTextfieldSize", "tuiTextfieldCleaner"] }, { kind: "directive", type: i7.TuiDropdownContent, selector: "ng-template[tuiDropdown]" }, { kind: "directive", type: i2.TuiInputDirective, selector: "input[tuiInput]", inputs: ["readOnly", "invalid", "focused", "state"] }, { kind: "directive", type: TuiTextfieldContent, selector: "ng-template[tuiTextfieldContent]" }, { kind: "directive", type: TuiTitle, selector: "[tuiTitle]", inputs: ["tuiTitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiInputPhoneInternationalComponent, decorators: [{
type: Component,
args: [{ selector: 'input[tuiInputPhoneInternational]', imports: [
FormsModule,
NgTemplateOutlet,
TuiAutoFocus,
TuiButton,
TuiChevron,
TuiDataList,
TuiFlagPipe,
TuiInput,
TuiTextfieldContent,
TuiTitle,
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
tuiAsControl(TuiInputPhoneInternationalComponent),
tuiFallbackValueProvider(''),
tuiAutoFocusOptionsProvider({ preventScroll: true }),
], hostDirectives: [MaskitoDirective, TuiWithInput, TuiAppearanceProxy], host: {
ngSkipHydration: 'true',
type: 'tel',
'[attr.inputmode]': '!ios && open() ? "none" : null',
'[disabled]': 'disabled()',
'(beforeinput.capture)': 'onPaste($event)',
'(click)': 'open.set(false)',
'(input)': 'masked.set($event.target.value)',
}, template: "<ng-container *tuiTextfieldContent>\n <button\n appearance=\"textfield\"\n tabindex=\"-1\"\n tuiButton\n tuiChevron\n type=\"button\"\n class=\"t-ipi-select\"\n [attr.data-mode]=\"mode()\"\n [class.t-ipi-select_readonly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n [tuiAppearanceFocus]=\"open()\"\n (click.prevent)=\"interactive() && open.set(!open())\"\n (pointerdown.stop)=\"(0)\"\n >\n <img\n class=\"t-ipi-flag\"\n [alt]=\"names()[countryIsoCode()]\"\n [class.t-ipi-flag_small]=\"size() === 's'\"\n [src]=\"countryIsoCode() | tuiFlag\"\n />\n </button>\n</ng-container>\n<ng-template #filter>\n <div class=\"t-ipi-search\">\n <tui-textfield\n [iconStart]=\"icons.search\"\n [tuiTextfieldSize]=\"size() === 's' ? 's' : 'm'\"\n >\n <input\n autocomplete=\"off\"\n tuiInput\n type=\"text\"\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n [placeholder]=\"label()\"\n [tuiAutoFocus]=\"!ios\"\n (keydown.arrowDown)=\"list()[0]?.nativeElement?.focus()\"\n (ngModelChange)=\"search.set($event)\"\n />\n </tui-textfield>\n </div>\n</ng-template>\n<ng-container *tuiDropdown>\n <!-- To keep it under local injector -->\n @if (countrySearch()) {\n <ng-container *ngTemplateOutlet=\"filter\" />\n }\n <tui-data-list class=\"t-ipi-options\">\n @for (item of filtered(); track $index) {\n <button\n tuiOption\n type=\"button\"\n (click)=\"onItemClick(item.iso)\"\n >\n <img\n alt=\"\"\n class=\"t-ipi-flag\"\n [class.t-ipi-flag_small]=\"size() === 's'\"\n [src]=\"item.iso | tuiFlag\"\n />\n <span tuiTitle>{{ item.name }}</span>\n <span class=\"t-ipi-code\">{{ item.code }}</span>\n </button>\n }\n </tui-data-list>\n</ng-container>\n", styles: ["[tuiInputPhoneInternational][tuiInputPhoneInternational]:where(*[data-tui-version=\"5.8.0\"]){inset-inline-start:var(--t-offset);border-top-left-radius:0;border-bottom-left-radius:0;inline-size:calc(100% - var(--t-offset))}[tuiInputPhoneInternational][tuiInputPhoneInternational]:where(*[data-tui-version=\"5.8.0\"])+[tuiLabel][tuiLabel]{padding-inline-start:var(--t-offset)}tui-textfield[data-size=s]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 4.125rem}tui-textfield[data-size=s]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 .1875rem}tui-textfield[data-size=m]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 4.875rem}tui-textfield[data-size=m]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 -.1875rem}tui-textfield[data-size=l]:where(*[data-tui-version=\"5.8.0\"]){--t-offset: 5.25rem}tui-textfield[data-size=l]:where(*[data-tui-version=\"5.8.0\"]) .t-ipi-flag{margin:0 -.1875rem}[data-tui-version=\"5.8.0\"] .t-ipi-select{position:absolute;inset-inline-start:0;border-radius:inherit;border-top-right-radius:0;border-bottom-right-radius:0}[data-tui-version=\"5.8.0\"] .t-ipi-select_readonly{pointer-events:none}[data-tui-version=\"5.8.0\"] .t-ipi-flag{inline-size:1.75rem;block-size:1.75rem;border-radius:100%}[data-tui-version=\"5.8.0\"] .t-ipi-flag_small{inline-size:1.25rem;block-size:1.25rem}[data-tui-version=\"5.8.0\"] .t-ipi-code{color:var(--tui-text-secondary)}[data-tui-version=\"5.8.0\"] .t-ipi-search{position:sticky;z-index:1;inset-block-start:0;background:var(--tui-background-elevation-3);padding:.375rem .375rem 0}@supports (-webkit-touch-callout: none){[data-tui-version=\"5.8.0\"] .t-ipi-search input:focus{animation:tuiPreventIOSScroll 1ms}}@keyframes tuiPreventIOSScroll{0%{opacity:0}to{opacity:1}}[data-tui-version=\"5.8.0\"] tui-dropdown-mobile .t-ipi-search,[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-search{background:var(--tui-background-elevation-1)}[data-tui-version=\"5.8.0\"] tui-dropdown-mobile .t-ipi-options:not(:first-child),[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-options:not(:first-child){min-block-size:calc(100 * var(--tui-viewport-vh) - 8.75rem)}[data-tui-version=\"5.8.0\"] tui-sheet-dialog .t-ipi-search{inset-block-start:4.5rem;padding-inline-start:0;padding-inline-end:0}\n"] }]
}] });
function getCallingCode(iso, metadata) {
return metadata ? CHAR_PLUS + getCountryCallingCode(iso, metadata) : '';
}
const TuiInputPhoneInternational = [
TuiInputPhoneInternationalComponent,
TuiLabel,
TuiTextfieldComponent,
TuiTextfieldOptionsDirective,
TuiDropdownContent,
];
/**
* Generated bundle index. Do not edit.
*/
export { TUI_INPUT_PHONE_INTERNATIONAL_DEFAULT_OPTIONS, TUI_INPUT_PHONE_INTERNATIONAL_OPTIONS, TuiInputPhoneInternational, TuiInputPhoneInternationalComponent, tuiInputPhoneInternationalOptionsProvider };
//# sourceMappingURL=taiga-ui-kit-components-input-phone-international.mjs.map