ngx-mat-input-tel 21.4.2 → 22.0.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.
Files changed (61) hide show
  1. package/.angulardoc.json +4 -0
  2. package/.editorconfig +13 -0
  3. package/.github/FUNDING.yml +13 -0
  4. package/.github/instructions/copilot-instructions.md +58 -0
  5. package/.github/workflows/ci.yml +27 -0
  6. package/.github/workflows/publish.yml +41 -0
  7. package/.github/workflows/test.yml +39 -0
  8. package/.husky/commit-msg +4 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.prettierrc +15 -0
  11. package/angular.json +170 -0
  12. package/commitlint.config.ts +3 -0
  13. package/eslint.config.js +43 -0
  14. package/example-1.png +0 -0
  15. package/package.json +84 -55
  16. package/pnpm-workspace.yaml +19 -0
  17. package/projects/demo/eslint.config.js +3 -0
  18. package/projects/demo/karma.conf.js +31 -0
  19. package/projects/demo/src/app/app.html +123 -0
  20. package/projects/demo/src/app/app.scss +16 -0
  21. package/projects/demo/src/app/app.spec.ts +35 -0
  22. package/projects/demo/src/app/app.ts +100 -0
  23. package/projects/demo/src/app/dialog/dialog.html +12 -0
  24. package/projects/demo/src/app/dialog/dialog.ts +31 -0
  25. package/projects/demo/src/environments/environment.prod.ts +3 -0
  26. package/projects/demo/src/environments/environment.ts +3 -0
  27. package/projects/demo/src/favicon.ico +0 -0
  28. package/projects/demo/src/index.html +21 -0
  29. package/projects/demo/src/main.ts +16 -0
  30. package/projects/demo/src/styles.scss +32 -0
  31. package/projects/demo/tsconfig.app.json +17 -0
  32. package/projects/demo/tsconfig.spec.json +17 -0
  33. package/projects/ngx-mat-input-tel/eslint.config.js +3 -0
  34. package/projects/ngx-mat-input-tel/karma.conf.js +31 -0
  35. package/projects/ngx-mat-input-tel/ng-package.json +8 -0
  36. package/projects/ngx-mat-input-tel/package.json +48 -0
  37. package/projects/ngx-mat-input-tel/src/lib/assets/arrow_drop_down_grey600_18dp.png +0 -0
  38. package/projects/ngx-mat-input-tel/src/lib/assets/flags_sprite_2x.png +0 -0
  39. package/projects/ngx-mat-input-tel/src/lib/data/country-code.const.ts +792 -0
  40. package/projects/ngx-mat-input-tel/src/lib/model/country.model.ts +12 -0
  41. package/projects/ngx-mat-input-tel/src/lib/model/phone-number-format.model.ts +1 -0
  42. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel-dialog/ngx-mat-input-tel.dialog.html +82 -0
  43. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel-dialog/ngx-mat-input-tel.dialog.scss +91 -0
  44. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel-dialog/ngx-mat-input-tel.dialog.ts +136 -0
  45. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel-flag/ngx-mat-input-tel-flag.scss +319 -0
  46. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel-flag/ngx-mat-input-tel-flag.ts +72 -0
  47. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel.html +42 -0
  48. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel.scss +122 -0
  49. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel.spec.ts +318 -0
  50. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel.ts +625 -0
  51. package/projects/ngx-mat-input-tel/src/lib/ngx-mat-input-tel.validator.ts +35 -0
  52. package/projects/ngx-mat-input-tel/src/lib/remove-iso.pipe.ts +13 -0
  53. package/projects/ngx-mat-input-tel/src/public-api.ts +7 -0
  54. package/projects/ngx-mat-input-tel/src/test.ts +10 -0
  55. package/projects/ngx-mat-input-tel/tsconfig.lib.json +25 -0
  56. package/projects/ngx-mat-input-tel/tsconfig.lib.prod.json +15 -0
  57. package/projects/ngx-mat-input-tel/tsconfig.spec.json +16 -0
  58. package/tsconfig.json +28 -0
  59. package/fesm2022/ngx-mat-input-tel.mjs +0 -1603
  60. package/fesm2022/ngx-mat-input-tel.mjs.map +0 -1
  61. package/types/ngx-mat-input-tel.d.ts +0 -162
@@ -0,0 +1,12 @@
1
+ export interface CountryFlag {
2
+ iso2: string;
3
+ dialCode: string;
4
+ name?: string;
5
+ areaCodes?: string[];
6
+ }
7
+
8
+ export interface Country extends CountryFlag {
9
+ name: string;
10
+ priority: number;
11
+ placeholder?: string;
12
+ }
@@ -0,0 +1 @@
1
+ export type PhoneNumberFormat = "default" | "national" | "international";
@@ -0,0 +1,82 @@
1
+ <div class="country-select-dialog">
2
+ <div class="dialog-header">
3
+ <h2 mat-dialog-title>{{ data.ariaLabel }}</h2>
4
+ <button
5
+ matIconButton
6
+ type="button"
7
+ class="close-button"
8
+ [mat-dialog-close]
9
+ aria-label="Close dialog"
10
+ >
11
+ <mat-icon>close</mat-icon>
12
+ </button>
13
+ </div>
14
+
15
+ <mat-dialog-content>
16
+ @if (data.enableSearch) {
17
+ <mat-form-field class="search-field" appearance="outline" subscriptSizing="dynamic">
18
+ <mat-label>{{ data.searchPlaceholder }}</mat-label>
19
+ <input
20
+ #searchInput
21
+ matInput
22
+ type="text"
23
+ [(ngModel)]="$searchCriteria"
24
+ [placeholder]="data.searchPlaceholder"
25
+ autocomplete="off"
26
+ />
27
+ @if ($searchCriteria()) {
28
+ <button
29
+ matSuffix
30
+ matIconButton
31
+ type="button"
32
+ aria-label="Clear search"
33
+ (click)="$searchCriteria.set('')"
34
+ >
35
+ <mat-icon>close</mat-icon>
36
+ </button>
37
+ }
38
+ </mat-form-field>
39
+ }
40
+
41
+ <div class="country-list-container">
42
+ <mat-list>
43
+ @for (
44
+ country of $selectablePreferredCountriesInDropDown() | keyvalue: null;
45
+ track country.key
46
+ ) {
47
+ <mat-list-item
48
+ class="country-list-item"
49
+ (click)="onCountrySelect(country)"
50
+ role="button"
51
+ tabindex="0"
52
+ (keydown.enter)="onCountrySelect(country)"
53
+ (keydown.space)="onCountrySelect(country); $event.preventDefault()"
54
+ >
55
+ <ngx-mat-input-tel-flag
56
+ class="country-flag"
57
+ [country]="country.value"
58
+ ></ngx-mat-input-tel-flag>
59
+ </mat-list-item>
60
+ }
61
+ @if ($showDivider()) {
62
+ <mat-divider></mat-divider>
63
+ }
64
+ @for (country of $selectableCountries() | keyvalue: null; track country.key) {
65
+ <mat-list-item
66
+ class="country-list-item"
67
+ (click)="onCountrySelect(country)"
68
+ role="button"
69
+ tabindex="0"
70
+ (keydown.enter)="onCountrySelect(country)"
71
+ (keydown.space)="onCountrySelect(country); $event.preventDefault()"
72
+ >
73
+ <ngx-mat-input-tel-flag
74
+ class="country-flag"
75
+ [country]="country.value"
76
+ ></ngx-mat-input-tel-flag>
77
+ </mat-list-item>
78
+ }
79
+ </mat-list>
80
+ </div>
81
+ </mat-dialog-content>
82
+ </div>
@@ -0,0 +1,91 @@
1
+ :host {
2
+ .country-select-dialog {
3
+ display: flex;
4
+ flex-direction: column;
5
+ max-height: 80vh;
6
+ width: 100%;
7
+ }
8
+
9
+ .dialog-header {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ padding: 0 8px 0 24px;
14
+ min-height: 64px;
15
+
16
+ h2 {
17
+ margin: 0;
18
+ font-size: 20px;
19
+ font-weight: 500;
20
+ flex: 1;
21
+ padding-left: 0;
22
+ }
23
+
24
+ .close-button {
25
+ flex-shrink: 0;
26
+ }
27
+ }
28
+
29
+ mat-dialog-content {
30
+ padding: 0;
31
+ margin: 0;
32
+ overflow: hidden;
33
+ display: flex;
34
+ flex-direction: column;
35
+ max-height: calc(80vh - 64px);
36
+ }
37
+
38
+ .search-field {
39
+ width: 100%;
40
+ margin: 0;
41
+ padding: 8px 24px;
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ .country-list-container {
46
+ overflow-y: auto;
47
+ flex: 1;
48
+ min-height: 200px;
49
+ max-height: calc(80vh - 64px - 80px);
50
+ }
51
+
52
+ .country-list-item {
53
+ cursor: pointer;
54
+ min-height: 48px;
55
+ padding: 8px 24px;
56
+ transition: background-color 0.2s ease;
57
+
58
+ &:hover,
59
+ &:focus {
60
+ background-color: rgba(0, 0, 0, 0.04);
61
+ outline: none;
62
+ }
63
+
64
+ &:active {
65
+ background-color: rgba(0, 0, 0, 0.08);
66
+ }
67
+ }
68
+
69
+ .country-flag {
70
+ width: 100%;
71
+ }
72
+
73
+ mat-divider {
74
+ margin: 8px 0;
75
+ }
76
+
77
+ // Responsive adjustments
78
+ @media (max-width: 599px) {
79
+ .country-select-dialog {
80
+ max-height: 100vh;
81
+ }
82
+
83
+ mat-dialog-content {
84
+ max-height: calc(100vh - 64px);
85
+ }
86
+
87
+ .country-list-container {
88
+ max-height: calc(100vh - 64px - 80px);
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,136 @@
1
+ import { KeyValuePipe } from "@angular/common";
2
+ import {
3
+ Component,
4
+ ElementRef,
5
+ Inject,
6
+ OnInit,
7
+ ViewChild,
8
+ computed,
9
+ signal
10
+ } from "@angular/core";
11
+ import { FormsModule } from "@angular/forms";
12
+ import { MatButtonModule } from "@angular/material/button";
13
+ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from "@angular/material/dialog";
14
+ import { MatDividerModule } from "@angular/material/divider";
15
+ import { MatFormFieldModule } from "@angular/material/form-field";
16
+ import { MatIconModule } from "@angular/material/icon";
17
+ import { MatInputModule } from "@angular/material/input";
18
+ import { MatListModule } from "@angular/material/list";
19
+ import { Country } from "../model/country.model";
20
+ import { NgxMatInputTelFlagComponent } from "../ngx-mat-input-tel-flag/ngx-mat-input-tel-flag";
21
+
22
+ export interface NgxMatInputTelDialogData {
23
+ selectedCountry: Country;
24
+ availableCountries: Record<string, Country>;
25
+ preferredCountriesInDropDown: Record<string, Country>;
26
+ enableSearch: boolean;
27
+ searchPlaceholder: string;
28
+ ariaLabel: string;
29
+ }
30
+
31
+ @Component({
32
+ selector: "ngx-mat-input-tel-dialog",
33
+ templateUrl: "./ngx-mat-input-tel.dialog.html",
34
+ styleUrls: ["./ngx-mat-input-tel.dialog.scss"],
35
+ imports: [
36
+ // Forms
37
+ FormsModule,
38
+ MatFormFieldModule,
39
+ MatInputModule,
40
+
41
+ // Mat
42
+ MatDialogModule,
43
+ MatButtonModule,
44
+ MatIconModule,
45
+ MatListModule,
46
+ MatDividerModule,
47
+ KeyValuePipe,
48
+
49
+ // Components
50
+ NgxMatInputTelFlagComponent,
51
+ ],
52
+ })
53
+ export class NgxMatInputTelDialog implements OnInit {
54
+ @ViewChild("searchInput", { static: false }) searchInput?: ElementRef<HTMLInputElement>;
55
+
56
+ $searchCriteria = signal<string>("");
57
+
58
+ $selectableCountries = computed(() => {
59
+ let countries: Record<string, Country> = {};
60
+ if (!this.$searchCriteria() || this.$searchCriteria() === "") {
61
+ countries = this.data.availableCountries;
62
+ } else {
63
+ countries = this._getOnSearchCountries(
64
+ this.$searchCriteria().toLowerCase(),
65
+ this.data.availableCountries,
66
+ );
67
+ }
68
+
69
+ // Remove preferred countries from the main list by creating a new object
70
+ const preferredKeys = Object.keys(this.data.preferredCountriesInDropDown);
71
+ const result: Record<string, Country> = {};
72
+ for (const [iso2, country] of Object.entries(countries)) {
73
+ if (!preferredKeys.includes(iso2)) {
74
+ result[iso2] = country;
75
+ }
76
+ }
77
+
78
+ return result;
79
+ });
80
+
81
+ $selectablePreferredCountriesInDropDown = computed(() => {
82
+ if (!this.$searchCriteria() || this.$searchCriteria() === "") {
83
+ return this.data.preferredCountriesInDropDown;
84
+ }
85
+
86
+ return this._getOnSearchCountries(
87
+ this.$searchCriteria().toLowerCase(),
88
+ this.data.preferredCountriesInDropDown,
89
+ );
90
+ });
91
+
92
+ $showDivider = computed(() => {
93
+ return (
94
+ Object.entries(this.data.preferredCountriesInDropDown).length > 0 &&
95
+ Object.entries(this.$selectableCountries()).length > 0
96
+ );
97
+ });
98
+
99
+ constructor(
100
+ public dialogRef: MatDialogRef<NgxMatInputTelDialog>,
101
+ @Inject(MAT_DIALOG_DATA) public data: NgxMatInputTelDialogData,
102
+ ) {}
103
+
104
+ ngOnInit(): void {
105
+ // Focus search input after view init
106
+ if (this.data.enableSearch) {
107
+ setTimeout(() => {
108
+ this.searchInput?.nativeElement?.focus();
109
+ }, 0);
110
+ }
111
+ }
112
+
113
+ onCountrySelect(country: { key: string; value: Country }): void {
114
+ this.dialogRef.close({
115
+ ...country.value,
116
+ iso2: country.key,
117
+ });
118
+ }
119
+
120
+ private _getOnSearchCountries(
121
+ searchTerm: string,
122
+ countries: Record<string, Country>,
123
+ ): Record<string, Country> {
124
+ const result: Record<string, Country> = {};
125
+ for (const country of Object.values(countries)) {
126
+ if (
127
+ country.name.toLowerCase().includes(searchTerm) ||
128
+ country.dialCode.includes(searchTerm) ||
129
+ (country.areaCodes && country.areaCodes.join(",").includes(searchTerm))
130
+ ) {
131
+ result[country.iso2] = country;
132
+ }
133
+ }
134
+ return result;
135
+ }
136
+ }
@@ -0,0 +1,319 @@
1
+ :host {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ gap: 4px;
6
+
7
+ .menu-flag {
8
+ display: var(--ngxMatInputTel-menu-flag-display);
9
+ }
10
+ @media print {
11
+ --ngxMatInputTel-flag-display: none;
12
+ }
13
+
14
+ $country-flags: (
15
+ KY: 0,
16
+ AC: -14px,
17
+ AE: -28px,
18
+ AF: -42px,
19
+ AG: -56px,
20
+ AI: -70px,
21
+ AL: -84px,
22
+ AM: -98px,
23
+ AO: -112px,
24
+ AQ: -126px,
25
+ AR: -140px,
26
+ AS: -154px,
27
+ AT: -168px,
28
+ AU: -182px,
29
+ AW: -196px,
30
+ AX: -210px,
31
+ AZ: -224px,
32
+ BA: -238px,
33
+ BB: -252px,
34
+ BD: -266px,
35
+ BE: -280px,
36
+ BF: -294px,
37
+ BG: -308px,
38
+ BH: -322px,
39
+ BI: -336px,
40
+ BJ: -350px,
41
+ BL: -364px,
42
+ BM: -378px,
43
+ BN: -392px,
44
+ BO: -406px,
45
+ BQ: -420px,
46
+ BR: -434px,
47
+ BS: -448px,
48
+ BT: -462px,
49
+ BV: -476px,
50
+ BW: -490px,
51
+ BY: -504px,
52
+ BZ: -518px,
53
+ CA: -532px,
54
+ CC: -546px,
55
+ CD: -560px,
56
+ CF: -574px,
57
+ CG: -588px,
58
+ CH: -602px,
59
+ CI: -616px,
60
+ CK: -630px,
61
+ CL: -644px,
62
+ CM: -658px,
63
+ CN: -672px,
64
+ CO: -686px,
65
+ CP: -700px,
66
+ CR: -714px,
67
+ CU: -728px,
68
+ CV: -742px,
69
+ CW: -756px,
70
+ CX: -770px,
71
+ CY: -784px,
72
+ CZ: -798px,
73
+ DE: -812px,
74
+ DG: -826px,
75
+ DJ: -840px,
76
+ DK: -854px,
77
+ DM: -868px,
78
+ DO: -882px,
79
+ DZ: -896px,
80
+ EA: -910px,
81
+ EC: -924px,
82
+ EE: -938px,
83
+ EG: -952px,
84
+ EH: -966px,
85
+ ER: -980px,
86
+ ES: -994px,
87
+ ET: -1008px,
88
+ EU: -1022px,
89
+ FI: -1036px,
90
+ FJ: -1050px,
91
+ FK: -1064px,
92
+ FM: -1078px,
93
+ FO: -1092px,
94
+ FR: -1106px,
95
+ GA: -1120px,
96
+ GB: -1134px,
97
+ GD: -1148px,
98
+ GE: -1162px,
99
+ GF: -1176px,
100
+ GG: -1190px,
101
+ GH: -1204px,
102
+ GI: -1218px,
103
+ GL: -1232px,
104
+ GM: -1246px,
105
+ GN: -1260px,
106
+ GP: -1274px,
107
+ GQ: -1288px,
108
+ GR: -1302px,
109
+ GS: -1316px,
110
+ GT: -1330px,
111
+ GU: -1344px,
112
+ GW: -1358px,
113
+ GY: -1372px,
114
+ HK: -1386px,
115
+ HM: -1400px,
116
+ HN: -1414px,
117
+ HR: -1428px,
118
+ HT: -1442px,
119
+ HU: -1456px,
120
+ IC: -1470px,
121
+ ID: -1484px,
122
+ IE: -1498px,
123
+ IL: -1512px,
124
+ IM: -1526px,
125
+ IN: -1540px,
126
+ IO: -1554px,
127
+ IQ: -1568px,
128
+ IR: -1582px,
129
+ IS: -1596px,
130
+ IT: -1610px,
131
+ JE: -1624px,
132
+ JM: -1638px,
133
+ JO: -1652px,
134
+ JP: -1666px,
135
+ KE: -1680px,
136
+ KG: -1694px,
137
+ KH: -1708px,
138
+ KI: -1722px,
139
+ KM: -1736px,
140
+ KN: -1750px,
141
+ KP: -1764px,
142
+ KR: -1778px,
143
+ KW: -1792px,
144
+ AD: -1806px,
145
+ KZ: -1820px,
146
+ LA: -1834px,
147
+ LB: -1848px,
148
+ LC: -1862px,
149
+ LI: -1876px,
150
+ LK: -1890px,
151
+ LR: -1904px,
152
+ LS: -1918px,
153
+ LT: -1932px,
154
+ LU: -1946px,
155
+ LV: -1960px,
156
+ LY: -1974px,
157
+ MA: -1988px,
158
+ MC: -2002px,
159
+ MD: -2016px,
160
+ ME: -2030px,
161
+ MF: -2044px,
162
+ MG: -2058px,
163
+ MH: -2072px,
164
+ MK: -2086px,
165
+ ML: -2100px,
166
+ MM: -2114px,
167
+ MN: -2128px,
168
+ MO: -2142px,
169
+ MP: -2156px,
170
+ MQ: -2170px,
171
+ MR: -2184px,
172
+ MS: -2198px,
173
+ MT: -2212px,
174
+ MU: -2226px,
175
+ MV: -2240px,
176
+ MW: -2254px,
177
+ MX: -2268px,
178
+ MY: -2282px,
179
+ MZ: -2296px,
180
+ NA: -2310px,
181
+ NC: -2324px,
182
+ NE: -2338px,
183
+ NF: -2352px,
184
+ NG: -2366px,
185
+ NI: -2380px,
186
+ NL: -2394px,
187
+ NO: -2408px,
188
+ NP: -2422px,
189
+ NR: -2436px,
190
+ NU: -2450px,
191
+ NZ: -2464px,
192
+ OM: -2478px,
193
+ PA: -2492px,
194
+ PE: -2506px,
195
+ PF: -2520px,
196
+ PG: -2534px,
197
+ PH: -2548px,
198
+ PK: -2562px,
199
+ PL: -2576px,
200
+ PM: -2590px,
201
+ PN: -2604px,
202
+ PR: -2618px,
203
+ PS: -2632px,
204
+ PT: -2646px,
205
+ PW: -2660px,
206
+ PY: -2674px,
207
+ QA: -2688px,
208
+ RE: -2702px,
209
+ RO: -2716px,
210
+ RS: -2730px,
211
+ RU: -2744px,
212
+ RW: -2758px,
213
+ SA: -2772px,
214
+ SB: -2786px,
215
+ SC: -2800px,
216
+ SD: -2814px,
217
+ SE: -2828px,
218
+ SG: -2842px,
219
+ SH: -2856px,
220
+ SI: -2870px,
221
+ SJ: -2884px,
222
+ SK: -2898px,
223
+ SL: -2912px,
224
+ SM: -2926px,
225
+ SN: -2940px,
226
+ SO: -2954px,
227
+ SR: -2968px,
228
+ SS: -2982px,
229
+ ST: -2996px,
230
+ SV: -3010px,
231
+ SX: -3024px,
232
+ SY: -3038px,
233
+ SZ: -3052px,
234
+ TA: -3066px,
235
+ TC: -3080px,
236
+ TD: -3094px,
237
+ TF: -3108px,
238
+ TG: -3122px,
239
+ TH: -3136px,
240
+ TJ: -3150px,
241
+ TK: -3164px,
242
+ TL: -3178px,
243
+ TM: -3192px,
244
+ TN: -3206px,
245
+ TO: -3220px,
246
+ TR: -3234px,
247
+ TT: -3248px,
248
+ TV: -3262px,
249
+ TW: -3276px,
250
+ TZ: -3290px,
251
+ UA: -3304px,
252
+ UG: -3318px,
253
+ UM: -3332px,
254
+ UN: -3346px,
255
+ US: -3360px,
256
+ UY: -3374px,
257
+ UZ: -3388px,
258
+ VA: -3402px,
259
+ VC: -3416px,
260
+ VE: -3430px,
261
+ VG: -3444px,
262
+ VI: -3458px,
263
+ VN: -3472px,
264
+ VU: -3486px,
265
+ WF: -3500px,
266
+ WS: -3514px,
267
+ XK: -3528px,
268
+ YE: -3542px,
269
+ YT: -3556px,
270
+ ZA: -3570px,
271
+ ZM: -3584px,
272
+ ZW: -3598px,
273
+ );
274
+
275
+ .flag {
276
+ display: var(--ngxMatInputTel-flag-display, inline-block);
277
+ margin-right: 0.5ex;
278
+ background: {
279
+ image: url(../assets/flags_sprite_2x.png);
280
+ size: 100% auto;
281
+ }
282
+ filter: drop-shadow(1px 1px 1px rgba(#000, 0.54));
283
+ height: 14px;
284
+ width: 24px;
285
+ min-width: 24px;
286
+
287
+ @each $country-class, $flag-position in $country-flags {
288
+ &.#{$country-class} {
289
+ background-position: 0 $flag-position;
290
+ }
291
+ }
292
+ }
293
+
294
+ .country-selector-name {
295
+ flex-grow: 1;
296
+ overflow: hidden;
297
+ text-overflow: ellipsis;
298
+ white-space: nowrap;
299
+ }
300
+
301
+ .country-selector-code {
302
+ flex-shrink: 0;
303
+ white-space: nowrap;
304
+ }
305
+
306
+ .area-codes-badge {
307
+ flex-shrink: 0;
308
+ padding: 2px 8px;
309
+ margin-left: 4px;
310
+ border-radius: 12px;
311
+ font-size: 0.75rem;
312
+ line-height: 1.2;
313
+ background-color: var(--mat-form-field-filled-container-color, var(--mat-sys-surface-variant));
314
+ color: var(--mat-list-list-item-label-text-color, var(--mat-sys-on-surface));
315
+ white-space: nowrap;
316
+ cursor: help;
317
+ transition: background-color 0.2s ease;
318
+ }
319
+ }
@@ -0,0 +1,72 @@
1
+ import { NgClass } from "@angular/common";
2
+ import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
3
+ import { MatTooltipModule } from "@angular/material/tooltip";
4
+ import { CountryFlag } from "../model/country.model";
5
+
6
+ @Component({
7
+ selector: "ngx-mat-input-tel-flag",
8
+ imports: [NgClass, MatTooltipModule],
9
+ template: `
10
+ <div class="flag" [ngClass]="country.iso2"></div>
11
+
12
+ @if (country.name) {
13
+ <span class="country-selector-name">
14
+ {{ country.name }}
15
+ </span>
16
+ }
17
+
18
+ @if (country.dialCode) {
19
+ <span class="country-selector-code"> +{{ country.dialCode }} </span>
20
+ }
21
+
22
+ @if (country.areaCodes && country.areaCodes.length > 0) {
23
+ <span
24
+ class="area-codes-badge"
25
+ [matTooltip]="getAreaCodesFullList()"
26
+ matTooltipPosition="above"
27
+ [attr.aria-label]="'Area codes: ' + country.areaCodes.join(', ')"
28
+ >
29
+ {{ getAreaCodesDisplay() }}
30
+ </span>
31
+ }
32
+ `,
33
+ styleUrl: "./ngx-mat-input-tel-flag.scss",
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ })
36
+ export class NgxMatInputTelFlagComponent {
37
+ @Input({ required: true }) country!: CountryFlag;
38
+
39
+ /**
40
+ * Returns a compact display string for area codes
41
+ * Examples:
42
+ * - "Area: 201, 202 +4" (shows first 2 codes and count of remaining)
43
+ * - "Area: 684" (shows single code)
44
+ * - "Area: 201, 202" (shows 2 codes when there are only 2)
45
+ */
46
+ getAreaCodesDisplay(): string {
47
+ if (!this.country.areaCodes || this.country.areaCodes.length === 0) {
48
+ return "";
49
+ }
50
+
51
+ const codes = this.country.areaCodes;
52
+ const displayLimit = 2;
53
+
54
+ if (codes.length <= displayLimit) {
55
+ return `Area: ${codes.join(", ")}`;
56
+ }
57
+
58
+ const displayCodes = codes.slice(0, displayLimit).join(", ");
59
+ const remainingCount = codes.length - displayLimit;
60
+ return `Area: ${displayCodes} +${remainingCount}`;
61
+ }
62
+
63
+ /**
64
+ * Returns the full list of area codes for tooltip display
65
+ */
66
+ getAreaCodesFullList(): string {
67
+ if (!this.country.areaCodes || this.country.areaCodes.length === 0) {
68
+ return "";
69
+ }
70
+ return `Area codes: ${this.country.areaCodes.join(", ")}`;
71
+ }
72
+ }