@sneat/components 0.26.0 → 0.26.2

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.
Files changed (41) hide show
  1. package/esm2022/lib/app-version/app-version.component.js +6 -6
  2. package/esm2022/lib/app-version/app-version.component.js.map +1 -1
  3. package/esm2022/lib/card-list/sneat-card-list.component.js +15 -16
  4. package/esm2022/lib/card-list/sneat-card-list.component.js.map +1 -1
  5. package/esm2022/lib/copyright/copyright.component.js +5 -5
  6. package/esm2022/lib/copyright/copyright.component.js.map +1 -1
  7. package/esm2022/lib/country-input/country-input.component.js +18 -18
  8. package/esm2022/lib/country-input/country-input.component.js.map +1 -1
  9. package/esm2022/lib/country-selector/countries-loader.service.js +5 -4
  10. package/esm2022/lib/country-selector/countries-loader.service.js.map +1 -1
  11. package/esm2022/lib/country-selector/country-selector.component.js +34 -32
  12. package/esm2022/lib/country-selector/country-selector.component.js.map +1 -1
  13. package/esm2022/lib/country-selector/country-selector.service.js +3 -3
  14. package/esm2022/lib/date-input/date-input.component.js +24 -15
  15. package/esm2022/lib/date-input/date-input.component.js.map +1 -1
  16. package/esm2022/lib/date-input/date-modal.component.js +14 -13
  17. package/esm2022/lib/date-input/date-modal.component.js.map +1 -1
  18. package/esm2022/lib/dialog-header/dialog-header.component.js +9 -10
  19. package/esm2022/lib/dialog-header/dialog-header.component.js.map +1 -1
  20. package/esm2022/lib/error-card/sneat-error-card.component.js +6 -6
  21. package/esm2022/lib/error-card/sneat-error-card.component.js.map +1 -1
  22. package/esm2022/lib/filter-item/filter-item.component.js +6 -5
  23. package/esm2022/lib/filter-item/filter-item.component.js.map +1 -1
  24. package/esm2022/lib/pipes/country-emoji.pipe.js +6 -6
  25. package/esm2022/lib/pipes/decimal64p2.pipe.js +6 -6
  26. package/esm2022/lib/pipes/gender.pipes.js +9 -9
  27. package/esm2022/lib/pipes/long-month-name.pipe.js +3 -3
  28. package/esm2022/lib/pipes/short-month-name.pipe.js +3 -3
  29. package/esm2022/lib/pipes/team-emoji.pipe.js +3 -3
  30. package/esm2022/lib/user-country/user-country.component.js +22 -17
  31. package/esm2022/lib/user-country/user-country.component.js.map +1 -1
  32. package/lib/card-list/sneat-card-list.component.d.ts +5 -5
  33. package/lib/country-input/country-input.component.d.ts +4 -4
  34. package/lib/country-selector/country-selector.component.d.ts +6 -6
  35. package/lib/date-input/date-input.component.d.ts +1 -1
  36. package/lib/date-input/date-modal.component.d.ts +3 -3
  37. package/lib/dialog-header/dialog-header.component.d.ts +2 -2
  38. package/lib/user-country/user-country.component.d.ts +2 -2
  39. package/package.json +6 -5
  40. package/src/sneat-styles.scss +10 -10
  41. package/tsconfig.lib.prod.tsbuildinfo +1 -1
@@ -1,16 +1,16 @@
1
- import { Component } from '@angular/core';
2
- import { IonInput, IonItem, IonItemDivider, IonLabel, } from '@ionic/angular/standalone';
1
+ import { Component, ChangeDetectionStrategy } from '@angular/core';
2
+ import { IonInput, IonItem, IonItemDivider, IonLabel, } from '@ionic/angular';
3
3
  import { buildInfo } from './build-info';
4
4
  import * as i0 from "@angular/core";
5
5
  export class AppVersionComponent {
6
6
  constructor() {
7
7
  this.buildInfo = buildInfo;
8
8
  }
9
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AppVersionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: AppVersionComponent, isStandalone: true, selector: "sneat-app-version", ngImport: i0, template: "<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n", dependencies: [{ kind: "component", type: IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { 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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }] }); }
9
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AppVersionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.3", type: AppVersionComponent, isStandalone: true, selector: "sneat-app-version", ngImport: i0, template: "<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n", dependencies: [{ kind: "component", type: IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { 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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11
11
  }
