@countriesdb/widget-core 0.1.1
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/LICENSE +25 -0
- package/README.md +39 -0
- package/dist/api-client.d.ts +22 -0
- package/dist/api-client.js +104 -0
- package/dist/esm/api-client.d.ts +22 -0
- package/dist/esm/api-client.js +100 -0
- package/dist/esm/filters.d.ts +24 -0
- package/dist/esm/filters.js +62 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/subdivision-tree.d.ts +16 -0
- package/dist/esm/subdivision-tree.js +102 -0
- package/dist/esm/types.d.ts +89 -0
- package/dist/esm/types.js +4 -0
- package/dist/filters.d.ts +24 -0
- package/dist/filters.js +67 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +19 -0
- package/dist/subdivision-tree.d.ts +16 -0
- package/dist/subdivision-tree.js +107 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +5 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
PROPRIETARY LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) NAYEE LLC
|
|
4
|
+
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
This software and associated documentation files (the "Software") are the
|
|
8
|
+
proprietary property of NAYEE LLC.
|
|
9
|
+
|
|
10
|
+
PERMITTED USES:
|
|
11
|
+
- You may use this Software in your applications and projects
|
|
12
|
+
- You may distribute this Software as part of your applications
|
|
13
|
+
|
|
14
|
+
PROHIBITED USES:
|
|
15
|
+
- You may NOT modify, adapt, or create derivative works of this Software
|
|
16
|
+
- You may NOT reverse engineer, decompile, or disassemble this Software
|
|
17
|
+
- You may NOT redistribute modified versions of this Software
|
|
18
|
+
- You may NOT remove or alter any copyright notices or proprietary markings
|
|
19
|
+
|
|
20
|
+
The Software is provided "AS IS", without warranty of any kind, express or
|
|
21
|
+
implied, including but not limited to the warranties of merchantability,
|
|
22
|
+
fitness for a particular purpose and noninfringement.
|
|
23
|
+
|
|
24
|
+
For licensing inquiries, please contact NAYEE LLC at https://nayee.net
|
|
25
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @countriesdb/widget-core
|
|
2
|
+
|
|
3
|
+
**Internal package** - Core TypeScript API client and utilities for CountriesDB widget and validation libraries. This package is intended for internal use by other CountriesDB packages only.
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Not for direct use** - If you're looking to use CountriesDB in your project, please use the public packages:
|
|
6
|
+
> - [`@countriesdb/widget`](../widget) - Plain JavaScript widget for country/subdivision selects
|
|
7
|
+
> - [`@countriesdb/react-widget`](../react-widget) - React components for CountriesDB
|
|
8
|
+
>
|
|
9
|
+
> 📖 **[Full Documentation](https://countriesdb.com/docs)** | 🌐 **[Website](https://countriesdb.com)**
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
This package contains shared TypeScript code, API client, type definitions, and utilities used internally by other CountriesDB packages. It is not intended for direct use by end users.
|
|
14
|
+
|
|
15
|
+
## For Package Maintainers
|
|
16
|
+
|
|
17
|
+
This package provides:
|
|
18
|
+
- TypeScript API client for CountriesDB REST API
|
|
19
|
+
- Type definitions for Country, Subdivision, and configuration types
|
|
20
|
+
- Utility functions for data processing and tree building
|
|
21
|
+
- Shared constants and helpers
|
|
22
|
+
|
|
23
|
+
The API and internal structure may change without notice. Use the public widget packages for stable, documented APIs.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
This package is automatically installed as a dependency of other CountriesDB packages. You should not install it directly.
|
|
28
|
+
|
|
29
|
+
## Resources
|
|
30
|
+
|
|
31
|
+
- 🌐 [Website](https://countriesdb.com) - Main CountriesDB website
|
|
32
|
+
- 📚 [Documentation](https://countriesdb.com/docs) - Complete API reference and guides
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
PROPRIETARY - Copyright (c) NAYEE LLC. See [LICENSE](LICENSE) for details.
|
|
37
|
+
|
|
38
|
+
**Developed by [NAYEE LLC](https://nayee.net)**
|
|
39
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for CountriesDB API
|
|
3
|
+
*/
|
|
4
|
+
import type { Country, Subdivision, ApiResponse, LanguageHeaders, FetchCountriesOptions, FetchSubdivisionsOptions } from './types';
|
|
5
|
+
export declare class CountriesDBClient {
|
|
6
|
+
/**
|
|
7
|
+
* Fetch countries from the API
|
|
8
|
+
*/
|
|
9
|
+
static fetchCountries(options: FetchCountriesOptions): Promise<ApiResponse<Country[]>>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetch subdivisions for a specific country
|
|
12
|
+
*/
|
|
13
|
+
static fetchSubdivisions(options: FetchSubdivisionsOptions): Promise<ApiResponse<Subdivision[]>>;
|
|
14
|
+
/**
|
|
15
|
+
* Generic API fetch method
|
|
16
|
+
*/
|
|
17
|
+
private static fetchFromApi;
|
|
18
|
+
/**
|
|
19
|
+
* Get language headers from browser and config
|
|
20
|
+
*/
|
|
21
|
+
static getLanguageHeaders(forcedLanguage?: string, defaultLanguage?: string): LanguageHeaders;
|
|
22
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API client for CountriesDB API
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CountriesDBClient = void 0;
|
|
7
|
+
class CountriesDBClient {
|
|
8
|
+
/**
|
|
9
|
+
* Fetch countries from the API
|
|
10
|
+
*/
|
|
11
|
+
static async fetchCountries(options) {
|
|
12
|
+
const { apiKey, backendUrl, shouldUseGeoIP = true, isoCountryNames = false, languageHeaders = {}, } = options;
|
|
13
|
+
const base = `${backendUrl}/api/countries`;
|
|
14
|
+
const params = [];
|
|
15
|
+
if (!shouldUseGeoIP) {
|
|
16
|
+
params.push('no_geoip=1');
|
|
17
|
+
}
|
|
18
|
+
if (isoCountryNames) {
|
|
19
|
+
params.push('country_name_source=iso');
|
|
20
|
+
}
|
|
21
|
+
const url = params.length ? `${base}?${params.join('&')}` : base;
|
|
22
|
+
return this.fetchFromApi(url, apiKey, languageHeaders);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetch subdivisions for a specific country
|
|
26
|
+
*/
|
|
27
|
+
static async fetchSubdivisions(options) {
|
|
28
|
+
const { apiKey, backendUrl, countryCode, shouldUseGeoIP = true, preferOfficial = false, subdivisionRomanizationPreference, preferLocalVariant = false, languageHeaders = {}, } = options;
|
|
29
|
+
if (!countryCode) {
|
|
30
|
+
return { data: [], language: null };
|
|
31
|
+
}
|
|
32
|
+
const base = `${backendUrl}/api/countries/${countryCode}/subdivisions`;
|
|
33
|
+
const params = [];
|
|
34
|
+
if (!shouldUseGeoIP) {
|
|
35
|
+
params.push('no_geoip=1');
|
|
36
|
+
}
|
|
37
|
+
if (subdivisionRomanizationPreference) {
|
|
38
|
+
params.push(`subdivision_romanization_preference=${encodeURIComponent(subdivisionRomanizationPreference)}`);
|
|
39
|
+
}
|
|
40
|
+
if (preferLocalVariant) {
|
|
41
|
+
params.push('prefer_local_variant=1');
|
|
42
|
+
}
|
|
43
|
+
if (preferOfficial) {
|
|
44
|
+
params.push('prefer_official=1');
|
|
45
|
+
}
|
|
46
|
+
const url = params.length ? `${base}?${params.join('&')}` : base;
|
|
47
|
+
return this.fetchFromApi(url, apiKey, languageHeaders);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generic API fetch method
|
|
51
|
+
*/
|
|
52
|
+
static async fetchFromApi(url, apiKey, languageHeaders) {
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
headers: {
|
|
56
|
+
...languageHeaders,
|
|
57
|
+
'X-API-KEY': apiKey,
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
let errorMessage = `HTTP Error: ${response.status}`;
|
|
63
|
+
try {
|
|
64
|
+
const errorData = await response.json();
|
|
65
|
+
if (errorData.error) {
|
|
66
|
+
errorMessage = errorData.error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Ignore JSON parse errors
|
|
71
|
+
}
|
|
72
|
+
throw new Error(errorMessage);
|
|
73
|
+
}
|
|
74
|
+
const data = (await response.json());
|
|
75
|
+
const language = response.headers.get('X-Selected-Language') || null;
|
|
76
|
+
return { data, language };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(`Failed to fetch data from ${url}:`, error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get language headers from browser and config
|
|
85
|
+
*/
|
|
86
|
+
static getLanguageHeaders(forcedLanguage, defaultLanguage) {
|
|
87
|
+
const headers = {};
|
|
88
|
+
if (forcedLanguage) {
|
|
89
|
+
headers['X-Forced-Language'] = forcedLanguage;
|
|
90
|
+
}
|
|
91
|
+
if (defaultLanguage) {
|
|
92
|
+
headers['X-Default-Language'] = defaultLanguage;
|
|
93
|
+
}
|
|
94
|
+
// Use browser's language preference
|
|
95
|
+
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
96
|
+
headers['Accept-Language'] = navigator.language;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
headers['Accept-Language'] = 'en';
|
|
100
|
+
}
|
|
101
|
+
return headers;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.CountriesDBClient = CountriesDBClient;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for CountriesDB API
|
|
3
|
+
*/
|
|
4
|
+
import type { Country, Subdivision, ApiResponse, LanguageHeaders, FetchCountriesOptions, FetchSubdivisionsOptions } from './types';
|
|
5
|
+
export declare class CountriesDBClient {
|
|
6
|
+
/**
|
|
7
|
+
* Fetch countries from the API
|
|
8
|
+
*/
|
|
9
|
+
static fetchCountries(options: FetchCountriesOptions): Promise<ApiResponse<Country[]>>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetch subdivisions for a specific country
|
|
12
|
+
*/
|
|
13
|
+
static fetchSubdivisions(options: FetchSubdivisionsOptions): Promise<ApiResponse<Subdivision[]>>;
|
|
14
|
+
/**
|
|
15
|
+
* Generic API fetch method
|
|
16
|
+
*/
|
|
17
|
+
private static fetchFromApi;
|
|
18
|
+
/**
|
|
19
|
+
* Get language headers from browser and config
|
|
20
|
+
*/
|
|
21
|
+
static getLanguageHeaders(forcedLanguage?: string, defaultLanguage?: string): LanguageHeaders;
|
|
22
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for CountriesDB API
|
|
3
|
+
*/
|
|
4
|
+
export class CountriesDBClient {
|
|
5
|
+
/**
|
|
6
|
+
* Fetch countries from the API
|
|
7
|
+
*/
|
|
8
|
+
static async fetchCountries(options) {
|
|
9
|
+
const { apiKey, backendUrl, shouldUseGeoIP = true, isoCountryNames = false, languageHeaders = {}, } = options;
|
|
10
|
+
const base = `${backendUrl}/api/countries`;
|
|
11
|
+
const params = [];
|
|
12
|
+
if (!shouldUseGeoIP) {
|
|
13
|
+
params.push('no_geoip=1');
|
|
14
|
+
}
|
|
15
|
+
if (isoCountryNames) {
|
|
16
|
+
params.push('country_name_source=iso');
|
|
17
|
+
}
|
|
18
|
+
const url = params.length ? `${base}?${params.join('&')}` : base;
|
|
19
|
+
return this.fetchFromApi(url, apiKey, languageHeaders);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fetch subdivisions for a specific country
|
|
23
|
+
*/
|
|
24
|
+
static async fetchSubdivisions(options) {
|
|
25
|
+
const { apiKey, backendUrl, countryCode, shouldUseGeoIP = true, preferOfficial = false, subdivisionRomanizationPreference, preferLocalVariant = false, languageHeaders = {}, } = options;
|
|
26
|
+
if (!countryCode) {
|
|
27
|
+
return { data: [], language: null };
|
|
28
|
+
}
|
|
29
|
+
const base = `${backendUrl}/api/countries/${countryCode}/subdivisions`;
|
|
30
|
+
const params = [];
|
|
31
|
+
if (!shouldUseGeoIP) {
|
|
32
|
+
params.push('no_geoip=1');
|
|
33
|
+
}
|
|
34
|
+
if (subdivisionRomanizationPreference) {
|
|
35
|
+
params.push(`subdivision_romanization_preference=${encodeURIComponent(subdivisionRomanizationPreference)}`);
|
|
36
|
+
}
|
|
37
|
+
if (preferLocalVariant) {
|
|
38
|
+
params.push('prefer_local_variant=1');
|
|
39
|
+
}
|
|
40
|
+
if (preferOfficial) {
|
|
41
|
+
params.push('prefer_official=1');
|
|
42
|
+
}
|
|
43
|
+
const url = params.length ? `${base}?${params.join('&')}` : base;
|
|
44
|
+
return this.fetchFromApi(url, apiKey, languageHeaders);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generic API fetch method
|
|
48
|
+
*/
|
|
49
|
+
static async fetchFromApi(url, apiKey, languageHeaders) {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(url, {
|
|
52
|
+
headers: {
|
|
53
|
+
...languageHeaders,
|
|
54
|
+
'X-API-KEY': apiKey,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
let errorMessage = `HTTP Error: ${response.status}`;
|
|
60
|
+
try {
|
|
61
|
+
const errorData = await response.json();
|
|
62
|
+
if (errorData.error) {
|
|
63
|
+
errorMessage = errorData.error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Ignore JSON parse errors
|
|
68
|
+
}
|
|
69
|
+
throw new Error(errorMessage);
|
|
70
|
+
}
|
|
71
|
+
const data = (await response.json());
|
|
72
|
+
const language = response.headers.get('X-Selected-Language') || null;
|
|
73
|
+
return { data, language };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(`Failed to fetch data from ${url}:`, error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get language headers from browser and config
|
|
82
|
+
*/
|
|
83
|
+
static getLanguageHeaders(forcedLanguage, defaultLanguage) {
|
|
84
|
+
const headers = {};
|
|
85
|
+
if (forcedLanguage) {
|
|
86
|
+
headers['X-Forced-Language'] = forcedLanguage;
|
|
87
|
+
}
|
|
88
|
+
if (defaultLanguage) {
|
|
89
|
+
headers['X-Default-Language'] = defaultLanguage;
|
|
90
|
+
}
|
|
91
|
+
// Use browser's language preference
|
|
92
|
+
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
93
|
+
headers['Accept-Language'] = navigator.language;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
headers['Accept-Language'] = 'en';
|
|
97
|
+
}
|
|
98
|
+
return headers;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name filtering utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { Country, Subdivision, CountryNameFilter, SubdivisionNameFilter } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Apply country name filter to a list of countries
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyCountryNameFilter(countries: Country[], filter: CountryNameFilter | undefined, language: string): Array<Country & {
|
|
9
|
+
_displayName: string;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Sort countries with Unicode-aware sorting
|
|
13
|
+
*/
|
|
14
|
+
export declare function sortCountries(countries: Array<Country & {
|
|
15
|
+
_displayName: string;
|
|
16
|
+
}>, language?: string): Array<Country & {
|
|
17
|
+
_displayName: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Apply subdivision name filter to a list of subdivisions
|
|
21
|
+
*/
|
|
22
|
+
export declare function applySubdivisionNameFilter(subdivisions: Subdivision[], filter: SubdivisionNameFilter | undefined, language: string, countryCode: string | null): Array<Subdivision & {
|
|
23
|
+
_displayName: string;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name filtering utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Apply country name filter to a list of countries
|
|
6
|
+
*/
|
|
7
|
+
export function applyCountryNameFilter(countries, filter, language) {
|
|
8
|
+
if (!filter || typeof filter !== 'function') {
|
|
9
|
+
return countries.map((c) => ({ ...c, _displayName: c.name }));
|
|
10
|
+
}
|
|
11
|
+
return countries
|
|
12
|
+
.map((country) => {
|
|
13
|
+
const filteredName = filter(country.iso_alpha_2, country.name, language, country);
|
|
14
|
+
if (filteredName === false) {
|
|
15
|
+
return null; // Skip this item
|
|
16
|
+
}
|
|
17
|
+
const displayName = filteredName !== null && filteredName !== undefined
|
|
18
|
+
? filteredName
|
|
19
|
+
: country.name;
|
|
20
|
+
return { ...country, _displayName: displayName };
|
|
21
|
+
})
|
|
22
|
+
.filter((c) => c !== null);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sort countries with Unicode-aware sorting
|
|
26
|
+
*/
|
|
27
|
+
export function sortCountries(countries, language) {
|
|
28
|
+
const locale = language || undefined;
|
|
29
|
+
return [...countries].sort((a, b) => {
|
|
30
|
+
if (locale) {
|
|
31
|
+
return a._displayName.localeCompare(b._displayName, locale, {
|
|
32
|
+
sensitivity: 'accent', // Case-insensitive, accent-sensitive
|
|
33
|
+
ignorePunctuation: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return a._displayName.localeCompare(b._displayName);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Apply subdivision name filter to a list of subdivisions
|
|
41
|
+
*/
|
|
42
|
+
export function applySubdivisionNameFilter(subdivisions, filter, language, countryCode) {
|
|
43
|
+
if (!filter || typeof filter !== 'function') {
|
|
44
|
+
return subdivisions.map((s) => ({
|
|
45
|
+
...s,
|
|
46
|
+
_displayName: s.full_name || s.name,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return subdivisions
|
|
50
|
+
.map((subdivision) => {
|
|
51
|
+
const originalName = subdivision.full_name || subdivision.name;
|
|
52
|
+
const filteredName = filter(subdivision.code, originalName, language, countryCode, subdivision);
|
|
53
|
+
if (filteredName === false) {
|
|
54
|
+
return null; // Skip this item
|
|
55
|
+
}
|
|
56
|
+
const displayName = filteredName !== null && filteredName !== undefined
|
|
57
|
+
? filteredName
|
|
58
|
+
: originalName;
|
|
59
|
+
return { ...subdivision, _displayName: displayName };
|
|
60
|
+
})
|
|
61
|
+
.filter((s) => s !== null);
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @countriesdb/widget-core
|
|
3
|
+
*
|
|
4
|
+
* Core package for CountriesDB widget functionality.
|
|
5
|
+
* Contains API client, types, and utilities shared across widget implementations.
|
|
6
|
+
*/
|
|
7
|
+
export { CountriesDBClient } from './api-client';
|
|
8
|
+
export { buildSubdivisionTree, flattenSubdivisionOptions, parseNestingPrefixes, } from './subdivision-tree';
|
|
9
|
+
export { applyCountryNameFilter, sortCountries, applySubdivisionNameFilter, } from './filters';
|
|
10
|
+
export type { Country, Subdivision, ApiResponse, LanguageHeaders, FetchCountriesOptions, FetchSubdivisionsOptions, CountryNameFilter, SubdivisionNameFilter, WidgetConfig, SubdivisionTreeNode, NestedPrefixes, } from './types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @countriesdb/widget-core
|
|
3
|
+
*
|
|
4
|
+
* Core package for CountriesDB widget functionality.
|
|
5
|
+
* Contains API client, types, and utilities shared across widget implementations.
|
|
6
|
+
*/
|
|
7
|
+
export { CountriesDBClient } from './api-client';
|
|
8
|
+
export { buildSubdivisionTree, flattenSubdivisionOptions, parseNestingPrefixes, } from './subdivision-tree';
|
|
9
|
+
export { applyCountryNameFilter, sortCountries, applySubdivisionNameFilter, } from './filters';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for building and working with subdivision trees
|
|
3
|
+
*/
|
|
4
|
+
import type { Subdivision, SubdivisionTreeNode, NestedPrefixes } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Build a tree structure from a flat list of subdivisions
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildSubdivisionTree(subdivisions: Subdivision[]): SubdivisionTreeNode[];
|
|
9
|
+
/**
|
|
10
|
+
* Flatten subdivision tree into HTML options
|
|
11
|
+
*/
|
|
12
|
+
export declare function flattenSubdivisionOptions(nodes: SubdivisionTreeNode[], level: number, prefixes: NestedPrefixes, labelKey: 'name' | 'full_name', language: string, allowParentSelection: boolean, subdivisionNameFilter?: (code: string, originalName: string, language: string, countryCode: string | null, item: Subdivision) => string | false | null | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Parse nesting prefixes from data attributes
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseNestingPrefixes(dataAttributes: Record<string, string | undefined>): NestedPrefixes;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for building and working with subdivision trees
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Build a tree structure from a flat list of subdivisions
|
|
6
|
+
*/
|
|
7
|
+
export function buildSubdivisionTree(subdivisions) {
|
|
8
|
+
const map = {};
|
|
9
|
+
const roots = [];
|
|
10
|
+
// Create map of all subdivisions
|
|
11
|
+
subdivisions.forEach((item) => {
|
|
12
|
+
map[item.id] = {
|
|
13
|
+
...item,
|
|
14
|
+
children: [],
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
// Build tree structure
|
|
18
|
+
subdivisions.forEach((item) => {
|
|
19
|
+
const node = map[item.id];
|
|
20
|
+
if (item.parent_id && map[item.parent_id]) {
|
|
21
|
+
map[item.parent_id].children.push(node);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
roots.push(node);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return roots;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Flatten subdivision tree into HTML options
|
|
31
|
+
*/
|
|
32
|
+
export function flattenSubdivisionOptions(nodes, level, prefixes, labelKey, language, allowParentSelection, subdivisionNameFilter) {
|
|
33
|
+
let html = '';
|
|
34
|
+
// Apply name filter and get display names
|
|
35
|
+
const nodesWithDisplayNames = nodes
|
|
36
|
+
.map((node) => {
|
|
37
|
+
let displayName = node[labelKey] || node.name;
|
|
38
|
+
const countryCode = node.code ? node.code.split('-')[0] : null;
|
|
39
|
+
// Apply subdivisionNameFilter if provided
|
|
40
|
+
if (subdivisionNameFilter && typeof subdivisionNameFilter === 'function') {
|
|
41
|
+
const filteredName = subdivisionNameFilter(node.code, displayName, language, countryCode, node);
|
|
42
|
+
if (filteredName === false) {
|
|
43
|
+
return null; // Mark for filtering - remove this item
|
|
44
|
+
}
|
|
45
|
+
if (filteredName !== null && filteredName !== undefined) {
|
|
46
|
+
displayName = filteredName;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { ...node, _displayName: displayName };
|
|
50
|
+
})
|
|
51
|
+
.filter((node) => node !== null);
|
|
52
|
+
// Sort: use Unicode-aware sorting if subdivisionNameFilter was used
|
|
53
|
+
const locale = subdivisionNameFilter && typeof subdivisionNameFilter === 'function' && language
|
|
54
|
+
? language
|
|
55
|
+
: undefined;
|
|
56
|
+
nodesWithDisplayNames.sort((a, b) => {
|
|
57
|
+
if (locale) {
|
|
58
|
+
return a._displayName.localeCompare(b._displayName, locale, {
|
|
59
|
+
sensitivity: 'accent', // Case-insensitive, accent-sensitive
|
|
60
|
+
ignorePunctuation: false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return a._displayName.localeCompare(b._displayName); // Default behavior
|
|
64
|
+
});
|
|
65
|
+
const prefix = level > 0 ? (prefixes[level] ?? ' '.repeat(level * 2)) : '';
|
|
66
|
+
const cssClass = `subdivision-level-${level}`;
|
|
67
|
+
for (const node of nodesWithDisplayNames) {
|
|
68
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
69
|
+
const displayName = node._displayName;
|
|
70
|
+
const label = `${prefix}${displayName}`;
|
|
71
|
+
if (hasChildren) {
|
|
72
|
+
if (allowParentSelection) {
|
|
73
|
+
html += `<option value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
html += `<option disabled value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
77
|
+
}
|
|
78
|
+
html += flattenSubdivisionOptions(node.children, level + 1, prefixes, labelKey, language, allowParentSelection, subdivisionNameFilter);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
html += `<option value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return html;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parse nesting prefixes from data attributes
|
|
88
|
+
*/
|
|
89
|
+
export function parseNestingPrefixes(dataAttributes) {
|
|
90
|
+
const prefixes = {};
|
|
91
|
+
for (let lvl = 1; lvl <= 10; lvl++) {
|
|
92
|
+
const key = `nested${lvl}Prefix`;
|
|
93
|
+
const value = dataAttributes[key];
|
|
94
|
+
if (value !== undefined) {
|
|
95
|
+
prefixes[lvl] = value;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return prefixes;
|
|
102
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for CountriesDB widget packages
|
|
3
|
+
*/
|
|
4
|
+
export interface Country {
|
|
5
|
+
iso_alpha_2: string;
|
|
6
|
+
iso_alpha_3?: string;
|
|
7
|
+
iso_numeric?: string;
|
|
8
|
+
name: string;
|
|
9
|
+
preselected?: boolean;
|
|
10
|
+
is_subdivision_of?: {
|
|
11
|
+
parent_country_code: string;
|
|
12
|
+
subdivision_code: string;
|
|
13
|
+
} | null;
|
|
14
|
+
related_country_code?: string | null;
|
|
15
|
+
related_subdivision_code?: string | null;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
export interface Subdivision {
|
|
19
|
+
id: number;
|
|
20
|
+
code: string;
|
|
21
|
+
name: string;
|
|
22
|
+
full_name?: string;
|
|
23
|
+
parent_id?: number | null;
|
|
24
|
+
preselected?: boolean;
|
|
25
|
+
is_subdivision_of?: {
|
|
26
|
+
parent_country_code: string;
|
|
27
|
+
subdivision_code: string;
|
|
28
|
+
} | null;
|
|
29
|
+
related_country_code?: string | null;
|
|
30
|
+
related_subdivision_code?: string | null;
|
|
31
|
+
children?: Subdivision[];
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}
|
|
34
|
+
export interface ApiResponse<T> {
|
|
35
|
+
data: T;
|
|
36
|
+
language: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface LanguageHeaders {
|
|
39
|
+
'Accept-Language'?: string;
|
|
40
|
+
'X-Forced-Language'?: string;
|
|
41
|
+
'X-Default-Language'?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface FetchCountriesOptions {
|
|
44
|
+
apiKey: string;
|
|
45
|
+
backendUrl: string;
|
|
46
|
+
shouldUseGeoIP?: boolean;
|
|
47
|
+
isoCountryNames?: boolean;
|
|
48
|
+
languageHeaders?: LanguageHeaders;
|
|
49
|
+
}
|
|
50
|
+
export interface FetchSubdivisionsOptions {
|
|
51
|
+
apiKey: string;
|
|
52
|
+
backendUrl: string;
|
|
53
|
+
countryCode: string;
|
|
54
|
+
shouldUseGeoIP?: boolean;
|
|
55
|
+
preferOfficial?: boolean;
|
|
56
|
+
subdivisionRomanizationPreference?: string;
|
|
57
|
+
preferLocalVariant?: boolean;
|
|
58
|
+
languageHeaders?: LanguageHeaders;
|
|
59
|
+
}
|
|
60
|
+
export type CountryNameFilter = (code: string, originalName: string, language: string, item: Country) => string | false | null | undefined;
|
|
61
|
+
export type SubdivisionNameFilter = (code: string, originalName: string, language: string, countryCode: string | null, item: Subdivision) => string | false | null | undefined;
|
|
62
|
+
export interface WidgetConfig {
|
|
63
|
+
publicKey: string;
|
|
64
|
+
backendUrl?: string;
|
|
65
|
+
defaultLanguage?: string;
|
|
66
|
+
forcedLanguage?: string;
|
|
67
|
+
showSubdivisionType?: boolean;
|
|
68
|
+
followRelated?: boolean;
|
|
69
|
+
followUpward?: boolean;
|
|
70
|
+
allowParentSelection?: boolean;
|
|
71
|
+
isoCountryNames?: boolean;
|
|
72
|
+
subdivisionRomanizationPreference?: string;
|
|
73
|
+
preferLocalVariant?: boolean;
|
|
74
|
+
countryNameFilter?: CountryNameFilter;
|
|
75
|
+
subdivisionNameFilter?: SubdivisionNameFilter;
|
|
76
|
+
autoInit?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface SubdivisionTreeNode {
|
|
79
|
+
id: number;
|
|
80
|
+
code: string;
|
|
81
|
+
name: string;
|
|
82
|
+
full_name?: string;
|
|
83
|
+
parent_id?: number | null;
|
|
84
|
+
children: SubdivisionTreeNode[];
|
|
85
|
+
[key: string]: any;
|
|
86
|
+
}
|
|
87
|
+
export interface NestedPrefixes {
|
|
88
|
+
[level: number]: string;
|
|
89
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name filtering utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { Country, Subdivision, CountryNameFilter, SubdivisionNameFilter } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Apply country name filter to a list of countries
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyCountryNameFilter(countries: Country[], filter: CountryNameFilter | undefined, language: string): Array<Country & {
|
|
9
|
+
_displayName: string;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Sort countries with Unicode-aware sorting
|
|
13
|
+
*/
|
|
14
|
+
export declare function sortCountries(countries: Array<Country & {
|
|
15
|
+
_displayName: string;
|
|
16
|
+
}>, language?: string): Array<Country & {
|
|
17
|
+
_displayName: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Apply subdivision name filter to a list of subdivisions
|
|
21
|
+
*/
|
|
22
|
+
export declare function applySubdivisionNameFilter(subdivisions: Subdivision[], filter: SubdivisionNameFilter | undefined, language: string, countryCode: string | null): Array<Subdivision & {
|
|
23
|
+
_displayName: string;
|
|
24
|
+
}>;
|
package/dist/filters.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Name filtering utilities
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyCountryNameFilter = applyCountryNameFilter;
|
|
7
|
+
exports.sortCountries = sortCountries;
|
|
8
|
+
exports.applySubdivisionNameFilter = applySubdivisionNameFilter;
|
|
9
|
+
/**
|
|
10
|
+
* Apply country name filter to a list of countries
|
|
11
|
+
*/
|
|
12
|
+
function applyCountryNameFilter(countries, filter, language) {
|
|
13
|
+
if (!filter || typeof filter !== 'function') {
|
|
14
|
+
return countries.map((c) => ({ ...c, _displayName: c.name }));
|
|
15
|
+
}
|
|
16
|
+
return countries
|
|
17
|
+
.map((country) => {
|
|
18
|
+
const filteredName = filter(country.iso_alpha_2, country.name, language, country);
|
|
19
|
+
if (filteredName === false) {
|
|
20
|
+
return null; // Skip this item
|
|
21
|
+
}
|
|
22
|
+
const displayName = filteredName !== null && filteredName !== undefined
|
|
23
|
+
? filteredName
|
|
24
|
+
: country.name;
|
|
25
|
+
return { ...country, _displayName: displayName };
|
|
26
|
+
})
|
|
27
|
+
.filter((c) => c !== null);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sort countries with Unicode-aware sorting
|
|
31
|
+
*/
|
|
32
|
+
function sortCountries(countries, language) {
|
|
33
|
+
const locale = language || undefined;
|
|
34
|
+
return [...countries].sort((a, b) => {
|
|
35
|
+
if (locale) {
|
|
36
|
+
return a._displayName.localeCompare(b._displayName, locale, {
|
|
37
|
+
sensitivity: 'accent', // Case-insensitive, accent-sensitive
|
|
38
|
+
ignorePunctuation: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return a._displayName.localeCompare(b._displayName);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Apply subdivision name filter to a list of subdivisions
|
|
46
|
+
*/
|
|
47
|
+
function applySubdivisionNameFilter(subdivisions, filter, language, countryCode) {
|
|
48
|
+
if (!filter || typeof filter !== 'function') {
|
|
49
|
+
return subdivisions.map((s) => ({
|
|
50
|
+
...s,
|
|
51
|
+
_displayName: s.full_name || s.name,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return subdivisions
|
|
55
|
+
.map((subdivision) => {
|
|
56
|
+
const originalName = subdivision.full_name || subdivision.name;
|
|
57
|
+
const filteredName = filter(subdivision.code, originalName, language, countryCode, subdivision);
|
|
58
|
+
if (filteredName === false) {
|
|
59
|
+
return null; // Skip this item
|
|
60
|
+
}
|
|
61
|
+
const displayName = filteredName !== null && filteredName !== undefined
|
|
62
|
+
? filteredName
|
|
63
|
+
: originalName;
|
|
64
|
+
return { ...subdivision, _displayName: displayName };
|
|
65
|
+
})
|
|
66
|
+
.filter((s) => s !== null);
|
|
67
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @countriesdb/widget-core
|
|
3
|
+
*
|
|
4
|
+
* Core package for CountriesDB widget functionality.
|
|
5
|
+
* Contains API client, types, and utilities shared across widget implementations.
|
|
6
|
+
*/
|
|
7
|
+
export { CountriesDBClient } from './api-client';
|
|
8
|
+
export { buildSubdivisionTree, flattenSubdivisionOptions, parseNestingPrefixes, } from './subdivision-tree';
|
|
9
|
+
export { applyCountryNameFilter, sortCountries, applySubdivisionNameFilter, } from './filters';
|
|
10
|
+
export type { Country, Subdivision, ApiResponse, LanguageHeaders, FetchCountriesOptions, FetchSubdivisionsOptions, CountryNameFilter, SubdivisionNameFilter, WidgetConfig, SubdivisionTreeNode, NestedPrefixes, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @countriesdb/widget-core
|
|
4
|
+
*
|
|
5
|
+
* Core package for CountriesDB widget functionality.
|
|
6
|
+
* Contains API client, types, and utilities shared across widget implementations.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.applySubdivisionNameFilter = exports.sortCountries = exports.applyCountryNameFilter = exports.parseNestingPrefixes = exports.flattenSubdivisionOptions = exports.buildSubdivisionTree = exports.CountriesDBClient = void 0;
|
|
10
|
+
var api_client_1 = require("./api-client");
|
|
11
|
+
Object.defineProperty(exports, "CountriesDBClient", { enumerable: true, get: function () { return api_client_1.CountriesDBClient; } });
|
|
12
|
+
var subdivision_tree_1 = require("./subdivision-tree");
|
|
13
|
+
Object.defineProperty(exports, "buildSubdivisionTree", { enumerable: true, get: function () { return subdivision_tree_1.buildSubdivisionTree; } });
|
|
14
|
+
Object.defineProperty(exports, "flattenSubdivisionOptions", { enumerable: true, get: function () { return subdivision_tree_1.flattenSubdivisionOptions; } });
|
|
15
|
+
Object.defineProperty(exports, "parseNestingPrefixes", { enumerable: true, get: function () { return subdivision_tree_1.parseNestingPrefixes; } });
|
|
16
|
+
var filters_1 = require("./filters");
|
|
17
|
+
Object.defineProperty(exports, "applyCountryNameFilter", { enumerable: true, get: function () { return filters_1.applyCountryNameFilter; } });
|
|
18
|
+
Object.defineProperty(exports, "sortCountries", { enumerable: true, get: function () { return filters_1.sortCountries; } });
|
|
19
|
+
Object.defineProperty(exports, "applySubdivisionNameFilter", { enumerable: true, get: function () { return filters_1.applySubdivisionNameFilter; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for building and working with subdivision trees
|
|
3
|
+
*/
|
|
4
|
+
import type { Subdivision, SubdivisionTreeNode, NestedPrefixes } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Build a tree structure from a flat list of subdivisions
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildSubdivisionTree(subdivisions: Subdivision[]): SubdivisionTreeNode[];
|
|
9
|
+
/**
|
|
10
|
+
* Flatten subdivision tree into HTML options
|
|
11
|
+
*/
|
|
12
|
+
export declare function flattenSubdivisionOptions(nodes: SubdivisionTreeNode[], level: number, prefixes: NestedPrefixes, labelKey: 'name' | 'full_name', language: string, allowParentSelection: boolean, subdivisionNameFilter?: (code: string, originalName: string, language: string, countryCode: string | null, item: Subdivision) => string | false | null | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Parse nesting prefixes from data attributes
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseNestingPrefixes(dataAttributes: Record<string, string | undefined>): NestedPrefixes;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utilities for building and working with subdivision trees
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildSubdivisionTree = buildSubdivisionTree;
|
|
7
|
+
exports.flattenSubdivisionOptions = flattenSubdivisionOptions;
|
|
8
|
+
exports.parseNestingPrefixes = parseNestingPrefixes;
|
|
9
|
+
/**
|
|
10
|
+
* Build a tree structure from a flat list of subdivisions
|
|
11
|
+
*/
|
|
12
|
+
function buildSubdivisionTree(subdivisions) {
|
|
13
|
+
const map = {};
|
|
14
|
+
const roots = [];
|
|
15
|
+
// Create map of all subdivisions
|
|
16
|
+
subdivisions.forEach((item) => {
|
|
17
|
+
map[item.id] = {
|
|
18
|
+
...item,
|
|
19
|
+
children: [],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
// Build tree structure
|
|
23
|
+
subdivisions.forEach((item) => {
|
|
24
|
+
const node = map[item.id];
|
|
25
|
+
if (item.parent_id && map[item.parent_id]) {
|
|
26
|
+
map[item.parent_id].children.push(node);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
roots.push(node);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return roots;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Flatten subdivision tree into HTML options
|
|
36
|
+
*/
|
|
37
|
+
function flattenSubdivisionOptions(nodes, level, prefixes, labelKey, language, allowParentSelection, subdivisionNameFilter) {
|
|
38
|
+
let html = '';
|
|
39
|
+
// Apply name filter and get display names
|
|
40
|
+
const nodesWithDisplayNames = nodes
|
|
41
|
+
.map((node) => {
|
|
42
|
+
let displayName = node[labelKey] || node.name;
|
|
43
|
+
const countryCode = node.code ? node.code.split('-')[0] : null;
|
|
44
|
+
// Apply subdivisionNameFilter if provided
|
|
45
|
+
if (subdivisionNameFilter && typeof subdivisionNameFilter === 'function') {
|
|
46
|
+
const filteredName = subdivisionNameFilter(node.code, displayName, language, countryCode, node);
|
|
47
|
+
if (filteredName === false) {
|
|
48
|
+
return null; // Mark for filtering - remove this item
|
|
49
|
+
}
|
|
50
|
+
if (filteredName !== null && filteredName !== undefined) {
|
|
51
|
+
displayName = filteredName;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { ...node, _displayName: displayName };
|
|
55
|
+
})
|
|
56
|
+
.filter((node) => node !== null);
|
|
57
|
+
// Sort: use Unicode-aware sorting if subdivisionNameFilter was used
|
|
58
|
+
const locale = subdivisionNameFilter && typeof subdivisionNameFilter === 'function' && language
|
|
59
|
+
? language
|
|
60
|
+
: undefined;
|
|
61
|
+
nodesWithDisplayNames.sort((a, b) => {
|
|
62
|
+
if (locale) {
|
|
63
|
+
return a._displayName.localeCompare(b._displayName, locale, {
|
|
64
|
+
sensitivity: 'accent', // Case-insensitive, accent-sensitive
|
|
65
|
+
ignorePunctuation: false,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return a._displayName.localeCompare(b._displayName); // Default behavior
|
|
69
|
+
});
|
|
70
|
+
const prefix = level > 0 ? (prefixes[level] ?? ' '.repeat(level * 2)) : '';
|
|
71
|
+
const cssClass = `subdivision-level-${level}`;
|
|
72
|
+
for (const node of nodesWithDisplayNames) {
|
|
73
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
74
|
+
const displayName = node._displayName;
|
|
75
|
+
const label = `${prefix}${displayName}`;
|
|
76
|
+
if (hasChildren) {
|
|
77
|
+
if (allowParentSelection) {
|
|
78
|
+
html += `<option value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
html += `<option disabled value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
82
|
+
}
|
|
83
|
+
html += flattenSubdivisionOptions(node.children, level + 1, prefixes, labelKey, language, allowParentSelection, subdivisionNameFilter);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
html += `<option value="${node.code}" class="${cssClass}">${label}</option>`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return html;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse nesting prefixes from data attributes
|
|
93
|
+
*/
|
|
94
|
+
function parseNestingPrefixes(dataAttributes) {
|
|
95
|
+
const prefixes = {};
|
|
96
|
+
for (let lvl = 1; lvl <= 10; lvl++) {
|
|
97
|
+
const key = `nested${lvl}Prefix`;
|
|
98
|
+
const value = dataAttributes[key];
|
|
99
|
+
if (value !== undefined) {
|
|
100
|
+
prefixes[lvl] = value;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return prefixes;
|
|
107
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for CountriesDB widget packages
|
|
3
|
+
*/
|
|
4
|
+
export interface Country {
|
|
5
|
+
iso_alpha_2: string;
|
|
6
|
+
iso_alpha_3?: string;
|
|
7
|
+
iso_numeric?: string;
|
|
8
|
+
name: string;
|
|
9
|
+
preselected?: boolean;
|
|
10
|
+
is_subdivision_of?: {
|
|
11
|
+
parent_country_code: string;
|
|
12
|
+
subdivision_code: string;
|
|
13
|
+
} | null;
|
|
14
|
+
related_country_code?: string | null;
|
|
15
|
+
related_subdivision_code?: string | null;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
export interface Subdivision {
|
|
19
|
+
id: number;
|
|
20
|
+
code: string;
|
|
21
|
+
name: string;
|
|
22
|
+
full_name?: string;
|
|
23
|
+
parent_id?: number | null;
|
|
24
|
+
preselected?: boolean;
|
|
25
|
+
is_subdivision_of?: {
|
|
26
|
+
parent_country_code: string;
|
|
27
|
+
subdivision_code: string;
|
|
28
|
+
} | null;
|
|
29
|
+
related_country_code?: string | null;
|
|
30
|
+
related_subdivision_code?: string | null;
|
|
31
|
+
children?: Subdivision[];
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}
|
|
34
|
+
export interface ApiResponse<T> {
|
|
35
|
+
data: T;
|
|
36
|
+
language: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface LanguageHeaders {
|
|
39
|
+
'Accept-Language'?: string;
|
|
40
|
+
'X-Forced-Language'?: string;
|
|
41
|
+
'X-Default-Language'?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface FetchCountriesOptions {
|
|
44
|
+
apiKey: string;
|
|
45
|
+
backendUrl: string;
|
|
46
|
+
shouldUseGeoIP?: boolean;
|
|
47
|
+
isoCountryNames?: boolean;
|
|
48
|
+
languageHeaders?: LanguageHeaders;
|
|
49
|
+
}
|
|
50
|
+
export interface FetchSubdivisionsOptions {
|
|
51
|
+
apiKey: string;
|
|
52
|
+
backendUrl: string;
|
|
53
|
+
countryCode: string;
|
|
54
|
+
shouldUseGeoIP?: boolean;
|
|
55
|
+
preferOfficial?: boolean;
|
|
56
|
+
subdivisionRomanizationPreference?: string;
|
|
57
|
+
preferLocalVariant?: boolean;
|
|
58
|
+
languageHeaders?: LanguageHeaders;
|
|
59
|
+
}
|
|
60
|
+
export type CountryNameFilter = (code: string, originalName: string, language: string, item: Country) => string | false | null | undefined;
|
|
61
|
+
export type SubdivisionNameFilter = (code: string, originalName: string, language: string, countryCode: string | null, item: Subdivision) => string | false | null | undefined;
|
|
62
|
+
export interface WidgetConfig {
|
|
63
|
+
publicKey: string;
|
|
64
|
+
backendUrl?: string;
|
|
65
|
+
defaultLanguage?: string;
|
|
66
|
+
forcedLanguage?: string;
|
|
67
|
+
showSubdivisionType?: boolean;
|
|
68
|
+
followRelated?: boolean;
|
|
69
|
+
followUpward?: boolean;
|
|
70
|
+
allowParentSelection?: boolean;
|
|
71
|
+
isoCountryNames?: boolean;
|
|
72
|
+
subdivisionRomanizationPreference?: string;
|
|
73
|
+
preferLocalVariant?: boolean;
|
|
74
|
+
countryNameFilter?: CountryNameFilter;
|
|
75
|
+
subdivisionNameFilter?: SubdivisionNameFilter;
|
|
76
|
+
autoInit?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface SubdivisionTreeNode {
|
|
79
|
+
id: number;
|
|
80
|
+
code: string;
|
|
81
|
+
name: string;
|
|
82
|
+
full_name?: string;
|
|
83
|
+
parent_id?: number | null;
|
|
84
|
+
children: SubdivisionTreeNode[];
|
|
85
|
+
[key: string]: any;
|
|
86
|
+
}
|
|
87
|
+
export interface NestedPrefixes {
|
|
88
|
+
[level: number]: string;
|
|
89
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@countriesdb/widget-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Internal package - Core TypeScript API client and utilities for CountriesDB widget and validation libraries. Not intended for direct use.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && tsc --module esnext --outDir dist/esm",
|
|
15
|
+
"clean": "rm -rf dist",
|
|
16
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"countries",
|
|
20
|
+
"subdivisions",
|
|
21
|
+
"widget",
|
|
22
|
+
"api",
|
|
23
|
+
"countriesdb",
|
|
24
|
+
"iso-3166",
|
|
25
|
+
"iso-3166-1",
|
|
26
|
+
"iso-3166-2",
|
|
27
|
+
"iso3166",
|
|
28
|
+
"iso3166-1",
|
|
29
|
+
"iso3166-2",
|
|
30
|
+
"api-client",
|
|
31
|
+
"countries-api",
|
|
32
|
+
"subdivisions-api",
|
|
33
|
+
"rest-api",
|
|
34
|
+
"country-data",
|
|
35
|
+
"subdivision-data",
|
|
36
|
+
"geography-data",
|
|
37
|
+
"typescript",
|
|
38
|
+
"types"
|
|
39
|
+
],
|
|
40
|
+
"author": {
|
|
41
|
+
"name": "NAYEE LLC",
|
|
42
|
+
"url": "https://nayee.net"
|
|
43
|
+
},
|
|
44
|
+
"license": "PROPRIETARY",
|
|
45
|
+
"homepage": "https://countriesdb.com",
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|