@ngstarter-ui/components 21.0.31 → 21.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,19 @@
1
1
  import { FocusMonitor } from '@angular/cdk/a11y';
2
2
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
3
3
  import * as i0 from '@angular/core';
4
- import { Injectable, Pipe, inject, DestroyRef, ChangeDetectorRef, ElementRef, viewChild, signal, output, input, booleanAttribute, computed, effect, Input as Input$1, Optional, Self, Component } from '@angular/core';
4
+ import { Injectable, inject, DestroyRef, ChangeDetectorRef, ElementRef, viewChild, signal, output, computed, input, booleanAttribute, effect, Input as Input$1, Optional, Self, Component, Pipe } from '@angular/core';
5
5
  import * as i1 from '@angular/forms';
6
6
  import { NgForm, ReactiveFormsModule, FormsModule, NG_VALIDATORS } from '@angular/forms';
7
7
  import * as i2 from '@ngstarter-ui/components/core';
8
8
  import { ErrorStateMatcher, Ripple } from '@ngstarter-ui/components/core';
9
9
  import { FormFieldControl } from '@ngstarter-ui/components/form-field';
10
- import { MenuTrigger, Menu, MenuItem } from '@ngstarter-ui/components/menu';
10
+ import { MenuTrigger, Menu, MenuHeader, MenuItem, MenuDivider } from '@ngstarter-ui/components/menu';
11
11
  import { parsePhoneNumber, parsePhoneNumberFromString, AsYouType } from 'libphonenumber-js';
12
12
  import { Subject } from 'rxjs';
13
13
  import { Icon } from '@ngstarter-ui/components/icon';
14
14
  import { Input } from '@ngstarter-ui/components/input';
15
15
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
- import { Divider } from '@ngstarter-ui/components/divider';
16
+ import { Button } from '@ngstarter-ui/components/button';
17
17
 