12
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AppVersionComponent, decorators: [{
12
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AppVersionComponent, decorators: [{
13
13
  type: Component,
14
- args: [{ selector: 'sneat-app-version', imports: [IonItemDivider, IonLabel, IonItem, IonInput], template: "<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n" }]
14
+ args: [{ selector: 'sneat-app-version', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IonItemDivider, IonLabel, IonItem, IonInput], template: "<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n" }]
15
15
  }] });
16
16
  //# sourceMappingURL=app-version.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-version.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/app-version/app-version.component.ts","../../../../../../libs/components/src/lib/app-version/app-version.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EACL,QAAQ,EACR,OAAO,EACP,cAAc,EACd,QAAQ,GACT,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;;AAOzC,MAAM,OAAO,mBAAmB;IALhC;QAMqB,cAAS,GAAG,SAAS,CAAC;KAC1C;8GAFY,mBAAmB;kGAAnB,mBAAmB,6ECdhC,+WAeA,4CDHY,cAAc,kGAAE,QAAQ,6FAAE,OAAO,0NAAE,QAAQ;;2FAE1C,mBAAmB;kBAL/B,SAAS;+BACE,mBAAmB,WAEpB,CAAC,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC","sourcesContent":["import { Component } from '@angular/core';\nimport {\n IonInput,\n IonItem,\n IonItemDivider,\n IonLabel,\n} from '@ionic/angular/standalone';\nimport { buildInfo } from './build-info';\n\n@Component({\n selector: 'sneat-app-version',\n templateUrl: 'app-version.component.html',\n imports: [IonItemDivider, IonLabel, IonItem, IonInput],\n})\nexport class AppVersionComponent {\n protected readonly buildInfo = buildInfo;\n}\n","<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n"]}
1
+ {"version":3,"file":"app-version.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/app-version/app-version.component.ts","../../../../../../libs/components/src/lib/app-version/app-version.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EACL,QAAQ,EACR,OAAO,EACP,cAAc,EACd,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;;AAQzC,MAAM,OAAO,mBAAmB;IANhC;QAOqB,cAAS,GAAG,SAAS,CAAC;KAC1C;8GAFY,mBAAmB;kGAAnB,mBAAmB,6ECfhC,+WAeA,4CDFY,cAAc,kGAAE,QAAQ,6FAAE,OAAO,0NAAE,QAAQ;;2FAE1C,mBAAmB;kBAN/B,SAAS;+BACE,mBAAmB,mBAEZ,uBAAuB,CAAC,MAAM,WACtC,CAAC,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC","sourcesContent":["import { Component, ChangeDetectionStrategy } from '@angular/core';\nimport {\n IonInput,\n IonItem,\n IonItemDivider,\n IonLabel,\n} from '@ionic/angular';\nimport { buildInfo } from './build-info';\n\n@Component({\n selector: 'sneat-app-version',\n templateUrl: 'app-version.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [IonItemDivider, IonLabel, IonItem, IonInput],\n})\nexport class AppVersionComponent {\n protected readonly buildInfo = buildInfo;\n}\n","<ion-item-divider color=\"light\">\n <ion-label>App version</ion-label>\n</ion-item-divider>\n<ion-item>\n <ion-input\n label=\"Build:\"\n color=\"medium\"\n readonly=\"true\"\n disabled=\"true\"\n [value]=\"\n buildInfo.gitHash.substring(0, 7) + ' @ ' + buildInfo.buildTimestamp\n \"\n [title]=\"buildInfo.gitHash\"\n />\n</ion-item>\n"]}
@@ -1,7 +1,7 @@
1
- import { Component, EventEmitter, Input, Output, ViewChild, inject, } from '@angular/core';
1
+ import { Component, EventEmitter, Input, Output, ViewChild, inject, ChangeDetectionStrategy, input } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
3
  import { RouterModule } from '@angular/router';
4
- import { IonButton, IonButtons, IonCard, IonCardContent, IonIcon, IonInput, IonItem, IonLabel, IonList, IonSegment, IonSegmentButton, IonSkeletonText, } from '@ionic/angular/standalone';
4
+ import { IonButton, IonButtons, IonCard, IonCardContent, IonIcon, IonInput, IonItem, IonLabel, IonList, IonSegment, IonSegmentButton, IonSkeletonText, } from '@ionic/angular';
5
5
  import { ErrorLogger } from '@sneat/core';
