@sneat/components 0.15.2 → 0.17.0

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,32 +1,48 @@
1
1
  import { Component, EventEmitter, inject, Input, Output, signal, } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
- import { IonButton, IonButtons, IonIcon, IonItem, IonSelect, IonSelectOption, } from '@ionic/angular/standalone';
3
+ import { IonButton, IonButtons, IonIcon, IonItem, IonLabel, IonSelect, IonSelectOption, } from '@ionic/angular/standalone';
4
+ import { SelectFromListComponent } from '@sneat/ui';
4
5
  import { CountriesLoaderService } from '../country-selector';
5
6
  import * as i0 from "@angular/core";
6
7
  import * as i1 from "@angular/forms";
7
8
  export class CountryInputComponent {
8
9
  constructor() {
9
10
  this.countriesLoader = inject(CountriesLoaderService);
11
+ this.mode = 'list';
10
12
  this.canReset = true;
11
13
  this.label = 'Country';
12
14
  this.countryID = '';
13
15
  this.countryIDChange = new EventEmitter();
14
16
  this.countries = signal([], ...(ngDevMode ? [{ debugName: "countries" }] : []));
17
+ // True when the country list failed to load — surfaced in the UI with a Retry.
18
+ this.loadFailed = this.countriesLoader.loadFailed;
19
+ // Match on ISO code as well as title (select-from-list filters title by default).
20
+ this.filterCountryByCode = (item, filter) => {
21
+ const f = filter.trim().toUpperCase();
22
+ const c = item;
23
+ return c.id === f || c.id3.startsWith(f);
24
+ };
15
25
  }
16
26
  ngOnInit() {
17
- // Load countries data
27
+ this.load();
28
+ }
29
+ load() {
18
30
  this.countriesLoader.getCountries().then((countries) => {
19
31
  this.countries.set(countries);
20
32
  });
21
33
  }
22
- // constructor(
23
- // // private readonly countrySelectorService: CountrySelectorService,
24
- // ) {
25
- // }
34
+ retry() {
35
+ this.countriesLoader.reload().then(() => this.load());
36
+ }
37
+ // Dropdown mode: ion-select ngModel has already updated countryID.
26
38
  onCountryChanged() {
27
- // console.log('CountryInputComponent.onCountryChanged()', this.countryID);
28
39
  this.countryIDChange.emit(this.countryID);
29
40
  }
41
+ // List mode: select-from-list emits the chosen id via (valueChange).
42
+ onListValueChanged(countryID) {
43
+ this.countryID = countryID;
44
+ this.countryIDChange.emit(countryID);
45
+ }
30
46
  reset(event) {
31
47
  event.preventDefault();
32
48
  event.stopPropagation();
@@ -34,20 +50,24 @@ export class CountryInputComponent {
34
50
  this.countryIDChange.emit('');
35
51
  }
36
52
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountryInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CountryInputComponent, isStandalone: true, selector: "sneat-country-input", inputs: { canReset: "canReset", label: "label", countryID: "countryID" }, outputs: { countryIDChange: "countryIDChange" }, ngImport: i0, template: "<ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">All</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n <!-- disable for now as select is showing incorrectyl and lookup us not linked-->\n <!--\t<ion-buttons slot=\"end\" class=\"ion-no-margin ion-no-padding\">-->\n <!--\t\t<ion-button *ngIf=\"canReset && countryID\"-->\n <!--\t\t\t\t\t\t\t\tcolor=\"medium\"-->\n <!--\t\t\t\t\t\t\t\t(click)=\"reset($event)\" [disabled]=\"!canReset || !countryID\">-->\n <!--\t\t\t<ion-icon name=\"close-outline\"></ion-icon>-->\n <!--\t\t</ion-button>-->\n <!--\t\t<ion-button (click)=\"openCountrySelector($event)\">-->\n <!--\t\t\t<ion-label>...</ion-label>-->\n <!--\t\t</ion-button>-->\n <!--\t</ion-buttons>-->\n</ion-item>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
53
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CountryInputComponent, isStandalone: true, selector: "sneat-country-input", inputs: { mode: "mode", canReset: "canReset", label: "label", countryID: "countryID" }, outputs: { countryIDChange: "countryIDChange" }, ngImport: i0, template: "@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n@if (mode === \"dropdown\") {\n <ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">Any</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n} @else {\n <!-- 'list' mode: inline searchable list while empty, collapses to a dropdown\n once a country is picked (sneat-select-from-list). -->\n <sneat-select-from-list\n [value]=\"countryID\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n (valueChange)=\"onListValueChanged($event)\"\n [filterItem]=\"filterCountryByCode\"\n />\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SelectFromListComponent, selector: "sneat-select-from-list", inputs: ["value", "filterLabel", "label", "listLabel", "listLabelColor", "isFilterable", "isLoading", "items", "items$", "lastItemLines", "labelPlacement", "justify", "other", "canAdd", "filterItem", "selectMode", "isReadonly", "$isProcessing", "sortBy"], outputs: ["valueChange", "filterChanged"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
38
54
  }
39
55
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountryInputComponent, decorators: [{
40
56
  type: Component,
41
57
  args: [{ selector: 'sneat-country-input', imports: [
42
58
  FormsModule,
59
+ SelectFromListComponent,
43
60
  IonItem,
61
+ IonLabel,
44
62
  IonSelect,
45
63
  IonSelectOption,
46
64
  IonButtons,
47
65
  IonButton,
48
66
  IonIcon,
49
- ], template: "<ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">All</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n <!-- disable for now as select is showing incorrectyl and lookup us not linked-->\n <!--\t<ion-buttons slot=\"end\" class=\"ion-no-margin ion-no-padding\">-->\n <!--\t\t<ion-button *ngIf=\"canReset && countryID\"-->\n <!--\t\t\t\t\t\t\t\tcolor=\"medium\"-->\n <!--\t\t\t\t\t\t\t\t(click)=\"reset($event)\" [disabled]=\"!canReset || !countryID\">-->\n <!--\t\t\t<ion-icon name=\"close-outline\"></ion-icon>-->\n <!--\t\t</ion-button>-->\n <!--\t\t<ion-button (click)=\"openCountrySelector($event)\">-->\n <!--\t\t\t<ion-label>...</ion-label>-->\n <!--\t\t</ion-button>-->\n <!--\t</ion-buttons>-->\n</ion-item>\n" }]
50
- }], propDecorators: { canReset: [{
67
+ ], template: "@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n@if (mode === \"dropdown\") {\n <ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">Any</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n} @else {\n <!-- 'list' mode: inline searchable list while empty, collapses to a dropdown\n once a country is picked (sneat-select-from-list). -->\n <sneat-select-from-list\n [value]=\"countryID\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n (valueChange)=\"onListValueChanged($event)\"\n [filterItem]=\"filterCountryByCode\"\n />\n}\n" }]
68
+ }], propDecorators: { mode: [{
69
+ type: Input
70
+ }], canReset: [{
51
71
  type: Input
52
72
  }], label: [{
53
73
  type: Input
@@ -1 +1 @@
1
- {"version":3,"file":"country-input.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-input/country-input.component.ts","../../../../../../libs/components/src/lib/country-input/country-input.component.html"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,YAAY,EACZ,MAAM,EACN,KAAK,EAEL,MAAM,EACN,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACL,SAAS,EACT,UAAU,EACV,OAAO,EACP,OAAO,EACP,SAAS,EACT,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAY,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;;;AAevE,MAAM,OAAO,qBAAqB;IAblC;QAcmB,oBAAe,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAEzD,aAAQ,GAAG,IAAI,CAAC;QAChB,UAAK,GAAG,SAAS,CAAC;QAClB,cAAS,GAAG,EAAE,CAAC;QACd,oBAAe,GAAG,IAAI,YAAY,EAAU,CAAC;QAE9C,cAAS,GAAG,MAAM,CAAsB,EAAE,qDAAC,CAAC;KAkCtD;IAhCC,QAAQ;QACN,sBAAsB;QACtB,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;YACrD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,eAAe;IACf,uEAAuE;IACvE,MAAM;IACN,IAAI;IAEG,gBAAgB;QACrB,2EAA2E;QAC3E,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,KAAY;QAChB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;8GAhCU,qBAAqB;kGAArB,qBAAqB,0MCjClC,gwCAiCA,2CDTI,WAAW,+VACX,OAAO,0NACP,SAAS,kVACT,eAAe,6FACf,UAAU,8EACV,SAAS,oPACT,OAAO;;2FAGE,qBAAqB;kBAbjC,SAAS;+BACE,qBAAqB,WAEtB;wBACP,WAAW;wBACX,OAAO;wBACP,SAAS;wBACT,eAAe;wBACf,UAAU;wBACV,SAAS;wBACT,OAAO;qBACR;;sBAKA,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,MAAM","sourcesContent":["import {\n Component,\n EventEmitter,\n inject,\n Input,\n OnInit,\n Output,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonIcon,\n IonItem,\n IonSelect,\n IonSelectOption,\n} from '@ionic/angular/standalone';\nimport { ICountry, CountriesLoaderService } from '../country-selector';\n\n@Component({\n selector: 'sneat-country-input',\n templateUrl: './country-input.component.html',\n imports: [\n FormsModule,\n IonItem,\n IonSelect,\n IonSelectOption,\n IonButtons,\n IonButton,\n IonIcon,\n ],\n})\nexport class CountryInputComponent implements OnInit {\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n @Input() canReset = true;\n @Input() label = 'Country';\n @Input() countryID = '';\n @Output() countryIDChange = new EventEmitter<string>();\n\n readonly countries = signal<readonly ICountry[]>([]);\n\n ngOnInit(): void {\n // Load countries data\n this.countriesLoader.getCountries().then((countries) => {\n this.countries.set(countries);\n });\n }\n\n // constructor(\n // \t// private readonly countrySelectorService: CountrySelectorService,\n // ) {\n // }\n\n public onCountryChanged(): void {\n // console.log('CountryInputComponent.onCountryChanged()', this.countryID);\n this.countryIDChange.emit(this.countryID);\n }\n\n reset(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.countryID = '';\n this.countryIDChange.emit('');\n }\n\n // protected openCountrySelector(): void {\n // \t// const options: ISelectorOptions<ICountry> = {\n // \t// \titems: of(countries),\n // \t// };\n // \t// this.countrySelectorService\n // \t// \t.selectSingleInModal(options)\n // \t// \t.then();\n // }\n}\n","<ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">All</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n <!-- disable for now as select is showing incorrectyl and lookup us not linked-->\n <!--\t<ion-buttons slot=\"end\" class=\"ion-no-margin ion-no-padding\">-->\n <!--\t\t<ion-button *ngIf=\"canReset && countryID\"-->\n <!--\t\t\t\t\t\t\t\tcolor=\"medium\"-->\n <!--\t\t\t\t\t\t\t\t(click)=\"reset($event)\" [disabled]=\"!canReset || !countryID\">-->\n <!--\t\t\t<ion-icon name=\"close-outline\"></ion-icon>-->\n <!--\t\t</ion-button>-->\n <!--\t\t<ion-button (click)=\"openCountrySelector($event)\">-->\n <!--\t\t\t<ion-label>...</ion-label>-->\n <!--\t\t</ion-button>-->\n <!--\t</ion-buttons>-->\n</ion-item>\n"]}
1
+ {"version":3,"file":"country-input.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-input/country-input.component.ts","../../../../../../libs/components/src/lib/country-input/country-input.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n EventEmitter,\n inject,\n Input,\n OnInit,\n Output,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonIcon,\n IonItem,\n IonLabel,\n IonSelect,\n IonSelectOption,\n} from '@ionic/angular/standalone';\nimport { ISelectItem, SelectFromListComponent } from '@sneat/ui';\nimport { ICountry, CountriesLoaderService } from '../country-selector';\n\n// How the country picker presents itself:\n// - 'list' (default): an inline, searchable LIST while empty that collapses\n// to a compact dropdown once a country is chosen — the pattern the\n// contact wizard uses (via sneat-select-from-list).\n// - 'dropdown': always a dropdown (popover), even when empty.\nexport type CountryInputMode = 'list' | 'dropdown';\n\n@Component({\n selector: 'sneat-country-input',\n templateUrl: './country-input.component.html',\n imports: [\n FormsModule,\n SelectFromListComponent,\n IonItem,\n IonLabel,\n IonSelect,\n IonSelectOption,\n IonButtons,\n IonButton,\n IonIcon,\n ],\n})\nexport class CountryInputComponent implements OnInit {\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n @Input() mode: CountryInputMode = 'list';\n @Input() canReset = true;\n @Input() label = 'Country';\n @Input() countryID = '';\n @Output() countryIDChange = new EventEmitter<string>();\n\n readonly countries = signal<readonly ICountry[]>([]);\n\n // True when the country list failed to load — surfaced in the UI with a Retry.\n protected readonly loadFailed = this.countriesLoader.loadFailed;\n\n ngOnInit(): void {\n this.load();\n }\n\n private load(): void {\n this.countriesLoader.getCountries().then((countries) => {\n this.countries.set(countries);\n });\n }\n\n protected retry(): void {\n this.countriesLoader.reload().then(() => this.load());\n }\n\n // Dropdown mode: ion-select ngModel has already updated countryID.\n public onCountryChanged(): void {\n this.countryIDChange.emit(this.countryID);\n }\n\n // List mode: select-from-list emits the chosen id via (valueChange).\n protected onListValueChanged(countryID: string): void {\n this.countryID = countryID;\n this.countryIDChange.emit(countryID);\n }\n\n reset(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.countryID = '';\n this.countryIDChange.emit('');\n }\n\n // Match on ISO code as well as title (select-from-list filters title by default).\n protected readonly filterCountryByCode = (\n item: ISelectItem,\n filter: string,\n ): boolean => {\n const f = filter.trim().toUpperCase();\n const c = item as ICountry;\n return c.id === f || c.id3.startsWith(f);\n };\n}\n","@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n@if (mode === \"dropdown\") {\n <ion-item class=\"sneat-tiny-end-padding\" lines=\"none\">\n <ion-select\n interface=\"popover\"\n [label]=\"label\"\n [(ngModel)]=\"countryID\"\n (ionChange)=\"onCountryChanged()\"\n >\n <ion-select-option value=\"\">Any</ion-select-option>\n @for (c of countries(); track c.id) {\n <ion-select-option [value]=\"c.id\">\n {{ c.title }} {{ c.emoji }}\n </ion-select-option>\n }\n </ion-select>\n @if (countryID && canReset) {\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"reset($event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n} @else {\n <!-- 'list' mode: inline searchable list while empty, collapses to a dropdown\n once a country is picked (sneat-select-from-list). -->\n <sneat-select-from-list\n [value]=\"countryID\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n (valueChange)=\"onListValueChanged($event)\"\n [filterItem]=\"filterCountryByCode\"\n />\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { inject, Injectable } from '@angular/core';
1
+ import { inject, Injectable, signal } from '@angular/core';
2
2
  import { HttpClient } from '@angular/common/http';
3
3
  import { firstValueFrom } from 'rxjs';
4
4
  import * as i0 from "@angular/core";
@@ -9,6 +9,12 @@ import * as i0 from "@angular/core";
9
9
  export class CountriesLoaderService {
10
10
  constructor() {
11
11
  this.http = inject(HttpClient);
12
+ /**
13
+ * True when the last load attempt failed (countries.json couldn't be
14
+ * fetched/parsed). Consumers can read this to show an error in the UI. Reset
15
+ * to false on a successful (re)load. See `reload()` to retry.
16
+ */
17
+ this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
12
18
  }
13
19
  /**
14
20
  * Loads country data from JSON file.
@@ -22,9 +28,11 @@ export class CountriesLoaderService {
22
28
  this.loadPromise = (async () => {
23
29
  try {
24
30
  this.countriesData = await firstValueFrom(this.http.get('assets/data/countries.json'));
31
+ this.loadFailed.set(false);
25
32
  }
26
33
  catch (error) {
27
34
  console.error('Failed to load countries:', error);
35
+ this.loadFailed.set(true);
28
36
  // Set to empty to avoid repeated failures
29
37
  this.countriesData = {
30
38
  countriesByID: {},
@@ -41,6 +49,15 @@ export class CountriesLoaderService {
41
49
  }
42
50
  return this.loadPromise;
43
51
  }
52
+ /**
53
+ * Discards the cached data and re-attempts the load — used to retry after a
54
+ * failure (`loadFailed()` is true). Resolves once the reload settles.
55
+ */
56
+ async reload() {
57
+ this.countriesData = undefined;
58
+ this.loadPromise = undefined;
59
+ await this.loadCountries();
60
+ }
44
61
  /**
45
62
  * Gets all countries as an array.
46
63
  * Automatically loads country data if not yet loaded.
@@ -1 +1 @@
1
- {"version":3,"file":"countries-loader.service.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/countries-loader.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;;AAQtC;;;GAGG;AAEH,MAAM,OAAO,sBAAsB;IADnC;QAEmB,SAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;KAmF5C;IA/EC;;;OAGG;IACK,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO,CAAC,iBAAiB;QAC3B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,IAAI,CAAC;oBACH,IAAI,CAAC,aAAa,GAAG,MAAM,cAAc,CACvC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAgB,4BAA4B,CAAC,CAC3D,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;oBAClD,0CAA0C;oBAC1C,IAAI,CAAC,aAAa,GAAG;wBACnB,aAAa,EAAE,EAAE;wBACjB,cAAc,EAAE;4BACd,EAAE,EAAE,IAAI;4BACR,GAAG,EAAE,KAAK;4BACV,KAAK,EAAE,SAAS;4BAChB,UAAU,EAAE,EAAE;4BACd,KAAK,EAAE,KAAK;yBACb;qBACF,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAc,CAAC,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB;QACpB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,aAAc,CAAC,aAAa,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,EAAU;QAC7B,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,aAAc,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,iBAAiB;QACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,aAAc,CAAC,cAAc,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,oBAAoB,CAAC,MAAiB;QAC1C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5C,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,CAAC;8GAnFU,sBAAsB;kHAAtB,sBAAsB,cADT,MAAM;;2FACnB,sBAAsB;kBADlC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { GeoRegion, ICountry } from './countries';\n\ninterface CountriesData {\n countriesByID: Record<string, ICountry>;\n unknownCountry: ICountry;\n}\n\n/**\n * Service to lazy load country data.\n * This reduces the initial bundle size by loading country data on-demand.\n */\n@Injectable({ providedIn: 'root' })\nexport class CountriesLoaderService {\n private readonly http = inject(HttpClient);\n private countriesData?: CountriesData;\n private loadPromise?: Promise<void>;\n\n /**\n * Loads country data from JSON file.\n * Subsequent calls return the same promise (singleton pattern).\n */\n private async loadCountries(): Promise<void> {\n if (this.countriesData) {\n return; // Already loaded\n }\n\n if (!this.loadPromise) {\n this.loadPromise = (async () => {\n try {\n this.countriesData = await firstValueFrom(\n this.http.get<CountriesData>('assets/data/countries.json'),\n );\n } catch (error) {\n console.error('Failed to load countries:', error);\n // Set to empty to avoid repeated failures\n this.countriesData = {\n countriesByID: {},\n unknownCountry: {\n id: '--',\n id3: '---',\n title: 'Unknown',\n geoRegions: [],\n emoji: '🏳️',\n },\n };\n }\n })();\n }\n\n return this.loadPromise;\n }\n\n /**\n * Gets all countries as an array.\n * Automatically loads country data if not yet loaded.\n */\n async getCountries(): Promise<readonly ICountry[]> {\n await this.loadCountries();\n return Object.values(this.countriesData!.countriesByID);\n }\n\n /**\n * Gets countries by ID lookup.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByID(): Promise<Record<string, ICountry>> {\n await this.loadCountries();\n return this.countriesData!.countriesByID;\n }\n\n /**\n * Gets a specific country by ID.\n * Automatically loads country data if not yet loaded.\n */\n async getCountryByID(id: string): Promise<ICountry | undefined> {\n await this.loadCountries();\n return this.countriesData!.countriesByID[id];\n }\n\n /**\n * Gets the unknown country constant.\n * Automatically loads country data if not yet loaded.\n */\n async getUnknownCountry(): Promise<ICountry> {\n await this.loadCountries();\n return this.countriesData!.unknownCountry;\n }\n\n /**\n * Gets countries filtered by geo region.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByRegion(region: GeoRegion): Promise<readonly ICountry[]> {\n const countries = await this.getCountries();\n return countries.filter((c) => c.geoRegions.includes(region));\n }\n}\n"]}
1
+ {"version":3,"file":"countries-loader.service.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/countries-loader.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, Injectable, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport { GeoRegion, ICountry } from './countries';\n\ninterface CountriesData {\n countriesByID: Record<string, ICountry>;\n unknownCountry: ICountry;\n}\n\n/**\n * Service to lazy load country data.\n * This reduces the initial bundle size by loading country data on-demand.\n */\n@Injectable({ providedIn: 'root' })\nexport class CountriesLoaderService {\n private readonly http = inject(HttpClient);\n private countriesData?: CountriesData;\n private loadPromise?: Promise<void>;\n\n /**\n * True when the last load attempt failed (countries.json couldn't be\n * fetched/parsed). Consumers can read this to show an error in the UI. Reset\n * to false on a successful (re)load. See `reload()` to retry.\n */\n readonly loadFailed = signal(false);\n\n /**\n * Loads country data from JSON file.\n * Subsequent calls return the same promise (singleton pattern).\n */\n private async loadCountries(): Promise<void> {\n if (this.countriesData) {\n return; // Already loaded\n }\n\n if (!this.loadPromise) {\n this.loadPromise = (async () => {\n try {\n this.countriesData = await firstValueFrom(\n this.http.get<CountriesData>('assets/data/countries.json'),\n );\n this.loadFailed.set(false);\n } catch (error) {\n console.error('Failed to load countries:', error);\n this.loadFailed.set(true);\n // Set to empty to avoid repeated failures\n this.countriesData = {\n countriesByID: {},\n unknownCountry: {\n id: '--',\n id3: '---',\n title: 'Unknown',\n geoRegions: [],\n emoji: '🏳️',\n },\n };\n }\n })();\n }\n\n return this.loadPromise;\n }\n\n /**\n * Discards the cached data and re-attempts the load — used to retry after a\n * failure (`loadFailed()` is true). Resolves once the reload settles.\n */\n async reload(): Promise<void> {\n this.countriesData = undefined;\n this.loadPromise = undefined;\n await this.loadCountries();\n }\n\n /**\n * Gets all countries as an array.\n * Automatically loads country data if not yet loaded.\n */\n async getCountries(): Promise<readonly ICountry[]> {\n await this.loadCountries();\n return Object.values(this.countriesData!.countriesByID);\n }\n\n /**\n * Gets countries by ID lookup.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByID(): Promise<Record<string, ICountry>> {\n await this.loadCountries();\n return this.countriesData!.countriesByID;\n }\n\n /**\n * Gets a specific country by ID.\n * Automatically loads country data if not yet loaded.\n */\n async getCountryByID(id: string): Promise<ICountry | undefined> {\n await this.loadCountries();\n return this.countriesData!.countriesByID[id];\n }\n\n /**\n * Gets the unknown country constant.\n * Automatically loads country data if not yet loaded.\n */\n async getUnknownCountry(): Promise<ICountry> {\n await this.loadCountries();\n return this.countriesData!.unknownCountry;\n }\n\n /**\n * Gets countries filtered by geo region.\n * Automatically loads country data if not yet loaded.\n */\n async getCountriesByRegion(region: GeoRegion): Promise<readonly ICountry[]> {\n const countries = await this.getCountries();\n return countries.filter((c) => c.geoRegions.includes(region));\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { Component, computed, EventEmitter, inject, Input, Output, signal, } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
- import { IonSegment, IonSegmentButton } from '@ionic/angular/standalone';
3
+ import { IonButton, IonItem, IonLabel, IonSegment, IonSegmentButton, } from '@ionic/angular/standalone';
4
4
  import { SelectFromListComponent } from '@sneat/ui';
5
5
  import { CountriesLoaderService } from './countries-loader.service';
6
6
  import * as i0 from "@angular/core";
@@ -23,6 +23,8 @@ export class CountrySelectorComponent {
23
23
  this.geoRegion = signal('All', ...(ngDevMode ? [{ debugName: "geoRegion" }] : []));
24
24
  this.allCountries = signal([], ...(ngDevMode ? [{ debugName: "allCountries" }] : []));
25
25
  this.unknownCountry = signal(undefined, ...(ngDevMode ? [{ debugName: "unknownCountry" }] : []));
26
+ // True when the country list failed to load — surfaced in the UI with a Retry.
27
+ this.loadFailed = this.countriesLoader.loadFailed;
26
28
  this.countries = computed(() => {
27
29
  const allCountries = this.allCountries();
28
30
  if (allCountries.length === 0) {
@@ -48,7 +50,9 @@ export class CountrySelectorComponent {
48
50
  };
49
51
  }
50
52
  ngOnInit() {
51
- // Load countries data
53
+ this.load();
54
+ }
55
+ load() {
52
56
  this.countriesLoader.getCountries().then((countries) => {
53
57
  this.allCountries.set(countries);
54
58
  });
@@ -56,6 +60,10 @@ export class CountrySelectorComponent {
56
60
  this.unknownCountry.set(unknown);
57
61
  });
58
62
  }
63
+ // Retry loading the country list after a failure (loadFailed()).
64
+ retry() {
65
+ this.countriesLoader.reload().then(() => this.load());
66
+ }
59
67
  // protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All';
60
68
  onChanged() {
61
69
  this.countryIDChange.emit(this.countryID);
@@ -79,11 +87,19 @@ export class CountrySelectorComponent {
79
87
  this.geoRegion.set(event.detail.value);
80
88
  }
81
89
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountrySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
82
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CountrySelectorComponent, isStandalone: true, selector: "sneat-country-selector", inputs: { countryID: "countryID", defaultCountryID: "defaultCountryID", readonly: "readonly", disabled: "disabled", label: "label", canBeUnknown: "canBeUnknown" }, outputs: { countryIDChange: "countryIDChange" }, usesOnChanges: true, ngImport: i0, template: "<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SelectFromListComponent, selector: "sneat-select-from-list", inputs: ["value", "filterLabel", "label", "listLabel", "listLabelColor", "isFilterable", "isLoading", "items", "items$", "lastItemLines", "labelPlacement", "justify", "other", "canAdd", "filterItem", "selectMode", "isReadonly", "$isProcessing", "sortBy"], outputs: ["valueChange", "filterChanged"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }] }); }
90
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CountrySelectorComponent, isStandalone: true, selector: "sneat-country-selector", inputs: { countryID: "countryID", defaultCountryID: "defaultCountryID", readonly: "readonly", disabled: "disabled", label: "label", canBeUnknown: "canBeUnknown" }, outputs: { countryIDChange: "countryIDChange" }, usesOnChanges: true, ngImport: i0, template: "@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SelectFromListComponent, selector: "sneat-select-from-list", inputs: ["value", "filterLabel", "label", "listLabel", "listLabelColor", "isFilterable", "isLoading", "items", "items$", "lastItemLines", "labelPlacement", "justify", "other", "canAdd", "filterItem", "selectMode", "isReadonly", "$isProcessing", "sortBy"], outputs: ["valueChange", "filterChanged"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }] }); }
83
91
  }
84
92
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountrySelectorComponent, decorators: [{
85
93
  type: Component,
86
- args: [{ selector: 'sneat-country-selector', imports: [FormsModule, SelectFromListComponent, IonSegment, IonSegmentButton], template: "<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n" }]
94
+ args: [{ selector: 'sneat-country-selector', imports: [
95
+ FormsModule,
96
+ SelectFromListComponent,
97
+ IonSegment,
98
+ IonSegmentButton,
99
+ IonItem,
100
+ IonLabel,
101
+ IonButton,
102
+ ], template: "@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n" }]
87
103
  }], propDecorators: { countryID: [{
88
104
  type: Input,
89
105
  args: [{ required: true }]
@@ -1 +1 @@
1
- {"version":3,"file":"country-selector.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/country-selector.component.ts","../../../../../../libs/components/src/lib/country-selector/country-selector.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnInit,\n Output,\n signal,\n SimpleChanges,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { IonSegment, IonSegmentButton } from '@ionic/angular/standalone';\nimport { ISelectItem, SelectFromListComponent } from '@sneat/ui';\nimport { GeoRegion, ICountry } from './countries';\nimport { CountriesLoaderService } from './countries-loader.service';\n\n@Component({\n selector: 'sneat-country-selector',\n templateUrl: './country-selector.component.html',\n imports: [FormsModule, SelectFromListComponent, IonSegment, IonSegmentButton],\n})\nexport class CountrySelectorComponent implements OnInit, OnChanges {\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n protected readonly geoRegions: readonly {\n readonly id: string;\n title?: string;\n }[] = [\n { id: 'Europe' },\n { id: 'Asia' },\n { id: 'Americas' },\n { id: 'Africa' },\n ];\n\n @Input({ required: true }) countryID?: string;\n\n @Input() defaultCountryID?: string;\n\n @Input() readonly = false;\n @Input() disabled = false;\n @Input() label = 'search';\n @Input() canBeUnknown = false;\n\n @Output() readonly countryIDChange = new EventEmitter<string>();\n\n protected readonly filter = signal<string>('');\n protected readonly geoRegion = signal<GeoRegion | 'All' | 'Americas'>('All');\n protected readonly allCountries = signal<readonly ICountry[]>([]);\n protected readonly unknownCountry = signal<ICountry | undefined>(undefined);\n\n protected readonly countries = computed<readonly ISelectItem[]>(() => {\n const allCountries = this.allCountries();\n if (allCountries.length === 0) {\n return []; // Data not loaded yet\n }\n\n const filter = this.filter();\n let countriesToShow: readonly ISelectItem[] = filter\n ? allCountries\n : allCountries.filter(\n (c) =>\n this.geoRegion() === 'All' ||\n (this.geoRegion() === 'Americas' &&\n (c.geoRegions.includes('North America') ||\n c.geoRegions.includes('South America'))) ||\n c.geoRegions.includes(this.geoRegion() as GeoRegion),\n );\n if (this.canBeUnknown && this.unknownCountry()) {\n countriesToShow = [...countriesToShow, this.unknownCountry()!];\n }\n return countriesToShow;\n });\n\n ngOnInit(): void {\n // Load countries data\n this.countriesLoader.getCountries().then((countries) => {\n this.allCountries.set(countries);\n });\n this.countriesLoader.getUnknownCountry().then((unknown) => {\n this.unknownCountry.set(unknown);\n });\n }\n\n // protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All';\n\n protected onChanged(): void {\n this.countryIDChange.emit(this.countryID);\n }\n\n protected onFilterChanged(filter: string): void {\n this.filter.set(filter);\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['country'] && this.countryID === '--' && !this.canBeUnknown) {\n this.countryID = undefined;\n }\n if (\n changes['defaultCountryID'] &&\n !this.countryID &&\n this.defaultCountryID &&\n this.defaultCountryID !== '--'\n ) {\n this.countryID = this.defaultCountryID;\n this.countryIDChange.emit(this.countryID);\n }\n }\n\n protected onRegionChanged(event: Event): void {\n this.geoRegion.set(\n (event as CustomEvent).detail.value as GeoRegion | 'All' | 'Americas',\n );\n }\n\n protected readonly filterCountryByCode = (\n item: ISelectItem,\n filter: string,\n ) => {\n const f = filter.trim().toUpperCase();\n const c = item as ICountry;\n return c.id === f || c.id3.startsWith(f);\n };\n}\n","<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n"]}
1
+ {"version":3,"file":"country-selector.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/country-selector/country-selector.component.ts","../../../../../../libs/components/src/lib/country-selector/country-selector.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnInit,\n Output,\n signal,\n SimpleChanges,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonItem,\n IonLabel,\n IonSegment,\n IonSegmentButton,\n} from '@ionic/angular/standalone';\nimport { ISelectItem, SelectFromListComponent } from '@sneat/ui';\nimport { GeoRegion, ICountry } from './countries';\nimport { CountriesLoaderService } from './countries-loader.service';\n\n@Component({\n selector: 'sneat-country-selector',\n templateUrl: './country-selector.component.html',\n imports: [\n FormsModule,\n SelectFromListComponent,\n IonSegment,\n IonSegmentButton,\n IonItem,\n IonLabel,\n IonButton,\n ],\n})\nexport class CountrySelectorComponent implements OnInit, OnChanges {\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n protected readonly geoRegions: readonly {\n readonly id: string;\n title?: string;\n }[] = [\n { id: 'Europe' },\n { id: 'Asia' },\n { id: 'Americas' },\n { id: 'Africa' },\n ];\n\n @Input({ required: true }) countryID?: string;\n\n @Input() defaultCountryID?: string;\n\n @Input() readonly = false;\n @Input() disabled = false;\n @Input() label = 'search';\n @Input() canBeUnknown = false;\n\n @Output() readonly countryIDChange = new EventEmitter<string>();\n\n protected readonly filter = signal<string>('');\n protected readonly geoRegion = signal<GeoRegion | 'All' | 'Americas'>('All');\n protected readonly allCountries = signal<readonly ICountry[]>([]);\n protected readonly unknownCountry = signal<ICountry | undefined>(undefined);\n\n // True when the country list failed to load — surfaced in the UI with a Retry.\n protected readonly loadFailed = this.countriesLoader.loadFailed;\n\n protected readonly countries = computed<readonly ISelectItem[]>(() => {\n const allCountries = this.allCountries();\n if (allCountries.length === 0) {\n return []; // Data not loaded yet\n }\n\n const filter = this.filter();\n let countriesToShow: readonly ISelectItem[] = filter\n ? allCountries\n : allCountries.filter(\n (c) =>\n this.geoRegion() === 'All' ||\n (this.geoRegion() === 'Americas' &&\n (c.geoRegions.includes('North America') ||\n c.geoRegions.includes('South America'))) ||\n c.geoRegions.includes(this.geoRegion() as GeoRegion),\n );\n if (this.canBeUnknown && this.unknownCountry()) {\n countriesToShow = [...countriesToShow, this.unknownCountry()!];\n }\n return countriesToShow;\n });\n\n ngOnInit(): void {\n this.load();\n }\n\n private load(): void {\n this.countriesLoader.getCountries().then((countries) => {\n this.allCountries.set(countries);\n });\n this.countriesLoader.getUnknownCountry().then((unknown) => {\n this.unknownCountry.set(unknown);\n });\n }\n\n // Retry loading the country list after a failure (loadFailed()).\n protected retry(): void {\n this.countriesLoader.reload().then(() => this.load());\n }\n\n // protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All';\n\n protected onChanged(): void {\n this.countryIDChange.emit(this.countryID);\n }\n\n protected onFilterChanged(filter: string): void {\n this.filter.set(filter);\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['country'] && this.countryID === '--' && !this.canBeUnknown) {\n this.countryID = undefined;\n }\n if (\n changes['defaultCountryID'] &&\n !this.countryID &&\n this.defaultCountryID &&\n this.defaultCountryID !== '--'\n ) {\n this.countryID = this.defaultCountryID;\n this.countryIDChange.emit(this.countryID);\n }\n }\n\n protected onRegionChanged(event: Event): void {\n this.geoRegion.set(\n (event as CustomEvent).detail.value as GeoRegion | 'All' | 'Americas',\n );\n }\n\n protected readonly filterCountryByCode = (\n item: ISelectItem,\n filter: string,\n ) => {\n const f = filter.trim().toUpperCase();\n const c = item as ICountry;\n return c.id === f || c.id3.startsWith(f);\n };\n}\n","@if (loadFailed()) {\n <ion-item color=\"danger\" lines=\"none\">\n <ion-label class=\"ion-text-wrap\">\n Couldn't load the list of countries. Check your connection and try again.\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"retry()\">Retry</ion-button>\n </ion-item>\n}\n\n<sneat-select-from-list\n [value]=\"countryID || ''\"\n [isFilterable]=\"true\"\n [filterLabel]=\"label\"\n [label]=\"label\"\n [items]=\"countries()\"\n [(ngModel)]=\"countryID\"\n (ngModelChange)=\"onChanged()\"\n [isReadonly]=\"readonly\"\n [filterItem]=\"filterCountryByCode\"\n (filterChanged)=\"onFilterChanged($event)\"\n labelPlacement=\"start\"\n>\n @if (!countryID && !filter()) {\n <ion-segment [(ngModel)]=\"geoRegion\" (ionChange)=\"onRegionChanged($event)\">\n <ion-segment-button value=\"All\">All</ion-segment-button>\n @for (region of geoRegions; track region.id) {\n <ion-segment-button [value]=\"region.id\">\n <small>{{ region.id }}</small>\n </ion-segment-button>\n }\n </ion-segment>\n }\n</sneat-select-from-list>\n"]}
@@ -111,7 +111,7 @@ export class UserCountryComponent extends SneatBaseComponent {
111
111
  provide: ClassName,
112
112
  useValue: 'UserCountryComponent',
113
113
  },
114
- ], usesInheritance: true, ngImport: i0, template: "@let userCountryID = $userCountryID();\n@let userHasCountry = $userHasCountry();\n\n@if (doNotHide || ($ipCountry() && !userHasCountry)) {\n <ion-card>\n @if (userHasCountry || !$ipCountry()) {\n <ion-card-content>\n <sneat-country-input\n label=\"Country of residence\"\n [canReset]=\"false\"\n [countryID]=\"userCountryID || ''\"\n (countryIDChange)=\"onCountryOfResidenceChanged($event)\"\n />\n </ion-card-content>\n } @else {\n <ion-card-header>\n <ion-card-title>\n Is {{ $ipCountry()?.emoji }} {{ $ipCountry()?.title }} your country of\n residence?\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-button\n [disabled]=\"$saving()\"\n color=\"primary\"\n class=\"ion-padding-end\"\n (click)=\"setCountry($ipCountryID())\"\n >\n <ion-icon name=\"checkmark-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\"\n ><b\n >Yes, my primary residence is in {{ $ipCountry()?.title }}\n {{ $ipCountry()?.emoji }}</b\n ></ion-label\n >\n </ion-button>\n <ion-button color=\"light\" [disabled]=\"$saving()\">\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\">Choose county</ion-label>\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n}\n", dependencies: [{ kind: "component", type: CountryInputComponent, selector: "sneat-country-input", inputs: ["canReset", "label", "countryID"], outputs: ["countryIDChange"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
114
+ ], usesInheritance: true, ngImport: i0, template: "@let userCountryID = $userCountryID();\n@let userHasCountry = $userHasCountry();\n\n@if (doNotHide || ($ipCountry() && !userHasCountry)) {\n <ion-card>\n @if (userHasCountry || !$ipCountry()) {\n <ion-card-content>\n <sneat-country-input\n label=\"Country of residence\"\n [canReset]=\"false\"\n [countryID]=\"userCountryID || ''\"\n (countryIDChange)=\"onCountryOfResidenceChanged($event)\"\n />\n </ion-card-content>\n } @else {\n <ion-card-header>\n <ion-card-title>\n Is {{ $ipCountry()?.emoji }} {{ $ipCountry()?.title }} your country of\n residence?\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-button\n [disabled]=\"$saving()\"\n color=\"primary\"\n class=\"ion-padding-end\"\n (click)=\"setCountry($ipCountryID())\"\n >\n <ion-icon name=\"checkmark-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\"\n ><b\n >Yes, my primary residence is in {{ $ipCountry()?.title }}\n {{ $ipCountry()?.emoji }}</b\n ></ion-label\n >\n </ion-button>\n <ion-button color=\"light\" [disabled]=\"$saving()\">\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\">Choose county</ion-label>\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n}\n", dependencies: [{ kind: "component", type: CountryInputComponent, selector: "sneat-country-input", inputs: ["mode", "canReset", "label", "countryID"], outputs: ["countryIDChange"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
115
115
  }
116
116
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: UserCountryComponent, decorators: [{
117
117
  type: Component,
@@ -1 +1 @@
1
- {"version":3,"file":"user-country.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/user-country/user-country.component.ts","../../../../../../libs/components/src/lib/user-country/user-country.component.html"],"names":[],"mappings":"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","sourcesContent":["import { HttpClient, HttpHeaders } from '@angular/common/http';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n Input,\n OnDestroy,\n signal,\n inject,\n} from '@angular/core';\nimport {\n IonButton,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonIcon,\n IonLabel,\n} from '@ionic/angular/standalone';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { ClassName, SneatBaseComponent } from '@sneat/ui';\nimport { map, race, takeUntil } from 'rxjs';\nimport { CountryInputComponent } from '../country-input';\nimport { ICountry, CountriesLoaderService } from '../country-selector';\n\nlet ipCountryCached: string | undefined; // TODO: Should have expiration?\n\n@Component({\n imports: [\n CountryInputComponent,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonButton,\n IonIcon,\n IonLabel,\n IonCard,\n ],\n providers: [\n {\n provide: ClassName,\n useValue: 'UserCountryComponent',\n },\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'sneat-user-country',\n templateUrl: './user-country.component.html',\n})\nexport class UserCountryComponent\n extends SneatBaseComponent\n implements OnDestroy\n{\n private readonly httpClient = inject(HttpClient);\n private readonly userService = inject(SneatUserService);\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n protected readonly $ipCountryID = signal('');\n protected readonly $ipCountry = signal<ICountry | undefined>(undefined);\n\n protected readonly $userCountryID = signal<string>('');\n protected readonly $userHasCountry = computed(() => {\n const userCountryID = this.$userCountryID();\n return !!userCountryID && userCountryID !== '--';\n });\n\n protected isCountryDetectionStarted = false;\n protected readonly $detectingCountry = signal(false);\n protected readonly $saving = signal(false);\n\n @Input() public doNotHide = false;\n\n private trackUserRecord(): void {\n this.userService.userState\n .pipe(takeUntil(this.destroyed$))\n .subscribe((user) => {\n if (user.record) {\n this.$userCountryID.set(user?.record?.countryID || '--');\n }\n });\n }\n\n constructor() {\n super();\n this.trackUserRecord();\n // this.getIpCountry();\n effect(() => {\n const userCountryID = this.$userCountryID();\n if (\n userCountryID === '--' &&\n // It looks like using signal here can cause an infinite loop\n !this.isCountryDetectionStarted\n ) {\n this.getIpCountry();\n }\n });\n }\n\n protected onCountryOfResidenceChanged(countryID: string): void {\n this.setCountry(countryID);\n }\n\n private getIpCountry(): void {\n if (this.isCountryDetectionStarted) {\n return;\n }\n this.isCountryDetectionStarted = true;\n if (ipCountryCached) {\n this.$ipCountryID.set(ipCountryCached);\n this.countriesLoader.getCountryByID(ipCountryCached).then((country) => {\n this.$ipCountry.set(country);\n });\n return;\n }\n this.$detectingCountry.set(true);\n // console.log('UserCountryComponent: Detecting IP country...');\n race(\n this.httpClient\n .get<string>('https://ipapi.co/country', {\n headers: new HttpHeaders({\n Accept: 'text/plain',\n }),\n responseType: 'text' as 'json',\n })\n .pipe(\n map((country) => ({ source: 'https://ipapi.co/country', country })),\n ),\n ).subscribe({\n next: (response) => {\n // console.log('UserCountryComponent: Got IP country:', response);\n const ipCountryID = response.country;\n ipCountryCached = ipCountryID;\n this.$ipCountryID.set(ipCountryID);\n this.countriesLoader.getCountryByID(ipCountryID).then((country) => {\n this.$ipCountry.set(country);\n });\n this.$detectingCountry.set(false);\n },\n error: (err) => {\n this.$detectingCountry.set(false);\n // console.error('UserCountryComponent: Failed to get IP country', err);\n this.errorLogger.logError(\n err,\n 'UserCountryComponent: Failed to get IP country',\n {\n show: false,\n },\n );\n },\n });\n }\n\n protected setCountry(countryID?: string): void {\n if (!countryID) {\n return;\n }\n this.$saving.set(true);\n this.userService.setUserCountry(countryID).subscribe({\n next: () => {\n // console.log('UserCountryComponent: User country set to', countryID);\n this.$userCountryID.set(countryID);\n },\n error: (err) => {\n this.errorLogger.logError(\n 'UserCountryComponent: Failed to set user country',\n err,\n );\n this.$saving.set(false);\n },\n });\n }\n}\n","@let userCountryID = $userCountryID();\n@let userHasCountry = $userHasCountry();\n\n@if (doNotHide || ($ipCountry() && !userHasCountry)) {\n <ion-card>\n @if (userHasCountry || !$ipCountry()) {\n <ion-card-content>\n <sneat-country-input\n label=\"Country of residence\"\n [canReset]=\"false\"\n [countryID]=\"userCountryID || ''\"\n (countryIDChange)=\"onCountryOfResidenceChanged($event)\"\n />\n </ion-card-content>\n } @else {\n <ion-card-header>\n <ion-card-title>\n Is {{ $ipCountry()?.emoji }} {{ $ipCountry()?.title }} your country of\n residence?\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-button\n [disabled]=\"$saving()\"\n color=\"primary\"\n class=\"ion-padding-end\"\n (click)=\"setCountry($ipCountryID())\"\n >\n <ion-icon name=\"checkmark-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\"\n ><b\n >Yes, my primary residence is in {{ $ipCountry()?.title }}\n {{ $ipCountry()?.emoji }}</b\n ></ion-label\n >\n </ion-button>\n <ion-button color=\"light\" [disabled]=\"$saving()\">\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\">Choose county</ion-label>\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n}\n"]}
1
+ {"version":3,"file":"user-country.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/user-country/user-country.component.ts","../../../../../../libs/components/src/lib/user-country/user-country.component.html"],"names":[],"mappings":"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","sourcesContent":["import { HttpClient, HttpHeaders } from '@angular/common/http';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n Input,\n OnDestroy,\n signal,\n inject,\n} from '@angular/core';\nimport {\n IonButton,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonIcon,\n IonLabel,\n} from '@ionic/angular/standalone';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { ClassName, SneatBaseComponent } from '@sneat/ui';\nimport { map, race, takeUntil } from 'rxjs';\nimport { CountryInputComponent } from '../country-input';\nimport { ICountry, CountriesLoaderService } from '../country-selector';\n\nlet ipCountryCached: string | undefined; // TODO: Should have expiration?\n\n@Component({\n imports: [\n CountryInputComponent,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonButton,\n IonIcon,\n IonLabel,\n IonCard,\n ],\n providers: [\n {\n provide: ClassName,\n useValue: 'UserCountryComponent',\n },\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'sneat-user-country',\n templateUrl: './user-country.component.html',\n})\nexport class UserCountryComponent\n extends SneatBaseComponent\n implements OnDestroy\n{\n private readonly httpClient = inject(HttpClient);\n private readonly userService = inject(SneatUserService);\n private readonly countriesLoader = inject(CountriesLoaderService);\n\n protected readonly $ipCountryID = signal('');\n protected readonly $ipCountry = signal<ICountry | undefined>(undefined);\n\n protected readonly $userCountryID = signal<string>('');\n protected readonly $userHasCountry = computed(() => {\n const userCountryID = this.$userCountryID();\n return !!userCountryID && userCountryID !== '--';\n });\n\n protected isCountryDetectionStarted = false;\n protected readonly $detectingCountry = signal(false);\n protected readonly $saving = signal(false);\n\n @Input() public doNotHide = false;\n\n private trackUserRecord(): void {\n this.userService.userState\n .pipe(takeUntil(this.destroyed$))\n .subscribe((user) => {\n if (user.record) {\n this.$userCountryID.set(user?.record?.countryID || '--');\n }\n });\n }\n\n constructor() {\n super();\n this.trackUserRecord();\n // this.getIpCountry();\n effect(() => {\n const userCountryID = this.$userCountryID();\n if (\n userCountryID === '--' &&\n // It looks like using signal here can cause an infinite loop\n !this.isCountryDetectionStarted\n ) {\n this.getIpCountry();\n }\n });\n }\n\n protected onCountryOfResidenceChanged(countryID: string): void {\n this.setCountry(countryID);\n }\n\n private getIpCountry(): void {\n if (this.isCountryDetectionStarted) {\n return;\n }\n this.isCountryDetectionStarted = true;\n if (ipCountryCached) {\n this.$ipCountryID.set(ipCountryCached);\n this.countriesLoader.getCountryByID(ipCountryCached).then((country) => {\n this.$ipCountry.set(country);\n });\n return;\n }\n this.$detectingCountry.set(true);\n // console.log('UserCountryComponent: Detecting IP country...');\n race(\n this.httpClient\n .get<string>('https://ipapi.co/country', {\n headers: new HttpHeaders({\n Accept: 'text/plain',\n }),\n responseType: 'text' as 'json',\n })\n .pipe(\n map((country) => ({ source: 'https://ipapi.co/country', country })),\n ),\n ).subscribe({\n next: (response) => {\n // console.log('UserCountryComponent: Got IP country:', response);\n const ipCountryID = response.country;\n ipCountryCached = ipCountryID;\n this.$ipCountryID.set(ipCountryID);\n this.countriesLoader.getCountryByID(ipCountryID).then((country) => {\n this.$ipCountry.set(country);\n });\n this.$detectingCountry.set(false);\n },\n error: (err) => {\n this.$detectingCountry.set(false);\n // console.error('UserCountryComponent: Failed to get IP country', err);\n this.errorLogger.logError(\n err,\n 'UserCountryComponent: Failed to get IP country',\n {\n show: false,\n },\n );\n },\n });\n }\n\n protected setCountry(countryID?: string): void {\n if (!countryID) {\n return;\n }\n this.$saving.set(true);\n this.userService.setUserCountry(countryID).subscribe({\n next: () => {\n // console.log('UserCountryComponent: User country set to', countryID);\n this.$userCountryID.set(countryID);\n },\n error: (err) => {\n this.errorLogger.logError(\n 'UserCountryComponent: Failed to set user country',\n err,\n );\n this.$saving.set(false);\n },\n });\n }\n}\n","@let userCountryID = $userCountryID();\n@let userHasCountry = $userHasCountry();\n\n@if (doNotHide || ($ipCountry() && !userHasCountry)) {\n <ion-card>\n @if (userHasCountry || !$ipCountry()) {\n <ion-card-content>\n <sneat-country-input\n label=\"Country of residence\"\n [canReset]=\"false\"\n [countryID]=\"userCountryID || ''\"\n (countryIDChange)=\"onCountryOfResidenceChanged($event)\"\n />\n </ion-card-content>\n } @else {\n <ion-card-header>\n <ion-card-title>\n Is {{ $ipCountry()?.emoji }} {{ $ipCountry()?.title }} your country of\n residence?\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-button\n [disabled]=\"$saving()\"\n color=\"primary\"\n class=\"ion-padding-end\"\n (click)=\"setCountry($ipCountryID())\"\n >\n <ion-icon name=\"checkmark-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\"\n ><b\n >Yes, my primary residence is in {{ $ipCountry()?.title }}\n {{ $ipCountry()?.emoji }}</b\n ></ion-label\n >\n </ion-button>\n <ion-button color=\"light\" [disabled]=\"$saving()\">\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label style=\"text-transform: none\">Choose county</ion-label>\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n}\n"]}
@@ -1,16 +1,24 @@
1
1
  import { EventEmitter, OnInit } from '@angular/core';
2
+ import { ISelectItem } from '@sneat/ui';
2
3
  import { ICountry } from '../country-selector';
3
4
  import * as i0 from "@angular/core";
5
+ export type CountryInputMode = 'list' | 'dropdown';
4
6
  export declare class CountryInputComponent implements OnInit {
5
7
  private readonly countriesLoader;
8
+ mode: CountryInputMode;
6
9
  canReset: boolean;
7
10
  label: string;
8
11
  countryID: string;
9
12
  countryIDChange: EventEmitter<string>;
10
13
  readonly countries: import("@angular/core").WritableSignal<readonly ICountry[]>;
14
+ protected readonly loadFailed: import("@angular/core").WritableSignal<boolean>;
11
15
  ngOnInit(): void;
16
+ private load;
17
+ protected retry(): void;
12
18
  onCountryChanged(): void;
19
+ protected onListValueChanged(countryID: string): void;
13
20
  reset(event: Event): void;
21
+ protected readonly filterCountryByCode: (item: ISelectItem, filter: string) => boolean;
14
22
  static ɵfac: i0.ɵɵFactoryDeclaration<CountryInputComponent, never>;
15
- static ɵcmp: i0.ɵɵComponentDeclaration<CountryInputComponent, "sneat-country-input", never, { "canReset": { "alias": "canReset"; "required": false; }; "label": { "alias": "label"; "required": false; }; "countryID": { "alias": "countryID"; "required": false; }; }, { "countryIDChange": "countryIDChange"; }, never, never, true, never>;
23
+ static ɵcmp: i0.ɵɵComponentDeclaration<CountryInputComponent, "sneat-country-input", never, { "mode": { "alias": "mode"; "required": false; }; "canReset": { "alias": "canReset"; "required": false; }; "label": { "alias": "label"; "required": false; }; "countryID": { "alias": "countryID"; "required": false; }; }, { "countryIDChange": "countryIDChange"; }, never, never, true, never>;
16
24
  }
@@ -8,11 +8,22 @@ export declare class CountriesLoaderService {
8
8
  private readonly http;
9
9
  private countriesData?;
10
10
  private loadPromise?;
11
+ /**
12
+ * True when the last load attempt failed (countries.json couldn't be
13
+ * fetched/parsed). Consumers can read this to show an error in the UI. Reset
14
+ * to false on a successful (re)load. See `reload()` to retry.
15
+ */
16
+ readonly loadFailed: import("@angular/core").WritableSignal<boolean>;
11
17
  /**
12
18
  * Loads country data from JSON file.
13
19
  * Subsequent calls return the same promise (singleton pattern).
14
20
  */
15
21
  private loadCountries;
22
+ /**
23
+ * Discards the cached data and re-attempts the load — used to retry after a
24
+ * failure (`loadFailed()` is true). Resolves once the reload settles.
25
+ */
26
+ reload(): Promise<void>;
16
27
  /**
17
28
  * Gets all countries as an array.
18
29
  * Automatically loads country data if not yet loaded.
@@ -19,8 +19,11 @@ export declare class CountrySelectorComponent implements OnInit, OnChanges {
19
19
  protected readonly geoRegion: import("@angular/core").WritableSignal<GeoRegion | "All">;
20
20
  protected readonly allCountries: import("@angular/core").WritableSignal<readonly ICountry[]>;
21
21
  protected readonly unknownCountry: import("@angular/core").WritableSignal<ICountry | undefined>;
22
+ protected readonly loadFailed: import("@angular/core").WritableSignal<boolean>;
22
23
  protected readonly countries: import("@angular/core").Signal<readonly ISelectItem[]>;
23
24
  ngOnInit(): void;
25
+ private load;
26
+ protected retry(): void;
24
27
  protected onChanged(): void;
25
28
  protected onFilterChanged(filter: string): void;
26
29
  ngOnChanges(changes: SimpleChanges): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/components",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },