country-data-filter 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -484,7 +484,16 @@ The dropdowns shown are determined by which `on*Change` callbacks are provided:
484
484
 
485
485
  ## Angular Location Picker
486
486
 
487
- A standalone PrimeNG cascade picker component for Angular 17+. Ships as raw TypeScript source — no build step required.
487
+ A standalone PrimeNG cascade picker for Angular 17+. Ships as raw TypeScript source — no build step required.
488
+
489
+ ### PrimeNG version compatibility
490
+
491
+ | PrimeNG version | Component to import | Selector |
492
+ |---|---|---|
493
+ | v17 – v19 | `LocationPickerComponent` | `<cdf-location-picker>` |
494
+ | v20+ | `LocationPickerSelectComponent` | `<cdf-location-picker-select>` |
495
+
496
+ PrimeNG v20 removed `primeng/dropdown` (`p-dropdown`). The `LocationPickerSelectComponent` uses `primeng/select` (`p-select`) which is required for v18+.
488
497
 
489
498
  ### Peer dependencies
490
499
 
@@ -507,18 +516,36 @@ Add the source path to your `tsconfig.json`:
507
516
 
508
517
  ### Import
509
518
 
519
+ Both components are exported from the main barrel. Import by name — TypeScript will resolve the correct file:
520
+
521
+ **PrimeNG v17–v19:**
522
+
510
523
  ```ts
511
524
  import { LocationPickerComponent } from 'country-data-filter/angular';
512
525
  ```
513
526
 
514
- Add to your component's `imports` array (it is a standalone component):
527
+ **PrimeNG v20+:**
515
528
 
516
529
  ```ts
530
+ import { LocationPickerSelectComponent } from 'country-data-filter/angular';
531
+ ```
532
+
533
+ Add to your component's `imports` array (both are standalone components):
534
+
535
+ ```ts
536
+ // PrimeNG v17-v19
517
537
  @Component({
518
538
  standalone: true,
519
539
  imports: [LocationPickerComponent],
520
540
  template: `<cdf-location-picker [countryControl]="countryCtrl" />`
521
541
  })
542
+
543
+ // PrimeNG v20+
544
+ @Component({
545
+ standalone: true,
546
+ imports: [LocationPickerSelectComponent],
547
+ template: `<cdf-location-picker-select [countryControl]="countryCtrl" />`
548
+ })
522
549
  ```
523
550
 
524
551
  ### Basic usage — reactive forms
@@ -587,7 +614,7 @@ Passing `[cityControl]` automatically implies all levels above it.
587
614
 
588
615
  ### PrimeNG prop passthrough
589
616
 
590
- Pass any PrimeNG `p-dropdown` props via the `*DropdownProps` inputs:
617
+ **v17–v19** use `*DropdownProps` inputs:
591
618
 
592
619
  ```html
593
620
  <cdf-location-picker
@@ -597,14 +624,30 @@ Pass any PrimeNG `p-dropdown` props via the `*DropdownProps` inputs:
597
624
  </cdf-location-picker>
598
625
  ```
599
626
 
627
+ **v20+** — use `*SelectProps` inputs (same object shape):
628
+
629
+ ```html
630
+ <cdf-location-picker-select
631
+ [countryControl]="countryCtrl"
632
+ [countrySelectProps]="{ styleClass: 'w-full', appendTo: 'body', showClear: true }"
633
+ [provinceSelectProps]="{ styleClass: 'w-full', filter: false }">
634
+ </cdf-location-picker-select>
635
+ ```
636
+
600
637
  ### Layout
601
638
 
602
639
  ```html
640
+ <!-- v17-v19 -->
603
641
  <cdf-location-picker layout="horizontal" [countryControl]="ctrl" />
642
+
643
+ <!-- v20+ -->
644
+ <cdf-location-picker-select layout="horizontal" [countryControl]="ctrl" />
604
645
  ```
605
646
 
606
647
  ### Inputs reference
607
648
 
649
+ Both components share the same inputs — the only difference is `*DropdownProps` vs `*SelectProps`:
650
+
608
651
  | Input | Type | Default | Description |
609
652
  |---|---|---|---|
610
653
  | `countryControl` | `AbstractControl` | — | Reactive form control for country |
@@ -613,10 +656,14 @@ Pass any PrimeNG `p-dropdown` props via the `*DropdownProps` inputs:
613
656
  | `cityControl` | `AbstractControl` | — | Reactive form control for city |