6
6
  import * as i0 from "@angular/core";
7
7
  import * as i1 from "@angular/router";
@@ -9,7 +9,12 @@ import * as i2 from "@angular/forms";
9
9
  export class SneatCardListComponent {
10
10
  constructor() {
11
11
  this.errorLogger = inject(ErrorLogger);
12
- this.getRouterLink = () => undefined;
12
+ this.isFilterable = input(/* @ts-ignore */
13
+ ...(ngDevMode ? [undefined, { debugName: "isFilterable" }] : /* istanbul ignore next */ []));
14
+ this.isLoading = input(/* @ts-ignore */
15
+ ...(ngDevMode ? [undefined, { debugName: "isLoading" }] : /* istanbul ignore next */ []));
16
+ this.getRouterLink = input(() => undefined, /* @ts-ignore */
17
+ ...(ngDevMode ? [{ debugName: "getRouterLink" }] : /* istanbul ignore next */ []));
13
18
  this.cardTitleClick = new EventEmitter();
14
19
  this.itemClick = new EventEmitter();
15
20
  this.tabChanged = new EventEmitter();
@@ -52,12 +57,12 @@ export class SneatCardListComponent {
52
57
  });
53
58
  }
54
59
  }
55
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SneatCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
56
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SneatCardListComponent, isStandalone: true, selector: "sneat-card-list", inputs: { title: "title", isFilterable: "isFilterable", isLoading: "isLoading", items: "items", create: "create", itemIcon: "itemIcon", tab: "tab", tabs: "tabs", noItemsText: "noItemsText", getRouterLink: "getRouterLink" }, outputs: { cardTitleClick: "cardTitleClick", itemClick: "itemClick", tabChanged: "tabChanged" }, viewQueries: [{ propertyName: "addInput", first: true, predicate: IonInput, descendants: true }], ngImport: i0, template: "<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: 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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "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: 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }] }); }
60
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SneatCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
61
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SneatCardListComponent, isStandalone: true, selector: "sneat-card-list", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, isFilterable: { classPropertyName: "isFilterable", publicName: "isFilterable", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: false, isRequired: false, transformFunction: null }, create: { classPropertyName: "create", publicName: "create", isSignal: false, isRequired: false, transformFunction: null }, itemIcon: { classPropertyName: "itemIcon", publicName: "itemIcon", isSignal: false, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: false, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: false, isRequired: false, transformFunction: null }, noItemsText: { classPropertyName: "noItemsText", publicName: "noItemsText", isSignal: false, isRequired: false, transformFunction: null }, getRouterLink: { classPropertyName: "getRouterLink", publicName: "getRouterLink", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardTitleClick: "cardTitleClick", itemClick: "itemClick", tabChanged: "tabChanged" }, viewQueries: [{ propertyName: "addInput", first: true, predicate: IonInput, descendants: true }], ngImport: i0, template: "<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading() && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable()) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading()) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink()(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { 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: 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: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "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"], outputs: ["ionFocus", "ionBlur"] }, { 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
57
62
  }
