@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 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] ?? '&nbsp;'.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,4 @@
1
+ /**
2
+ * Core types for CountriesDB widget packages
3
+ */
4
+ export {};
@@ -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,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
+ }
@@ -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] ?? '&nbsp;'.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
+ }
@@ -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
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Core types for CountriesDB widget packages
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+