18
18
  class CountryCode {
19
19
  allCountries = [
@@ -286,27 +286,10 @@ const phoneValidator = (control) => {
286
286
  return null;
287
287
  };
288
288
 
289
- class SearchPipe {
290
- transform(countries, searchCriteria) {
291
- if (!searchCriteria || searchCriteria === '') {
292
- return countries;
293
- }
294
- return countries.filter((country) => {
295
- return `${country.name}${country.phoneCode}`
296
- .toLowerCase()
297
- .includes(searchCriteria.toLowerCase());
298
- });
299
- }
300
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
301
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, isStandalone: true, name: "search" });
302
- }
303
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, decorators: [{
304
- type: Pipe,
305
- args: [{ name: 'search' }]
306
- }] });
307
-
308
289
  class PhoneInput {
309
290
  ngControl;
291
+ static COUNTRY_RENDER_CHUNK_SIZE = 32;
292
+ static COUNTRY_RENDER_CHUNK_DELAY = 50;
310
293
  _destroyRef = inject(DestroyRef);
311
294
  _changeDetectorRef = inject(ChangeDetectorRef);
312
295
  countryCodeData = inject(CountryCode);
@@ -315,7 +298,7 @@ class PhoneInput {
315
298
  _errorStateMatcher = inject(ErrorStateMatcher);
316
299
  _parentForm = inject(NgForm, { optional: true });
317
300
  static nextId = 0;
318
- menuSearchInput = viewChild('menuSearchInput', ...(ngDevMode ? [{ debugName: "menuSearchInput" }] : /* istanbul ignore next */ []));
301
+ searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
319
302
  focusable = viewChild.required('focusable');
320
303
  _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
321
304
  _errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
@@ -331,8 +314,25 @@ class PhoneInput {
331
314
  selectedCountry = signal(null, ...(ngDevMode ? [{ debugName: "selectedCountry" }] : /* istanbul ignore next */ []));
332
315
  numberInstance;
333
316
  _value;
334
- searchCriteria;
317
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
318
+ visibleCountryCount = signal(PhoneInput.COUNTRY_RENDER_CHUNK_SIZE, ...(ngDevMode ? [{ debugName: "visibleCountryCount" }] : /* istanbul ignore next */ []));
319
+ filteredCountries = computed(() => {
320
+ const searchTerm = this.searchTerm().trim().toLowerCase();
321
+ if (!searchTerm) {
322
+ return this.allCountries;
323
+ }
324
+ return this.allCountries.filter((country) => {
325
+ return `${country.name}${country.phoneCode}`
326
+ .toLowerCase()
327
+ .includes(searchTerm);
328
+ });
329
+ }, ...(ngDevMode ? [{ debugName: "filteredCountries" }] : /* istanbul ignore next */ []));
330
+ visibleCountries = computed(() => {
331
+ return this.filteredCountries().slice(0, this.visibleCountryCount());
332
+ }, ...(ngDevMode ? [{ debugName: "visibleCountries" }] : /* istanbul ignore next */ []));
335
333
  _previousFormattedNumber;
334
+ _countryRenderTimeout;
335
+ _countrySearchFocusTimeout;
336
336
  onTouched = () => { };
337
337
  propagateChange = (_) => { };
338
338
  get focused() {
@@ -445,6 +445,7 @@ class PhoneInput {
445
445
  this._changeDetectorRef.detectChanges();
446
446
  }
447
447
  ngOnDestroy() {
448
+ this.clearCountryMenuTimeouts();
448
449
  this.stateChanges.complete();
449
450
  this._focusMonitor.stopMonitoring(this._elementRef);
450
451
  }
@@ -492,6 +493,69 @@ class PhoneInput {
492
493
  this.onPhoneNumberChange();
493
494
  el.focus();
494
495
  }
496
+ focusSearchInput() {
497
+ this.searchInput()?.nativeElement.focus();
498
+ }
499
+ openCountryMenu() {
500
+ this.visibleCountryCount.set(PhoneInput.COUNTRY_RENDER_CHUNK_SIZE);
501
+ this.scheduleCountryRendering();
502
+ this._countrySearchFocusTimeout = setTimeout(() => this.focusSearchInput());
503
+ }
504
+ closeCountryMenu() {
505
+ this.clearCountryMenuTimeouts();
506
+ this.searchTerm.set('');
507
+ this.visibleCountryCount.set(PhoneInput.COUNTRY_RENDER_CHUNK_SIZE);
508
+ }
509
+ onCountrySearch(searchTerm) {
510
+ this.searchTerm.set(searchTerm);
511
+ this.visibleCountryCount.set(PhoneInput.COUNTRY_RENDER_CHUNK_SIZE);
512
+ this.scheduleCountryRendering();
513
+ }
514
+ clearSearch(event) {
515
+ event.stopPropagation();
516
+ this.onCountrySearch('');
517
+ this.focusSearchInput();
518
+ }
519
+ countryFlagEmoji(shortCode) {
520
+ if (!shortCode || shortCode.length !== 2) {
521
+ return '';
522
+ }
523
+ const upperShortCode = shortCode.toUpperCase();
524
+ return Array.from(upperShortCode)
525
+ .map((letter) => String.fromCodePoint(0x1F1E6 + letter.charCodeAt(0) - 65))
526
+ .join('');
527
+ }
528
+ scheduleCountryRendering() {
529
+ this.clearCountryRenderTimeout();
530
+ if (this.visibleCountryCount() >= this.filteredCountries().length) {
531
+ return;
532
+ }
533
+ this._countryRenderTimeout = setTimeout(() => {
534
+ this.visibleCountryCount.update((count) => {
535
+ return Math.min(count + PhoneInput.COUNTRY_RENDER_CHUNK_SIZE, this.filteredCountries().length);
536
+ });
537
+ this.scheduleCountryRendering();
538
+ this._changeDetectorRef.markForCheck();
539
+ }, PhoneInput.COUNTRY_RENDER_CHUNK_DELAY);
540
+ }
541
+ clearCountryMenuTimeouts() {
542
+ this.clearCountryRenderTimeout();
543
+ this.clearCountrySearchFocusTimeout();
544
+ }
545
+ clearCountryRenderTimeout() {
546
+ if (!this._countryRenderTimeout) {
547
+ return;
548
+ }
549
+ clearTimeout(this._countryRenderTimeout);
550
+ this._countryRenderTimeout = undefined;
551
+ }
552
+ clearCountrySearchFocusTimeout() {
553
+ if (!this._countrySearchFocusTimeout) {
554
+ return;
555
+ }
556
+ clearTimeout(this._countrySearchFocusTimeout);
557
+ this._countrySearchFocusTimeout = undefined;
558
+ }
495
559
  getCountry(shortCode) {
496
560
  return (this.allCountries.find((c) => c.shortCode === shortCode.toLowerCase()) || {
497
561
  name: 'UN',
@@ -619,7 +683,7 @@ class PhoneInput {
619
683
  useValue: phoneValidator,
620
684
  multi: true,
621
685
  },
622
- ], viewQueries: [{ propertyName: "menuSearchInput", first: true, predicate: ["menuSearchInput"], descendants: true, isSignal: true }, { propertyName: "focusable", first: true, predicate: ["focusable"], descendants: true, isSignal: true }], exportAs: ["ngsPhoneInput"], ngImport: i0, template: "<div class=\"phone-input-container\">\n <button\n type=\"button\"\n ngsRipple\n [ngsMenuTriggerFor]=\"menu\"\n class=\"country-selector pe-1.5\"\n [disabled]=\"disabled\"\n (menuOpened)=\"menuSearchInput()?.nativeElement?.focus()\">\n @if (selectedCountry()) {\n <ngs-icon [name]=\"'circle-flags:' + selectedCountry()?.shortCode\" class=\"flag-icon\" />\n\n @if (selectedCountry()?.phoneCode) {\n <span class=\"country-selector-code\">{{ selectedCountry()?.phoneCode }}</span>\n }\n }\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"text-neutral-500 size-4 relative ms-0.5\"/>\n </button>\n <input\n ngsInput\n type=\"tel\"\n inputmode=\"tel\"\n [autocomplete]=\"autocomplete()\"\n (blur)=\"onTouched()\"\n (keypress)=\"onInputKeyPress($event)\"\n [(ngModel)]=\"phoneNumber\"\n (ngModelChange)=\"onPhoneNumberChange()\"\n [errorStateMatcher]=\"errorStateMatcher()\"\n [placeholder]=\"_placeholder()\"\n [disabled]=\"disabled\"\n class=\"grow\"\n #focusable>\n</div>\n<ngs-menu #menu=\"ngsMenu\">\n @for (country of preferredCountriesInDropDown; track country) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <ngs-icon [name]=\"'circle-flags:' + country.shortCode\" class=\"flag-icon\" />\n <div class=\"label-wrapper\">\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchCriteria\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n\n @if (preferredCountriesInDropDown.length) {\n <ngs-divider/>\n }\n\n @for (country of allCountries | search : searchCriteria; track country) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <ngs-icon [name]=\"'circle-flags:' + country.shortCode\" class=\"flag-icon\" />\n <div>\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchCriteria\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n</ngs-menu>\n", styles: [":host{--ngs-phone-input-color: var(--ngs-color-neutral-600);--ngs-phone-input-opacity: 0}@media print{:host{--ngs-phone-input-flag-display: none}}:host .flag-icon{margin-inline-end:calc(var(--spacing, .25rem) * 1);--ngs-icon-size: 18px}:host.is-floating .country-selector{opacity:1}:host .phone-input-container{display:flex;align-items:center}:host input{border:none;background:none;outline:none;font:inherit;width:max-content;box-sizing:border-box;padding-right:6px;padding-left:4px;position:relative;z-index:0}:host .icon-wrapper{padding-right:24px}:host .mdc-button__label{margin-right:auto}:host .country-selector{display:flex;align-items:center;line-height:0;border-radius:0;color:var(--ngs-phone-input-color);flex-shrink:0;height:initial;opacity:var(--ngs-phone-input-opacity, 0);transition:opacity .2s;border:unset}:host .country-selector:disabled{opacity:.8}:host .country-selector-code{color:var(--ngs-phone-input-color);padding-right:0}:host-context(html.dark){--ngs-phone-input-color: var(--ngs-color-neutral-500)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: Ripple, selector: "[ngsRipple]", inputs: ["ngsRippleColor", "ngsRippleUnbounded", "ngsRippleCentered", "ngsRippleRadius", "ngsRippleAnimation", "ngsRippleDisabled", "ngsRippleTrigger"], outputs: ["ngsRippleCenteredChange", "ngsRippleDisabledChange", "ngsRippleTriggerChange"], exportAs: ["ngsRipple"] }, { kind: "directive", type: MenuTrigger, selector: "[ngsMenuTriggerFor]", inputs: ["ngsMenuTriggerFor", "ngsMenuTriggerData", "ngsMenuDisabled", "xPosition", "yPosition", "ngsMenuTriggerRestoreFocus"], outputs: ["menuOpened", "menuClosed"], exportAs: ["ngsMenuTrigger"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: Menu, selector: "ngs-menu", inputs: ["role", "classList", "xPosition", "yPosition"], outputs: ["closed"], exportAs: ["ngsMenu"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MenuItem, selector: "ngs-menu-item, [ngs-menu-item]", inputs: ["disabled", "role", "selected"], outputs: ["_triggered"], exportAs: ["ngsMenuItem"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Divider, selector: "ngs-divider", inputs: ["vertical", "inset", "fixedHeight"], exportAs: ["ngsDivider"] }, { kind: "pipe", type: SearchPipe, name: "search" }] });
686
+ ], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "focusable", first: true, predicate: ["focusable"], descendants: true, isSignal: true }], exportAs: ["ngsPhoneInput"], ngImport: i0, template: "<div class=\"phone-input-container\">\n <button\n type=\"button\"\n ngsRipple\n [ngsMenuTriggerFor]=\"menu\"\n class=\"country-selector pe-1.5\"\n [disabled]=\"disabled\"\n (menuOpened)=\"openCountryMenu()\"\n (menuClosed)=\"closeCountryMenu()\">\n @if (selectedCountry()) {\n <span class=\"flag-icon\" aria-hidden=\"true\">{{ countryFlagEmoji(selectedCountry()?.shortCode) }}</span>\n\n @if (selectedCountry()?.phoneCode) {\n <span class=\"country-selector-code\">{{ selectedCountry()?.phoneCode }}</span>\n }\n }\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"text-neutral-500 size-4 relative ms-0.5\"/>\n </button>\n\n <input\n ngsInput\n type=\"tel\"\n inputmode=\"tel\"\n [autocomplete]=\"autocomplete()\"\n (blur)=\"onTouched()\"\n (keypress)=\"onInputKeyPress($event)\"\n [(ngModel)]=\"phoneNumber\"\n (ngModelChange)=\"onPhoneNumberChange()\"\n [errorStateMatcher]=\"errorStateMatcher()\"\n [placeholder]=\"_placeholder()\"\n [disabled]=\"disabled\"\n class=\"grow\"\n #focusable>\n</div>\n\n<ngs-menu #menu=\"ngsMenu\">\n <ngs-menu-header>\n <div class=\"sticky top-0 z-1 bg-surface-container-lowest\">\n <input #searchInput\n type=\"text\"\n placeholder=\"Search...\"\n autocomplete=\"off\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onCountrySearch($event)\"\n class=\"w-full text-sm focus:outline-none border-b border-border focus:border-b-primary h-14 px-3\">\n @if (searchTerm().trim()) {\n <div class=\"absolute end-1 top-1/2 -translate-y-1/2\">\n <button\n ngsIconButton\n (click)=\"clearSearch($event)\"\n class=\"clear-button\"\n type=\"button\"\n aria-label=\"Clear search\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n }\n </div>\n </ngs-menu-header>\n\n @for (country of preferredCountriesInDropDown; track country.shortCode) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <span class=\"flag-icon-option text-2xl\" aria-hidden=\"true\">{{ countryFlagEmoji(country.shortCode) }}</span>\n <div class=\"label-wrapper\">\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchTerm()\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n\n @if (preferredCountriesInDropDown.length) {\n <ngs-menu-divider/>\n }\n\n @for (country of visibleCountries(); track country.shortCode) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <span class=\"flag-icon-option text-2xl\" aria-hidden=\"true\">{{ countryFlagEmoji(country.shortCode) }}</span>\n <div>\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchTerm()\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n } @empty {\n @if (searchTerm()) {\n <div class=\"text-sm px-4 py-3\">\n <span i18n>No countries found</span>.\n </div>\n }\n }\n\n @if (visibleCountryCount() < filteredCountries().length) {\n <div class=\"country-loading\">\n Loading more countries...\n </div>\n }\n</ngs-menu>\n", styles: [":host{--ngs-phone-input-color: var(--ngs-color-neutral-600);--ngs-phone-input-opacity: 0}@media print{:host{--ngs-phone-input-flag-display: none}}:host .flag-icon{display:var(--ngs-phone-input-flag-display, inline-flex);align-items:center;justify-content:center;width:22px;margin-inline-end:calc(var(--spacing, .25rem) * 1);font-size:22px;line-height:1}:host.is-floating .country-selector{opacity:1}:host .phone-input-container{display:flex;align-items:center}:host .country-empty,:host .country-loading{padding:calc(var(--spacing, .25rem) * 3);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .country-loading{padding-block:calc(var(--spacing, .25rem) * 2)}:host .phone-input-container input{border:none;background:none;outline:none;font:inherit;width:max-content;box-sizing:border-box;padding-right:6px;padding-left:4px;position:relative;z-index:0}:host .icon-wrapper{padding-right:24px}:host .mdc-button__label{margin-right:auto}:host .country-selector{display:flex;align-items:center;line-height:0;border-radius:0;color:var(--ngs-phone-input-color);flex-shrink:0;height:initial;opacity:var(--ngs-phone-input-opacity, 0);transition:opacity .2s;border:unset}:host .country-selector:disabled{opacity:.8}:host .country-selector-code{color:var(--ngs-phone-input-color);padding-right:0}:host-context(html.dark){--ngs-phone-input-color: var(--ngs-color-neutral-500)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: Ripple, selector: "[ngsRipple]", inputs: ["ngsRippleColor", "ngsRippleUnbounded", "ngsRippleCentered", "ngsRippleRadius", "ngsRippleAnimation", "ngsRippleDisabled", "ngsRippleTrigger"], outputs: ["ngsRippleCenteredChange", "ngsRippleDisabledChange", "ngsRippleTriggerChange"], exportAs: ["ngsRipple"] }, { kind: "directive", type: MenuTrigger, selector: "[ngsMenuTriggerFor]", inputs: ["ngsMenuTriggerFor", "ngsMenuTriggerData", "ngsMenuDisabled", "xPosition", "yPosition", "ngsMenuTriggerRestoreFocus"], outputs: ["menuOpened", "menuClosed"], exportAs: ["ngsMenuTrigger"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "component", type: Menu, selector: "ngs-menu", inputs: ["role", "classList", "xPosition", "yPosition"], outputs: ["closed"], exportAs: ["ngsMenu"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MenuHeader, selector: "ngs-menu-header", exportAs: ["ngsMenuHeader"] }, { kind: "component", type: MenuItem, selector: "ngs-menu-item, [ngs-menu-item]", inputs: ["disabled", "role", "selected"], outputs: ["_triggered"], exportAs: ["ngsMenuItem"] }, { kind: "component", type: MenuDivider, selector: "ngs-menu-divider" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }] });
623
687
  }
624
688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PhoneInput, decorators: [{
625
689
  type: Component,
@@ -630,10 +694,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
630
694
  Menu,
631
695
  ReactiveFormsModule,
632
696
  FormsModule,
697
+ MenuHeader,
633
698
  MenuItem,
699
+ MenuDivider,
634
700
  Input,
635
- SearchPipe,
636
- Divider,
701
+ Button,
637
702
  ], providers: [
638
703
  CountryCode,
639
704
  {
@@ -648,7 +713,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
648
713
  ], host: {
649
714
  'class': 'ngs-phone-input',
650
715
  '[class.is-floating]': 'shouldLabelFloat',
651
- }, template: "<div class=\"phone-input-container\">\n <button\n type=\"button\"\n ngsRipple\n [ngsMenuTriggerFor]=\"menu\"\n class=\"country-selector pe-1.5\"\n [disabled]=\"disabled\"\n (menuOpened)=\"menuSearchInput()?.nativeElement?.focus()\">\n @if (selectedCountry()) {\n <ngs-icon [name]=\"'circle-flags:' + selectedCountry()?.shortCode\" class=\"flag-icon\" />\n\n @if (selectedCountry()?.phoneCode) {\n <span class=\"country-selector-code\">{{ selectedCountry()?.phoneCode }}</span>\n }\n }\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"text-neutral-500 size-4 relative ms-0.5\"/>\n </button>\n <input\n ngsInput\n type=\"tel\"\n inputmode=\"tel\"\n [autocomplete]=\"autocomplete()\"\n (blur)=\"onTouched()\"\n (keypress)=\"onInputKeyPress($event)\"\n [(ngModel)]=\"phoneNumber\"\n (ngModelChange)=\"onPhoneNumberChange()\"\n [errorStateMatcher]=\"errorStateMatcher()\"\n [placeholder]=\"_placeholder()\"\n [disabled]=\"disabled\"\n class=\"grow\"\n #focusable>\n</div>\n<ngs-menu #menu=\"ngsMenu\">\n @for (country of preferredCountriesInDropDown; track country) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <ngs-icon [name]=\"'circle-flags:' + country.shortCode\" class=\"flag-icon\" />\n <div class=\"label-wrapper\">\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchCriteria\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n\n @if (preferredCountriesInDropDown.length) {\n <ngs-divider/>\n }\n\n @for (country of allCountries | search : searchCriteria; track country) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <ngs-icon [name]=\"'circle-flags:' + country.shortCode\" class=\"flag-icon\" />\n <div>\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchCriteria\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n</ngs-menu>\n", styles: [":host{--ngs-phone-input-color: var(--ngs-color-neutral-600);--ngs-phone-input-opacity: 0}@media print{:host{--ngs-phone-input-flag-display: none}}:host .flag-icon{margin-inline-end:calc(var(--spacing, .25rem) * 1);--ngs-icon-size: 18px}:host.is-floating .country-selector{opacity:1}:host .phone-input-container{display:flex;align-items:center}:host input{border:none;background:none;outline:none;font:inherit;width:max-content;box-sizing:border-box;padding-right:6px;padding-left:4px;position:relative;z-index:0}:host .icon-wrapper{padding-right:24px}:host .mdc-button__label{margin-right:auto}:host .country-selector{display:flex;align-items:center;line-height:0;border-radius:0;color:var(--ngs-phone-input-color);flex-shrink:0;height:initial;opacity:var(--ngs-phone-input-opacity, 0);transition:opacity .2s;border:unset}:host .country-selector:disabled{opacity:.8}:host .country-selector-code{color:var(--ngs-phone-input-color);padding-right:0}:host-context(html.dark){--ngs-phone-input-color: var(--ngs-color-neutral-500)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
716
+ }, template: "<div class=\"phone-input-container\">\n <button\n type=\"button\"\n ngsRipple\n [ngsMenuTriggerFor]=\"menu\"\n class=\"country-selector pe-1.5\"\n [disabled]=\"disabled\"\n (menuOpened)=\"openCountryMenu()\"\n (menuClosed)=\"closeCountryMenu()\">\n @if (selectedCountry()) {\n <span class=\"flag-icon\" aria-hidden=\"true\">{{ countryFlagEmoji(selectedCountry()?.shortCode) }}</span>\n\n @if (selectedCountry()?.phoneCode) {\n <span class=\"country-selector-code\">{{ selectedCountry()?.phoneCode }}</span>\n }\n }\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"text-neutral-500 size-4 relative ms-0.5\"/>\n </button>\n\n <input\n ngsInput\n type=\"tel\"\n inputmode=\"tel\"\n [autocomplete]=\"autocomplete()\"\n (blur)=\"onTouched()\"\n (keypress)=\"onInputKeyPress($event)\"\n [(ngModel)]=\"phoneNumber\"\n (ngModelChange)=\"onPhoneNumberChange()\"\n [errorStateMatcher]=\"errorStateMatcher()\"\n [placeholder]=\"_placeholder()\"\n [disabled]=\"disabled\"\n class=\"grow\"\n #focusable>\n</div>\n\n<ngs-menu #menu=\"ngsMenu\">\n <ngs-menu-header>\n <div class=\"sticky top-0 z-1 bg-surface-container-lowest\">\n <input #searchInput\n type=\"text\"\n placeholder=\"Search...\"\n autocomplete=\"off\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onCountrySearch($event)\"\n class=\"w-full text-sm focus:outline-none border-b border-border focus:border-b-primary h-14 px-3\">\n @if (searchTerm().trim()) {\n <div class=\"absolute end-1 top-1/2 -translate-y-1/2\">\n <button\n ngsIconButton\n (click)=\"clearSearch($event)\"\n class=\"clear-button\"\n type=\"button\"\n aria-label=\"Clear search\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n }\n </div>\n </ngs-menu-header>\n\n @for (country of preferredCountriesInDropDown; track country.shortCode) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <span class=\"flag-icon-option text-2xl\" aria-hidden=\"true\">{{ countryFlagEmoji(country.shortCode) }}</span>\n <div class=\"label-wrapper\">\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchTerm()\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n }\n\n @if (preferredCountriesInDropDown.length) {\n <ngs-menu-divider/>\n }\n\n @for (country of visibleCountries(); track country.shortCode) {\n <button type=\"button\" ngs-menu-item (click)=\"onCountrySelect(country, focusable)\">\n <div class=\"flex items-center gap-2\">\n <span class=\"flag-icon-option text-2xl\" aria-hidden=\"true\">{{ countryFlagEmoji(country.shortCode) }}</span>\n <div>\n {{ country.name }}\n <span [class.whitespace-nowrap]=\"!searchTerm()\" class=\"text-neutral-500\">{{ country.phoneCode }}</span>\n </div>\n </div>\n </button>\n } @empty {\n @if (searchTerm()) {\n <div class=\"text-sm px-4 py-3\">\n <span i18n>No countries found</span>.\n </div>\n }\n }\n\n @if (visibleCountryCount() < filteredCountries().length) {\n <div class=\"country-loading\">\n Loading more countries...\n </div>\n }\n</ngs-menu>\n", styles: [":host{--ngs-phone-input-color: var(--ngs-color-neutral-600);--ngs-phone-input-opacity: 0}@media print{:host{--ngs-phone-input-flag-display: none}}:host .flag-icon{display:var(--ngs-phone-input-flag-display, inline-flex);align-items:center;justify-content:center;width:22px;margin-inline-end:calc(var(--spacing, .25rem) * 1);font-size:22px;line-height:1}:host.is-floating .country-selector{opacity:1}:host .phone-input-container{display:flex;align-items:center}:host .country-empty,:host .country-loading{padding:calc(var(--spacing, .25rem) * 3);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .country-loading{padding-block:calc(var(--spacing, .25rem) * 2)}:host .phone-input-container input{border:none;background:none;outline:none;font:inherit;width:max-content;box-sizing:border-box;padding-right:6px;padding-left:4px;position:relative;z-index:0}:host .icon-wrapper{padding-right:24px}:host .mdc-button__label{margin-right:auto}:host .country-selector{display:flex;align-items:center;line-height:0;border-radius:0;color:var(--ngs-phone-input-color);flex-shrink:0;height:initial;opacity:var(--ngs-phone-input-opacity, 0);transition:opacity .2s;border:unset}:host .country-selector:disabled{opacity:.8}:host .country-selector-code{color:var(--ngs-phone-input-color);padding-right:0}:host-context(html.dark){--ngs-phone-input-color: var(--ngs-color-neutral-500)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
652
717
  }], ctorParameters: () => [{ type: i1.NgControl, decorators: [{
653
718
  type: Optional
654
719
  }, {
@@ -657,11 +722,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
657
722
  type: Optional
658
723
  }] }, { type: i1.FormGroupDirective, decorators: [{
659
724
  type: Optional
660
- }] }, { type: i2.ErrorStateMatcher }], propDecorators: { menuSearchInput: [{ type: i0.ViewChild, args: ['menuSearchInput', { isSignal: true }] }], focusable: [{ type: i0.ViewChild, args: ['focusable', { isSignal: true }] }], countryChanged: [{ type: i0.Output, args: ["countryChanged"] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], onlyCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "onlyCountries", required: false }] }], preferredCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "preferredCountries", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], defaultSelectedCountryCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSelectedCountryCode", required: false }] }], _placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{
725
+ }] }, { type: i2.ErrorStateMatcher }], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], focusable: [{ type: i0.ViewChild, args: ['focusable', { isSignal: true }] }], countryChanged: [{ type: i0.Output, args: ["countryChanged"] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], onlyCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "onlyCountries", required: false }] }], preferredCountries: [{ type: i0.Input, args: [{ isSignal: true, alias: "preferredCountries", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], defaultSelectedCountryCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSelectedCountryCode", required: false }] }], _placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{
661
726
  type: Input$1,
662
727
  args: [{ alias: 'required', transform: booleanAttribute }]
663
728
  }], phoneDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
664
729
 
730
+ class SearchPipe {
731
+ transform(countries, searchCriteria) {
732
+ if (!searchCriteria || searchCriteria === '') {
733
+ return countries;
734
+ }
735
+ return countries.filter((country) => {
736
+ return `${country.name}${country.phoneCode}`
737
+ .toLowerCase()
738
+ .includes(searchCriteria.toLowerCase());
739
+ });
740
+ }
741
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
742
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, isStandalone: true, name: "search" });
743
+ }
744
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SearchPipe, decorators: [{
745
+ type: Pipe,
746
+ args: [{ name: 'search' }]
747
+ }] });
748
+
665
749
  /**
666
750
  * Generated bundle index. Do not edit.
667
751
  */