58
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SneatCardListComponent, decorators: [{
63
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SneatCardListComponent, decorators: [{
59
64
  type: Component,
60
- args: [{ selector: 'sneat-card-list', imports: [
65
+ args: [{ selector: 'sneat-card-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
61
66
  RouterModule,
62
67
  IonCard,
63
68
  IonItem,
@@ -72,14 +77,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
72
77
  IonCardContent,
73
78
  IonSkeletonText,
74
79
  IonList,
75
- ], template: "<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n" }]
80
+ ], template: "<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading() && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable()) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading()) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink()(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n" }]
76
81
  }], propDecorators: { title: [{
77
82
  type: Input
78
- }], isFilterable: [{
79
- type: Input
80
- }], isLoading: [{
81
- type: Input
82
- }], items: [{
83
+ }], isFilterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isFilterable", required: false }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], items: [{
83
84
  type: Input
84
85
  }], create: [{
85
86
  type: Input
@@ -91,9 +92,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
91
92
  type: Input
92
93
  }], noItemsText: [{
93
94
  type: Input
94
- }], getRouterLink: [{
95
- type: Input
96
- }], cardTitleClick: [{
95
+ }], getRouterLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "getRouterLink", required: false }] }], cardTitleClick: [{
97
96
  type: Output
98
97
  }], itemClick: [{
99
98
  type: Output
@@ -1 +1 @@
1
- {"version":3,"file":"sneat-card-list.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/card-list/sneat-card-list.component.ts","../../../../../../libs/components/src/lib/card-list/sneat-card-list.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n inject,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { RouterModule } from '@angular/router';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonIcon,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonSegment,\n IonSegmentButton,\n IonSkeletonText,\n} from '@ionic/angular/standalone';\nimport { IRecord } from '@sneat/data';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { Observable } from 'rxjs';\n\nexport interface ICardTab {\n id: string;\n title: string;\n}\n\ninterface IOptionallyTitled {\n id?: string;\n title?: string;\n}\n\n@Component({\n selector: 'sneat-card-list',\n templateUrl: './sneat-card-list.component.html',\n imports: [\n RouterModule,\n IonCard,\n IonItem,\n IonIcon,\n IonLabel,\n IonInput,\n IonButtons,\n IonButton,\n IonSegment,\n IonSegmentButton,\n FormsModule,\n IonCardContent,\n IonSkeletonText,\n IonList,\n ],\n})\nexport class SneatCardListComponent {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n\n @Input() title?: string;\n @Input() isFilterable?: boolean;\n @Input() isLoading?: boolean;\n @Input() items?: { id?: unknown; title?: string }[];\n @Input() create?: (name: string) => Observable<IRecord<IOptionallyTitled>>;\n @Input() itemIcon?: string;\n @Input() tab?: string;\n @Input() tabs?: ICardTab[];\n @Input() noItemsText?: string;\n @Input() getRouterLink: (item: unknown) => string = () =>\n undefined as unknown as string;\n\n @Output() readonly cardTitleClick = new EventEmitter<void>();\n @Output() readonly itemClick = new EventEmitter<unknown>();\n @Output() readonly tabChanged = new EventEmitter<string>();\n\n @ViewChild(IonInput, { static: false }) addInput?: IonInput;\n\n filter = '';\n\n protected mode: 'list' | 'add' = 'list';\n protected name = '';\n protected isAdding?: boolean;\n\n protected click(event: Event, item: unknown): void {\n event.preventDefault();\n event.stopPropagation();\n this.itemClick.emit(item);\n }\n\n protected showAddForm(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.mode = 'add';\n setTimeout(() => {\n // console.log(this.addInput);\n if (this.addInput) {\n this.addInput\n ?.setFocus()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to set focus'),\n );\n }\n }, 200);\n }\n\n protected tryCreate(): void {\n this.isAdding = true;\n if (this.create) {\n this.create(this.name.trim()).subscribe({\n next: (item) => {\n this.items?.push(item);\n this.isAdding = false;\n this.mode = 'list';\n this.name = '';\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new item');\n this.isAdding = false;\n },\n });\n }\n }\n}\n","<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n"]}
1
+ {"version":3,"file":"sneat-card-list.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/card-list/sneat-card-list.component.ts","../../../../../../libs/components/src/lib/card-list/sneat-card-list.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n inject,\n ChangeDetectionStrategy,\n input\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { RouterModule } from '@angular/router';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonIcon,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonSegment,\n IonSegmentButton,\n IonSkeletonText,\n} from '@ionic/angular';\nimport { IRecord } from '@sneat/data';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { Observable } from 'rxjs';\n\nexport interface ICardTab {\n id: string;\n title: string;\n}\n\ninterface IOptionallyTitled {\n id?: string;\n title?: string;\n}\n\n@Component({\n selector: 'sneat-card-list',\n templateUrl: './sneat-card-list.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n RouterModule,\n IonCard,\n IonItem,\n IonIcon,\n IonLabel,\n IonInput,\n IonButtons,\n IonButton,\n IonSegment,\n IonSegmentButton,\n FormsModule,\n IonCardContent,\n IonSkeletonText,\n IonList,\n ],\n})\nexport class SneatCardListComponent {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n\n // TODO: Skipped for migration because:\n // This input is used in a control flow expression (e.g. `@if` or `*ngIf`)\n // and migrating would break narrowing currently.\n @Input() title?: string;\n readonly isFilterable = input<boolean>();\n readonly isLoading = input<boolean>();\n // TODO: Skipped for migration because:\n // Your application code writes to the input. This prevents migration.\n @Input() items?: { id?: unknown; title?: string }[];\n // TODO: Skipped for migration because:\n // Your application code writes to the input. This prevents migration.\n @Input() create?: (name: string) => Observable<IRecord<IOptionallyTitled>>;\n // TODO: Skipped for migration because:\n // This input is used in a control flow expression (e.g. `@if` or `*ngIf`)\n // and migrating would break narrowing currently.\n @Input() itemIcon?: string;\n // TODO: Skipped for migration because:\n // Your application code writes to the input. This prevents migration.\n @Input() tab?: string;\n // TODO: Skipped for migration because:\n // This input is used in a control flow expression (e.g. `@if` or `*ngIf`)\n // and migrating would break narrowing currently.\n @Input() tabs?: ICardTab[];\n // TODO: Skipped for migration because:\n // This input is used in a control flow expression (e.g. `@if` or `*ngIf`)\n // and migrating would break narrowing currently.\n @Input() noItemsText?: string;\n readonly getRouterLink = input<(item: unknown) => string>(() => undefined as unknown as string);\n\n @Output() readonly cardTitleClick = new EventEmitter<void>();\n @Output() readonly itemClick = new EventEmitter<unknown>();\n @Output() readonly tabChanged = new EventEmitter<string>();\n\n @ViewChild(IonInput, { static: false }) addInput?: IonInput;\n\n filter = '';\n\n protected mode: 'list' | 'add' = 'list';\n protected name = '';\n protected isAdding?: boolean;\n\n protected click(event: Event, item: unknown): void {\n event.preventDefault();\n event.stopPropagation();\n this.itemClick.emit(item);\n }\n\n protected showAddForm(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.mode = 'add';\n setTimeout(() => {\n // console.log(this.addInput);\n if (this.addInput) {\n this.addInput\n ?.setFocus()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to set focus'),\n );\n }\n }, 200);\n }\n\n protected tryCreate(): void {\n this.isAdding = true;\n if (this.create) {\n this.create(this.name.trim()).subscribe({\n next: (item) => {\n this.items?.push(item);\n this.isAdding = false;\n this.mode = 'list';\n this.name = '';\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new item');\n this.isAdding = false;\n },\n });\n }\n }\n}\n","<ion-card>\n @if (title) {\n <ion-item (click)=\"cardTitleClick.emit()\" tappable>\n @if (itemIcon && !isLoading() && !items?.length) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label style=\"font-weight: bold\">{{ title }}</ion-label>\n @if (isFilterable()) {\n <ion-input [(ngModel)]=\"filter\" placeholder=\"(type to filter)\" />\n }\n <ion-buttons slot=\"end\">\n @if (filter) {\n <ion-button (click)=\"filter = ''\" title=\"Clear filter\">\n <ion-icon name=\"close-outline\" color=\"medium\" />\n </ion-button>\n }\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (tabs) {\n <ion-segment [(ngModel)]=\"tab\" (ionChange)=\"tabChanged.emit(tab || '')\">\n @for (tab of tabs; track tab.id) {\n <ion-segment-button [value]=\"tab.id\">\n <ion-label>{{ tab.title }}</ion-label>\n </ion-segment-button>\n }\n </ion-segment>\n <ion-buttons>\n @if (mode !== \"add\" && create) {\n <ion-button (click)=\"showAddForm($event)\">\n <ion-icon color=\"primary\" slot=\"start\" name=\"add\" />\n <ion-label color=\"medium\">Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n }\n @if (isLoading()) {\n <ion-card-content>\n <ion-skeleton-text animated=\"\" style=\"width: 100%\" />\n </ion-card-content>\n } @else {\n <ion-list>\n @for (item of items; track item.id) {\n <ion-item\n tappable\n (click)=\"itemClick && click($event, item)\"\n [routerLink]=\"getRouterLink()(item)\"\n >\n @if (itemIcon) {\n <ion-icon [name]=\"itemIcon\" slot=\"start\" />\n }\n <ion-label>{{ item.title || item.id }}</ion-label>\n </ion-item>\n }\n @if (mode === \"add\") {\n <ion-item>\n <ion-icon name=\"earth-outline\" slot=\"start\" color=\"medium\" />\n <ion-input\n [(ngModel)]=\"name\"\n placeholder=\"Name\"\n [disabled]=\"!!isAdding\"\n />\n <ion-buttons slot=\"end\">\n <ion-button\n fill=\"solid\"\n color=\"primary\"\n [disabled]=\"isAdding || !name.trim()\"\n (click)=\"tryCreate()\"\n >\n Create\n </ion-button>\n <ion-button\n (click)=\"mode = 'list'\"\n color=\"medium\"\n title=\"Cancel\"\n [disabled]=\"isAdding\"\n >\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n }\n </ion-list>\n @if (!items?.length && mode !== \"add\") {\n <ion-card-content>\n @if (!noItemsText) {\n <p>\n {{ noItemsText }}\n </p>\n } @else {\n <p>\n Not defined for this project yet. Click the\n <span style=\"background-color: whitesmoke; padding: 4px 8px\"\n >+ Add</span\n >\n button above in the header of this card to create a first one.\n </p>\n }\n </ion-card-content>\n }\n }\n</ion-card>\n"]}
@@ -1,11 +1,11 @@
1
- import { Component } from '@angular/core';
1
+ import { Component, ChangeDetectionStrategy } from '@angular/core';
2
2
  import * as i0 from "@angular/core";