614
657
  | `countryLabelKey` | `'name' \| 'code' \| 'currency' \| 'countryCode'` | `'name'` | Field to use as dropdown label |
615
658
  | `countryValueKey` | `'name' \| 'code' \| 'currency' \| 'countryCode'` | `'code'` | Field to use as option value |
616
- | `countryDropdownProps` | `DropdownProps` | `{}` | Props spread onto the country `<p-dropdown>` |
617
- | `provinceDropdownProps` | `DropdownProps` | `{}` | Props spread onto the province `<p-dropdown>` |
618
- | `districtDropdownProps` | `DropdownProps` | `{}` | Props spread onto the district `<p-dropdown>` |
619
- | `cityDropdownProps` | `DropdownProps` | `{}` | Props spread onto the city `<p-dropdown>` |
659
+ | `countryDropdownProps` *(v17–v19)* | `DropdownProps` | `{}` | Props spread onto the country `<p-dropdown>` |
660
+ | `provinceDropdownProps` *(v17–v19)* | `DropdownProps` | `{}` | Props spread onto the province `<p-dropdown>` |
661
+ | `districtDropdownProps` *(v17–v19)* | `DropdownProps` | `{}` | Props spread onto the district `<p-dropdown>` |
662
+ | `cityDropdownProps` *(v17–v19)* | `DropdownProps` | `{}` | Props spread onto the city `<p-dropdown>` |
663
+ | `countrySelectProps` *(v20+)* | `SelectProps` | `{}` | Props spread onto the country `<p-select>` |
664
+ | `provinceSelectProps` *(v20+)* | `SelectProps` | `{}` | Props spread onto the province `<p-select>` |
665
+ | `districtSelectProps` *(v20+)* | `SelectProps` | `{}` | Props spread onto the district `<p-select>` |
666
+ | `citySelectProps` *(v20+)* | `SelectProps` | `{}` | Props spread onto the city `<p-select>` |
620
667
  | `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Flex direction of the wrapper |
621
668
 
622
669
  ### Outputs reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "country-data-filter",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Geographical data for 190 countries — provinces, districts, cities and postal codes. Includes React (Ant Design / MUI) and Angular (PrimeNG) location picker cascade components with full TypeScript support.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -16,7 +16,7 @@ import { DropdownModule } from 'primeng/dropdown';
16
16
  import { Observable, of, Subject } from 'rxjs';
17
17
  import { startWith, switchMap, tap, takeUntil } from 'rxjs/operators';
18
18
 
19
- import { LocationPickerService } from './location-picker.service';
19
+ import { LocationPickerService } from '../location-picker.service';
20
20
  import {
21
21
  LocationOption,
22
22
  CountryChangeEvent,
@@ -24,14 +24,20 @@ import {
24
24
  CityChangeEvent,
25
25
  DropdownProps,
26
26
  CountryKey,
27
- } from './location-picker.types';
28
-
27
+ } from '../location-picker.types';
28
+
29
+ /**
30
+ * Location picker cascade component for PrimeNG v17–v19.
31
+ *
32
+ * Uses `<p-dropdown>` from `primeng/dropdown`.
33
+ * For PrimeNG v20+, use `LocationPickerSelectComponent` from `../select` instead.
34
+ */
29
35
  @Component({
30
36
  selector: 'cdf-location-picker',
31
37
  standalone: true,
32
38
  imports: [CommonModule, ReactiveFormsModule, DropdownModule],
33
39
  templateUrl: './location-picker.component.html',
34
- styleUrls: ['./location-picker.component.css'],
40
+ styleUrls: ['../location-picker.component.css'],
35
41
  })
36
42
  export class LocationPickerComponent implements OnInit, OnDestroy {
37
43
  // ── External reactive-form controls (all optional) ──────────────────────────
@@ -41,10 +47,10 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
41
47
  @Input() cityControl?: AbstractControl;
42
48
 
43
49
  // ── PrimeNG prop passthrough bags ────────────────────────────────────────────
44
- @Input() countryDropdownProps: DropdownProps = {};
50
+ @Input() countryDropdownProps: DropdownProps = {};
45
51
  @Input() provinceDropdownProps: DropdownProps = {};
46
52
  @Input() districtDropdownProps: DropdownProps = {};
47
- @Input() cityDropdownProps: DropdownProps = {};
53
+ @Input() cityDropdownProps: DropdownProps = {};
48
54
 
49
55
  // ── Country option key selectors ─────────────────────────────────────────────
50
56
  /** Which field of the country record to use as the dropdown label. Default: 'name' */
@@ -92,12 +98,10 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
92
98
  constructor(private svc: LocationPickerService) {}
93
99
 
94
100
  ngOnInit(): void {
95
- // Compute which dropdowns to show
96
101
  this.showCity = !!this.cityControl;
97
102
  this.showDistrict = !!this.districtControl || this.showCity;
98
103
  this.showProvince = !!this.provinceControl || this.showDistrict;
99
104
 
100
- // ── Countries (static, sorted A-Z) ──────────────────────────────────────
101
105
  const allCountries = this.svc.getCountries()
102
106
  .map(c => ({
103
107
  label: (c as any)[this.countryLabelKey] as string,
@@ -106,14 +110,12 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
106
110
  .sort((a, b) => a.label.localeCompare(b.label));
107
111
  this.countryOptions$ = of(allCountries);
108
112
 
109
- // Reverse map: mapped option value → raw country code (used for data lookups)
110
113
  const mappedValueToCode = new Map<string, string>(
111
114
  this.svc.getCountries().map(c => [
112
115
  (c as any)[this.countryValueKey] as string,
113
116
  c.code,
114
117
  ])
115
118
  );
116
- // Forward map: raw code → mapped option value (used when auto-filling country)
117
119
  const codeToMappedValue = new Map<string, string>(
118
120
  this.svc.getCountries().map(c => [
119
121
  c.code,
@@ -121,11 +123,9 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
121
123
  ])
122
124
  );
123
125
 
124
- // Helper: get raw code from whatever value is stored in the country control
125
126
  const rawCode = (val: string | null): string | null =>
126
127
  val ? (mappedValueToCode.get(val) ?? val) : null;
127
128
 
128
- // ── Province options (reload when country changes) ───────────────────────
129
129
  this.provinceOptions$ = this._country.valueChanges.pipe(
130
130
  takeUntil(this.destroy$),
131
131
  startWith(this._country.value),
@@ -133,7 +133,6 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
133
133
  this._province.setValue(null, { emitEvent: false });
134
134
  this._district.setValue(null, { emitEvent: false });
135
135
  this._city.setValue(null, { emitEvent: false });
136
- // Update placeholders from the selected country's labels
137
136
  const code = rawCode(val);
138
137
  if (code) {
139
138
  const country = this.svc.getCountries().find(c => c.code === code);
@@ -154,18 +153,15 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
154
153
  }),
155
154
  );
156
155
 
157
- // ── District options (reload when province changes; auto-fill country) ───
158
156
  this.districtOptions$ = this._province.valueChanges.pipe(
159
157
  takeUntil(this.destroy$),
160
158
  startWith(this._province.value),
161
159
  tap((name: string | null) => {
162
160
  this._district.setValue(null, { emitEvent: false });
163
161
  this._city.setValue(null, { emitEvent: false });
164
- // Reverse lookup: if country not set, auto-fill from province name
165
162
  if (name && !this._country.value) {
166
163
  const code = this.svc.getCountryByProvince(name);
167
164
  if (code) {
168
- // Set the mapped value (not the raw code) so the control stays consistent
169
165
  this._country.setValue(codeToMappedValue.get(code) ?? code);
170
166
  }
171
167
  }
@@ -178,7 +174,6 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
178
174
  }),
179
175
  );
180
176
 
181
- // ── City options (reload when district changes) ───────────────────────────
182
177
  this.cityOptions$ = this._district.valueChanges.pipe(
183
178
  takeUntil(this.destroy$),
184
179
  startWith(this._district.value),
@@ -192,7 +187,6 @@ export class LocationPickerComponent implements OnInit, OnDestroy {
192
187
  }),
193
188
  );
194
189
 
195
- // ── Emit output events ───────────────────────────────────────────────────
196
190
  this._country.valueChanges
197
191
  .pipe(takeUntil(this.destroy$))
198
192
  .subscribe((v: string | null) => {
@@ -1,10 +1,12 @@
1
- export { LocationPickerComponent } from './location-picker.component';
2
- export { LocationPickerService } from './location-picker.service';
1
+ export { LocationPickerComponent } from './dropdown/location-picker.component';
2
+ export { LocationPickerSelectComponent } from './select/location-picker.component';
3
+ export { LocationPickerService } from './location-picker.service';
3
4
  export type {
4
5
  LocationOption,
5
6
  CountryChangeEvent,
6
7
  NameChangeEvent,
7
8
  CityChangeEvent,
8
9
  DropdownProps,
10
+ SelectProps,
9
11
  CountryKey,
10
12
  } from './location-picker.types';
@@ -31,3 +31,6 @@ export interface CityChangeEvent {
31
31
  }
32
32
 
33
33
  export type DropdownProps = Record<string, unknown>;
34
+
35
+ /** Alias for use with LocationPickerSelectComponent (PrimeNG v18+) */
36
+ export type SelectProps = DropdownProps;
@@ -0,0 +1,62 @@
1
+ <div [class]="'cdf-location-picker cdf-' + layout">
2
+
3
+ <!-- Country select — always shown -->
4
+ <p-select
5
+ [formControl]="_country"
6
+ [options]="(countryOptions$ | async) ?? []"
7
+ [filter]="true"
8
+ [placeholder]="countrySelectProps['placeholder'] || 'Select Country'"
9
+ [styleClass]="countrySelectProps['styleClass']"
10
+ [showClear]="countrySelectProps['showClear'] ?? true"
11
+ [appendTo]="countrySelectProps['appendTo'] || 'body'"
12
+ [pt]="countrySelectProps['pt']"
13
+ optionLabel="label"
14
+ optionValue="value">
15
+ </p-select>
16
+
17
+ <!-- Province select -->
18
+ <p-select
19
+ *ngIf="showProvince"
20
+ [formControl]="_province"
21
+ [options]="(provinceOptions$ | async) ?? []"
22
+ [filter]="true"
23
+ [placeholder]="provinceSelectProps['placeholder'] || _provincePlaceholder"
24
+ [styleClass]="provinceSelectProps['styleClass']"
25
+ [showClear]="provinceSelectProps['showClear'] ?? true"
26
+ [appendTo]="provinceSelectProps['appendTo'] || 'body'"
27
+ [pt]="provinceSelectProps['pt']"
28
+ optionLabel="label"
29
+ optionValue="value">
30
+ </p-select>
31
+
32
+ <!-- District select -->
33
+ <p-select
34
+ *ngIf="showDistrict"
35
+ [formControl]="_district"
36
+ [options]="(districtOptions$ | async) ?? []"
37
+ [filter]="true"
38
+ [placeholder]="districtSelectProps['placeholder'] || _districtPlaceholder"
39
+ [styleClass]="districtSelectProps['styleClass']"
40
+ [showClear]="districtSelectProps['showClear'] ?? true"
41
+ [appendTo]="districtSelectProps['appendTo'] || 'body'"
42
+ [pt]="districtSelectProps['pt']"
43
+ optionLabel="label"
44
+ optionValue="value">
45
+ </p-select>
46
+
47
+ <!-- City select -->
48
+ <p-select
49
+ *ngIf="showCity"
50
+ [formControl]="_city"
51
+ [options]="(cityOptions$ | async) ?? []"
52
+ [filter]="true"
53
+ [placeholder]="citySelectProps['placeholder'] || 'Select City'"
54
+ [styleClass]="citySelectProps['styleClass']"
55
+ [showClear]="citySelectProps['showClear'] ?? true"
56
+ [appendTo]="citySelectProps['appendTo'] || 'body'"
57
+ [pt]="citySelectProps['pt']"
58
+ optionLabel="label"
59
+ optionValue="value">
60
+ </p-select>
61
+
62
+ </div>
@@ -0,0 +1,224 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ Output,
5
+ EventEmitter,
6
+ OnInit,
7
+ OnDestroy,
8
+ } from '@angular/core';
9
+ import { CommonModule } from '@angular/common';
10
+ import {
11
+ AbstractControl,
12
+ FormControl,
13
+ ReactiveFormsModule,
14
+ } from '@angular/forms';
15
+ import { SelectModule } from 'primeng/select';
16
+ import { Observable, of, Subject } from 'rxjs';
17
+ import { startWith, switchMap, tap, takeUntil } from 'rxjs/operators';
18
+
19
+ import { LocationPickerService } from '../location-picker.service';
20
+ import {
21
+ LocationOption,
22
+ CountryChangeEvent,
23
+ NameChangeEvent,
24
+ CityChangeEvent,
25
+ SelectProps,
26
+ CountryKey,
27
+ } from '../location-picker.types';
28
+
29
+ /**
30
+ * Location picker cascade component for PrimeNG v18+.
31
+ *
32
+ * Uses `<p-select>` from `primeng/select` — required for PrimeNG v20 which
33
+ * removed the old `<p-dropdown>` / `primeng/dropdown`.
34
+ * For PrimeNG v17–v19, use `LocationPickerComponent` from `../dropdown` instead.
35
+ */
36
+ @Component({
37
+ selector: 'cdf-location-picker-select',
38
+ standalone: true,
39
+ imports: [CommonModule, ReactiveFormsModule, SelectModule],
40
+ templateUrl: './location-picker.component.html',
41
+ styleUrls: ['../location-picker.component.css'],
42
+ })
43
+ export class LocationPickerSelectComponent implements OnInit, OnDestroy {
44
+ // ── External reactive-form controls (all optional) ──────────────────────────
45
+ @Input() countryControl?: AbstractControl;
46
+ @Input() provinceControl?: AbstractControl;
47
+ @Input() districtControl?: AbstractControl;
48
+ @Input() cityControl?: AbstractControl;
49
+
50
+ // ── PrimeNG prop passthrough bags ────────────────────────────────────────────
51
+ @Input() countrySelectProps: SelectProps = {};
52
+ @Input() provinceSelectProps: SelectProps = {};
53
+ @Input() districtSelectProps: SelectProps = {};
54
+ @Input() citySelectProps: SelectProps = {};
55
+
56
+ // ── Country option key selectors ─────────────────────────────────────────────
57
+ /** Which field of the country record to use as the dropdown label. Default: 'name' */
58
+ @Input() countryLabelKey: CountryKey = 'name';
59
+ /** Which field of the country record to use as the option value. Default: 'code' */
60
+ @Input() countryValueKey: CountryKey = 'code';
61
+
62
+ // ── Layout ───────────────────────────────────────────────────────────────────
63
+ @Input() layout: 'horizontal' | 'vertical' = 'vertical';
64
+
65
+ // ── Event outputs (for users not using reactive forms) ───────────────────────
66
+ @Output() countryChange = new EventEmitter<CountryChangeEvent>();
67
+ @Output() provinceChange = new EventEmitter<NameChangeEvent>();
68
+ @Output() districtChange = new EventEmitter<NameChangeEvent>();
69
+ @Output() cityChange = new EventEmitter<CityChangeEvent>();
70
+
71
+ // ── Internal fallback controls ───────────────────────────────────────────────
72
+ private _internalCountry = new FormControl<string | null>(null);
73
+ private _internalProvince = new FormControl<string | null>(null);
74
+ private _internalDistrict = new FormControl<string | null>(null);
75
+ private _internalCity = new FormControl<string | null>(null);
76
+
77
+ get _country() { return (this.countryControl ?? this._internalCountry) as FormControl; }
78
+ get _province() { return (this.provinceControl ?? this._internalProvince) as FormControl; }
79
+ get _district() { return (this.districtControl ?? this._internalDistrict) as FormControl; }
80
+ get _city() { return (this.cityControl ?? this._internalCity) as FormControl; }
81
+
82
+ // ── Visibility flags ─────────────────────────────────────────────────────────
83
+ showProvince = false;
84
+ showDistrict = false;
85
+ showCity = false;
86
+
87
+ // ── Dynamic placeholder text (uses selected country's labels) ────────────────
88
+ _provincePlaceholder = 'Select Province';
89
+ _districtPlaceholder = 'Select District';
90
+
91
+ // ── Option streams ───────────────────────────────────────────────────────────
92
+ countryOptions$!: Observable<LocationOption[]>;
93
+ provinceOptions$!: Observable<LocationOption[]>;
94
+ districtOptions$!: Observable<LocationOption[]>;
95
+ cityOptions$!: Observable<LocationOption[]>;
96
+
97
+ private destroy$ = new Subject<void>();
98
+
99
+ constructor(private svc: LocationPickerService) {}
100
+
101
+ ngOnInit(): void {
102
+ this.showCity = !!this.cityControl;
103
+ this.showDistrict = !!this.districtControl || this.showCity;
104
+ this.showProvince = !!this.provinceControl || this.showDistrict;
105
+
106
+ const allCountries = this.svc.getCountries()
107
+ .map(c => ({
108
+ label: (c as any)[this.countryLabelKey] as string,
109
+ value: (c as any)[this.countryValueKey] as string,
110
+ }))
111
+ .sort((a, b) => a.label.localeCompare(b.label));
112
+ this.countryOptions$ = of(allCountries);
113
+
114
+ const mappedValueToCode = new Map<string, string>(
115
+ this.svc.getCountries().map(c => [
116
+ (c as any)[this.countryValueKey] as string,
117
+ c.code,
118
+ ])
119
+ );
120
+ const codeToMappedValue = new Map<string, string>(
121
+ this.svc.getCountries().map(c => [
122
+ c.code,
123
+ (c as any)[this.countryValueKey] as string,
124
+ ])
125
+ );
126
+
127
+ const rawCode = (val: string | null): string | null =>
128
+ val ? (mappedValueToCode.get(val) ?? val) : null;
129
+
130
+ this.provinceOptions$ = this._country.valueChanges.pipe(
131
+ takeUntil(this.destroy$),
132
+ startWith(this._country.value),
133
+ tap((val: string | null) => {
134
+ this._province.setValue(null, { emitEvent: false });
135
+ this._district.setValue(null, { emitEvent: false });
136
+ this._city.setValue(null, { emitEvent: false });
137
+ const code = rawCode(val);
138
+ if (code) {
139
+ const country = this.svc.getCountries().find(c => c.code === code);
140
+ if (country) {
141
+ this._provincePlaceholder = `Select ${country.provinceLabel ?? 'Province'}`;
142
+ this._districtPlaceholder = `Select ${country.districtLabel ?? 'District'}`;
143
+ }
144
+ } else {
145
+ this._provincePlaceholder = 'Select Province';
146
+ this._districtPlaceholder = 'Select District';
147
+ }
148
+ }),
149
+ switchMap((val: string | null) => {
150
+ const code = rawCode(val);
151
+ return code
152
+ ? of(this.svc.getProvinces(code).map(p => ({ label: p, value: p })))
153
+ : of([]);
154
+ }),
155
+ );
156
+
157
+ this.districtOptions$ = this._province.valueChanges.pipe(
158
+ takeUntil(this.destroy$),
159
+ startWith(this._province.value),
160
+ tap((name: string | null) => {
161
+ this._district.setValue(null, { emitEvent: false });
162
+ this._city.setValue(null, { emitEvent: false });
163
+ if (name && !this._country.value) {
164
+ const code = this.svc.getCountryByProvince(name);
165
+ if (code) {
166
+ this._country.setValue(codeToMappedValue.get(code) ?? code);
167
+ }
168
+ }
169
+ }),
170
+ switchMap((name: string | null) => {
171
+ const code = rawCode(this._country.value);
172
+ return name && code
173
+ ? of(this.svc.getDistricts(code, name).map(d => ({ label: d, value: d })))
174
+ : of([]);
175
+ }),
176
+ );
177
+
178
+ this.cityOptions$ = this._district.valueChanges.pipe(
179
+ takeUntil(this.destroy$),
180
+ startWith(this._district.value),
181
+ tap(() => this._city.setValue(null, { emitEvent: false })),
182
+ switchMap((dist: string | null) => {
183
+ const code = rawCode(this._country.value);
184
+ const prov = this._province.value;
185
+ return dist && code && prov
186
+ ? of(this.svc.getCities(code, prov, dist).map(c => ({ label: c, value: c })))
187
+ : of([]);
188
+ }),
189
+ );
190
+
191
+ this._country.valueChanges
192
+ .pipe(takeUntil(this.destroy$))
193
+ .subscribe((v: string | null) => {
194
+ const code = rawCode(v);
195
+ const country = code ? (this.svc.getCountries().find(c => c.code === code) ?? null) : null;
196
+ this.countryChange.emit({ formValue: v, country });
197
+ });
198
+
199
+ this._province.valueChanges
200
+ .pipe(takeUntil(this.destroy$))
201
+ .subscribe((v: string | null) => this.provinceChange.emit({ name: v }));
202
+
203
+ this._district.valueChanges
204
+ .pipe(takeUntil(this.destroy$))
205
+ .subscribe((v: string | null) => this.districtChange.emit({ name: v }));
206
+
207
+ this._city.valueChanges
208
+ .pipe(takeUntil(this.destroy$))
209
+ .subscribe((v: string | null) => {
210
+ const code = rawCode(this._country.value);
211
+ const prov = this._province.value;
212
+ const dist = this._district.value;
213
+ const raw = (code && prov && dist && v)
214
+ ? this.svc.getCityRaw(code, prov, dist, v)
215
+ : null;
216
+ this.cityChange.emit({ name: v, postalCode: raw?.postalCode ?? '', type: raw?.type });
217
+ });
218
+ }
219
+
220
+ ngOnDestroy(): void {
221
+ this.destroy$.next();
222
+ this.destroy$.complete();
223
+ }
224
+ }