3
3
  export class CopyrightComponent {
4
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CopyrightComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: CopyrightComponent, isStandalone: true, selector: "sneat-copyright", ngImport: i0, template: "<p>2020 &copy; Sneat.team</p>\n" }); }
4
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CopyrightComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.3", type: CopyrightComponent, isStandalone: true, selector: "sneat-copyright", ngImport: i0, template: "<p>2020 &copy; Sneat.team</p>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6
6
  }
7
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CopyrightComponent, decorators: [{
7
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CopyrightComponent, decorators: [{
8
8
  type: Component,
9
- args: [{ selector: 'sneat-copyright', template: "<p>2020 &copy; Sneat.team</p>\n" }]
9
+ args: [{ selector: 'sneat-copyright', changeDetection: ChangeDetectionStrategy.OnPush, template: "<p>2020 &copy; Sneat.team</p>\n" }]
10
10
  }] });
11
11
  //# sourceMappingURL=copyright.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copyright.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/copyright/copyright.component.ts","../../../../../../libs/components/src/lib/copyright/copyright.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;;AAM1C,MAAM,OAAO,kBAAkB;8GAAlB,kBAAkB;kGAAlB,kBAAkB,2ECN/B,iCACA;;2FDKa,kBAAkB;kBAJ9B,SAAS;+BACE,iBAAiB","sourcesContent":["import { Component } from '@angular/core';\n\n@Component({\n selector: 'sneat-copyright',\n templateUrl: './copyright.component.html',\n})\nexport class CopyrightComponent {}\n","<p>2020 &copy; Sneat.team</p>\n"]}
1
+ {"version":3,"file":"copyright.component.js","sourceRoot":"","sources":["../../../../../../libs/components/src/lib/copyright/copyright.component.ts","../../../../../../libs/components/src/lib/copyright/copyright.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;;AAOnE,MAAM,OAAO,kBAAkB;8GAAlB,kBAAkB;kGAAlB,kBAAkB,2ECP/B,iCACA;;2FDMa,kBAAkB;kBAL9B,SAAS;+BACE,iBAAiB,mBACV,uBAAuB,CAAC,MAAM","sourcesContent":["import { Component, ChangeDetectionStrategy } from '@angular/core';\n\n@Component({\n selector: 'sneat-copyright',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './copyright.component.html',\n})\nexport class CopyrightComponent {}\n","<p>2020 &copy; Sneat.team</p>\n"]}
@@ -1,6 +1,6 @@
1
- import { Component, EventEmitter, inject, Input, Output, signal, } from '@angular/core';
1
+ import { Component, EventEmitter, inject, Input, Output, signal, ChangeDetectionStrategy, input } from '@angular/core';
2
2
  import { FormsModule } from '@angular/forms';
3
- import { IonButton, IonButtons, IonIcon, IonItem, IonLabel, IonSelect, IonSelectOption, } from '@ionic/angular/standalone';
3
+ import { IonButton, IonButtons, IonIcon, IonItem, IonLabel, IonSelect, IonSelectOption, } from '@ionic/angular';
4
4
  import { SelectFromListComponent } from '@sneat/ui';
5
5
  import { CountriesLoaderService } from '../country-selector';
6
6
  import * as i0 from "@angular/core";
@@ -8,12 +8,18 @@ import * as i1 from "@angular/forms";
8
8
  export class CountryInputComponent {
9
9
  constructor() {
10
10
  this.countriesLoader = inject(CountriesLoaderService);
11
- this.mode = 'list';
12
- this.canReset = true;
13
- this.label = 'Country';
11
+ this.mode = input('list', /* @ts-ignore */
12
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
13
+ this.canReset = input(true, /* @ts-ignore */
14
+ ...(ngDevMode ? [{ debugName: "canReset" }] : /* istanbul ignore next */ []));
15
+ this.label = input('Country', /* @ts-ignore */
16
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
17
+ // TODO: Skipped for migration because:
18
+ // Your application code writes to the input. This prevents migration.
14
19
  this.countryID = '';
15
20
  this.countryIDChange = new EventEmitter();
16
- this.countries = signal([], ...(ngDevMode ? [{ debugName: "countries" }] : []));
21
+ this.countries = signal([], /* @ts-ignore */
22
+ ...(ngDevMode ? [{ debugName: "countries" }] : /* istanbul ignore next */ []));
17
23
  // True when the country list failed to load — surfaced in the UI with a Retry.
18
24
  this.loadFailed = this.countriesLoader.loadFailed;
19
25
  // Match on ISO code as well as title (select-from-list filters title by default).
@@ -49,12 +55,12 @@ export class CountryInputComponent {
49
55
  this.countryID = '';
50
56
  this.countryIDChange.emit('');
51
57
  }
52
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountryInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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"] }] }); }
58
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CountryInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
59
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: CountryInputComponent, isStandalone: true, selector: "sneat-country-input", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, canReset: { classPropertyName: "canReset", publicName: "canReset", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, countryID: { classPropertyName: "countryID", publicName: "countryID", isSignal: false, isRequired: false, transformFunction: null } }, 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: ["description", "disabled", "justify", "labelPlacement", "mode", "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"], outputs: ["ionFocus", "ionBlur"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
54
60
  }
55
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountryInputComponent, decorators: [{
61
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CountryInputComponent, decorators: [{
56
62
  type: Component,
57
- args: [{ selector: 'sneat-country-input', imports: [
63
+ args: [{ selector: 'sneat-country-input', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
58
64
  FormsModule,
59
65
  SelectFromListComponent,
60
66
  IonItem,
@@ -64,14 +70,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
64
70
  IonButtons,
65
71
  IonButton,
66
72
  IonIcon,
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: [{
71
- type: Input
72
- }], label: [{
73
- type: Input
74
- }], countryID: [{
73
+ ], 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" }]
74
+ }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], canReset: [{ type: i0.Input, args: [{ isSignal: true, alias: "canReset", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], countryID: [{
75
75
  type: Input
76
76
  }], countryIDChange: [{
77
77
  type: Output
@@ -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":"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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
+ {"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 ChangeDetectionStrategy,\n input\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';\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 changeDetection: ChangeDetectionStrategy.OnPush,\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 readonly mode = input<CountryInputMode>('list');\n readonly canReset = input(true);\n readonly label = input('Country');\n // TODO: Skipped for migration because:\n // Your application code writes to the input. This prevents migration.\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"]}
@@ -14,7 +14,8 @@ export class CountriesLoaderService {
14
14
  * fetched/parsed). Consumers can read this to show an error in the UI. Reset
15
15
  * to false on a successful (re)load. See `reload()` to retry.
16
16
  */
17
- this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
17
+ this.loadFailed = signal(false, /* @ts-ignore */
18
+ ...(ngDevMode ? [{ debugName: "loadFailed" }] : /* istanbul ignore next */ []));
18
19
  }
19
20
  /**
20
21
  * Loads country data from JSON file.
@@ -98,10 +99,10 @@ export class CountriesLoaderService {
98
99
  const countries = await this.getCountries();
99
100
  return countries.filter((c) => c.geoRegions.includes(region));
100
101
  }
101
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountriesLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
102
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountriesLoaderService, providedIn: 'root' }); }
102
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CountriesLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
103
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CountriesLoaderService, providedIn: 'root' }); }
103
104
  }
104
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CountriesLoaderService, decorators: [{
105
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CountriesLoaderService, decorators: [{
105
106
  type: Injectable,
106
107
  args: [{ providedIn: 'root' }]
107
108
  }] });
@@ -1 +1 @@
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
+ {"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"]}