@marianmeres/stuic 3.107.0 → 3.108.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.
@@ -1,14 +1,14 @@
1
1
  <script lang="ts" module>
2
+ import type { Country } from "@marianmeres/countries";
2
3
  import type { Snippet } from "svelte";
3
4
  import type { HTMLAttributes } from "svelte/elements";
4
5
  import type { TranslateFn } from "../../types.js";
6
+ import type { Props as FieldCountryProps } from "../Input/FieldCountry.svelte";
7
+ import type { Props as FieldPhoneNumberProps } from "../Input/FieldPhoneNumber.svelte";
5
8
  import type {
6
9
  CheckoutAddressData,
7
10
  CheckoutValidationError,
8
11
  } from "./_internal/checkout-types.js";
9
- import type { Props as FieldPhoneNumberProps } from "../Input/FieldPhoneNumber.svelte";
10
- import type { Props as FieldCountryProps } from "../Input/FieldCountry.svelte";
11
- import type { Country } from "../Input/_internal/countries.js";
12
12
 
13
13
  export interface Props extends Omit<HTMLAttributes<HTMLFieldSetElement>, "children"> {
14
14
  /**
@@ -106,12 +106,12 @@
106
106
  scrollToFirstInvalidField,
107
107
  validateAllFields,
108
108
  } from "../../utils/validate-fields.js";
109
- import { t_default } from "./_internal/checkout-i18n-defaults.js";
110
- import { createEmptyAddress } from "./_internal/checkout-utils.js";
109
+ import FieldCountry from "../Input/FieldCountry.svelte";
111
110
  import FieldInput from "../Input/FieldInput.svelte";
112
111
  import FieldPhoneNumber from "../Input/FieldPhoneNumber.svelte";
113
- import FieldCountry from "../Input/FieldCountry.svelte";
114
112
  import { validatePhoneNumber } from "../Input/phone-validation.js";
113
+ import { t_default } from "./_internal/checkout-i18n-defaults.js";
114
+ import { createEmptyAddress } from "./_internal/checkout-utils.js";
115
115
 
116
116
  const DEFAULT_REQUIRED = ["name", "street", "city", "postal_code", "country"];
117
117
 
@@ -1,10 +1,10 @@
1
+ import type { Country } from "@marianmeres/countries";
1
2
  import type { Snippet } from "svelte";
2
3
  import type { HTMLAttributes } from "svelte/elements";
3
4
  import type { TranslateFn } from "../../types.js";
4
- import type { CheckoutAddressData, CheckoutValidationError } from "./_internal/checkout-types.js";
5
- import type { Props as FieldPhoneNumberProps } from "../Input/FieldPhoneNumber.svelte";
6
5
  import type { Props as FieldCountryProps } from "../Input/FieldCountry.svelte";
7
- import type { Country } from "../Input/_internal/countries.js";
6
+ import type { Props as FieldPhoneNumberProps } from "../Input/FieldPhoneNumber.svelte";
7
+ import type { CheckoutAddressData, CheckoutValidationError } from "./_internal/checkout-types.js";
8
8
  export interface Props extends Omit<HTMLAttributes<HTMLFieldSetElement>, "children"> {
9
9
  /**
10
10
  * Bindable address data. Default: createEmptyAddress().
@@ -1,9 +1,9 @@
1
1
  <script lang="ts" module>
2
+ import type { Country } from "@marianmeres/countries";
2
3
  import type { Snippet } from "svelte";
3
4
  import type { ValidateOptions } from "../../actions/validate.svelte.js";
4
5
  import type { TranslateFn } from "../../types.js";
5
6
  import type { THC } from "../Thc/Thc.svelte";
6
- import type { Country } from "./_internal/countries.js";
7
7
  import type { InputWrapClassProps } from "./types.js";
8
8
 
9
9
  type SnippetWithId = Snippet<[{ id: string }]>;
@@ -26,11 +26,33 @@
26
26
 
27
27
  /**
28
28
  * Override displayed country names. Keys are ISO alpha-2 codes,
29
- * values are the localized name. Missing keys fall back to the English
30
- * name from countries.ts.
29
+ * values are the localized name. Takes precedence over `locale`; missing
30
+ * keys fall back to the locale-resolved name (then the English name).
31
31
  */
32
32
  countryNames?: Record<string, string>;
33
33
 
34
+ /**
35
+ * Display country names in this locale (ISO 639-1 code, e.g. "sk").
36
+ * Resolved via @marianmeres/countries' built-in locales.
37
+ *
38
+ * Locales load lazily (code-split), so by default names render in
39
+ * English for one frame until the chunk arrives. To avoid that flash,
40
+ * make the locale synchronously available before this field renders —
41
+ * either register it once at app startup:
42
+ *
43
+ * ```ts
44
+ * import { registerLocale } from "@marianmeres/countries";
45
+ * import sk from "@marianmeres/countries/locales/sk";
46
+ * registerLocale("sk", sk);
47
+ * ```
48
+ *
49
+ * …or skip the `locale` prop entirely and pass the statically-imported
50
+ * map straight to `countryNames={sk}` (it's keyed by ISO code).
51
+ *
52
+ * Default: "en". Overridden per-country by `countryNames`.
53
+ */
54
+ locale?: string;
55
+
34
56
  /** Show country flag emoji in dropdown items. Default: true. */
35
57
  flags?: boolean;
36
58
 
@@ -68,21 +90,28 @@
68
90
  </script>
69
91
 
70
92
  <script lang="ts">
93
+ import {
94
+ COUNTRIES,
95
+ DEFAULT_LOCALE,
96
+ ISO_MAP,
97
+ getName,
98
+ hasLocale,
99
+ loadLocale,
100
+ } from "@marianmeres/countries";
71
101
  import { tick } from "svelte";
72
102
  import {
73
103
  validate as validateAction,
74
104
  type ValidationResult,
75
105
  } from "../../actions/validate.svelte.js";
106
+ import { iconChevronDown } from "../../icons/index.js";
76
107
  import { getId } from "../../utils/get-id.js";
77
108
  import { twMerge } from "../../utils/tw-merge.js";
78
- import { iconChevronDown } from "../../icons/index.js";
79
109
  import DropdownMenu, {
80
110
  type DropdownMenuActionItem,
81
111
  type DropdownMenuItem,
82
112
  type DropdownMenuSearchConfig,
83
113
  } from "../DropdownMenu/DropdownMenu.svelte";
84
114
  import InputWrap from "./_internal/InputWrap.svelte";
85
- import { COUNTRIES, ISO_MAP } from "./_internal/countries.js";
86
115
 
87
116
  let {
88
117
  value = $bindable(""),
@@ -90,6 +119,7 @@
90
119
  countryList: countryListProp,
91
120
  preferredCountries,
92
121
  countryNames,
122
+ locale = DEFAULT_LOCALE,
93
123
  flags = true,
94
124
  //
95
125
  name,
@@ -171,8 +201,32 @@
171
201
  });
172
202
  }
173
203
 
204
+ // Reactive marker: bumps once the requested locale is registered, so the
205
+ // derived names below recompute. The locale registry in @marianmeres/countries
206
+ // is plain module state, not a Svelte reactive source, hence this $state tick.
207
+ let localeLoaded = $state(DEFAULT_LOCALE);
208
+ $effect(() => {
209
+ const code = locale;
210
+ if (hasLocale(code)) {
211
+ localeLoaded = code;
212
+ return;
213
+ }
214
+ let cancelled = false;
215
+ loadLocale(code)
216
+ .then(() => {
217
+ if (!cancelled) localeLoaded = code;
218
+ })
219
+ .catch(() => {
220
+ if (!cancelled) localeLoaded = DEFAULT_LOCALE;
221
+ });
222
+ return () => {
223
+ cancelled = true;
224
+ };
225
+ });
226
+
174
227
  function localizedName(c: Country): string {
175
- return countryNames?.[c.iso] ?? c.name;
228
+ void localeLoaded; // establish reactive dependency on locale load
229
+ return countryNames?.[c.iso] ?? getName(c.iso, locale) ?? c.name;
176
230
  }
177
231
 
178
232
  // Resolve the working country list (accept ISO codes or Country objects).
@@ -180,9 +234,7 @@
180
234
  if (!countryListProp) return COUNTRIES;
181
235
  if (countryListProp.length === 0) return [];
182
236
  if (typeof countryListProp[0] === "string") {
183
- const set = new Set(
184
- (countryListProp as string[]).map((c) => c.toUpperCase())
185
- );
237
+ const set = new Set((countryListProp as string[]).map((c) => c.toUpperCase()));
186
238
  return COUNTRIES.filter((c) => set.has(c.iso));
187
239
  }
188
240
  return countryListProp as Country[];
@@ -223,9 +275,7 @@
223
275
 
224
276
  let items: DropdownMenuItem[] = $derived.by(() => {
225
277
  const result: DropdownMenuItem[] = [];
226
- const preferredSet = new Set(
227
- preferredCountries?.map((c) => c.toUpperCase()) ?? []
228
- );
278
+ const preferredSet = new Set(preferredCountries?.map((c) => c.toUpperCase()) ?? []);
229
279
 
230
280
  if (preferredSet.size > 0) {
231
281
  // Preserve the order given in `preferredCountries`.
@@ -260,8 +310,7 @@
260
310
  return `${localized} ${c.name} ${c.iso}`;
261
311
  },
262
312
  autoFocus: true,
263
- noResultsMessage:
264
- t?.("checkout.address.country_no_results") || "No country found",
313
+ noResultsMessage: t?.("checkout.address.country_no_results") || "No country found",
265
314
  });
266
315
 
267
316
  let triggerText = $derived.by(() => {
@@ -1,8 +1,8 @@
1
+ import type { Country } from "@marianmeres/countries";
1
2
  import type { Snippet } from "svelte";
2
3
  import type { ValidateOptions } from "../../actions/validate.svelte.js";
3
4
  import type { TranslateFn } from "../../types.js";
4
5
  import type { THC } from "../Thc/Thc.svelte";
5
- import type { Country } from "./_internal/countries.js";
6
6
  import type { InputWrapClassProps } from "./types.js";
7
7
  type SnippetWithId = Snippet<[{
8
8
  id: string;
@@ -21,10 +21,31 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
21
21
  preferredCountries?: string[];
22
22
  /**
23
23
  * Override displayed country names. Keys are ISO alpha-2 codes,
24
- * values are the localized name. Missing keys fall back to the English
25
- * name from countries.ts.
24
+ * values are the localized name. Takes precedence over `locale`; missing
25
+ * keys fall back to the locale-resolved name (then the English name).
26
26
  */
27
27
  countryNames?: Record<string, string>;
28
+ /**
29
+ * Display country names in this locale (ISO 639-1 code, e.g. "sk").
30
+ * Resolved via @marianmeres/countries' built-in locales.
31
+ *
32
+ * Locales load lazily (code-split), so by default names render in
33
+ * English for one frame until the chunk arrives. To avoid that flash,
34
+ * make the locale synchronously available before this field renders —
35
+ * either register it once at app startup:
36
+ *
37
+ * ```ts
38
+ * import { registerLocale } from "@marianmeres/countries";
39
+ * import sk from "@marianmeres/countries/locales/sk";
40
+ * registerLocale("sk", sk);
41
+ * ```
42
+ *
43
+ * …or skip the `locale` prop entirely and pass the statically-imported
44
+ * map straight to `countryNames={sk}` (it's keyed by ISO code).
45
+ *
46
+ * Default: "en". Overridden per-country by `countryNames`.
47
+ */
48
+ locale?: string;
28
49
  /** Show country flag emoji in dropdown items. Default: true. */
29
50
  flags?: boolean;
30
51
  /** Hidden input name (enables form submission + validation). */
@@ -59,6 +59,13 @@
59
59
  </script>
60
60
 
61
61
  <script lang="ts">
62
+ import {
63
+ COUNTRIES,
64
+ DIAL_CODES_DESC,
65
+ DIAL_CODE_MAP,
66
+ ISO_MAP,
67
+ type Country,
68
+ } from "@marianmeres/countries";
62
69
  import { tick, untrack } from "svelte";
63
70
  import {
64
71
  validate as validateAction,
@@ -68,13 +75,6 @@
68
75
  import { twMerge } from "../../utils/tw-merge.js";
69
76
  import InputWrap from "./_internal/InputWrap.svelte";
70
77
  import PhonePrefixPicker from "./_internal/PhonePrefixPicker.svelte";
71
- import {
72
- COUNTRIES,
73
- ISO_MAP,
74
- DIAL_CODES_DESC,
75
- DIAL_CODE_MAP,
76
- type Country,
77
- } from "./_internal/countries.js";
78
78
  import { validatePhoneNumber } from "./phone-validation.js";
79
79
 
80
80
  let {
@@ -173,7 +173,9 @@
173
173
 
174
174
  // Selected country object (initialize once from defaultCountry prop)
175
175
  let selectedCountry: Country | undefined = $state(
176
- untrack(() => (defaultCountry ? ISO_MAP.get(defaultCountry.toUpperCase()) : undefined))
176
+ untrack(() =>
177
+ defaultCountry ? ISO_MAP.get(defaultCountry.toUpperCase()) : undefined
178
+ )
177
179
  );
178
180
 
179
181
  // Internal local number (for controlled input)
@@ -1,12 +1,12 @@
1
1
  <script lang="ts">
2
- import { twMerge } from "../../../utils/tw-merge.js";
2
+ import { type Country, ISO_MAP } from "@marianmeres/countries";
3
3
  import { iconChevronDown } from "../../../icons/index.js";
4
+ import { twMerge } from "../../../utils/tw-merge.js";
4
5
  import DropdownMenu, {
5
6
  type DropdownMenuActionItem,
6
7
  type DropdownMenuItem,
7
8
  type DropdownMenuSearchConfig,
8
9
  } from "../../DropdownMenu/DropdownMenu.svelte";
9
- import { type Country, ISO_MAP } from "./countries.js";
10
10
 
11
11
  interface Props {
12
12
  selectedCountry?: Country;
@@ -1,4 +1,4 @@
1
- import { type Country } from "./countries.js";
1
+ import { type Country } from "@marianmeres/countries";
2
2
  interface Props {
3
3
  selectedCountry?: Country;
4
4
  countryList: Country[];
@@ -16,4 +16,3 @@ export { default as FieldObject, type Props as FieldObjectProps, } from "./Field
16
16
  export { default as FieldPhoneNumber, type Props as FieldPhoneNumberProps, } from "./FieldPhoneNumber.svelte";
17
17
  export { default as FieldCountry, type Props as FieldCountryProps, } from "./FieldCountry.svelte";
18
18
  export { validatePhoneNumber } from "./phone-validation.js";
19
- export { type Country, COUNTRIES, ISO_MAP } from "./_internal/countries.js";
@@ -16,4 +16,3 @@ export { default as FieldObject, } from "./FieldObject.svelte";
16
16
  export { default as FieldPhoneNumber, } from "./FieldPhoneNumber.svelte";
17
17
  export { default as FieldCountry, } from "./FieldCountry.svelte";
18
18
  export { validatePhoneNumber } from "./phone-validation.js";
19
- export { COUNTRIES, ISO_MAP } from "./_internal/countries.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.107.0",
3
+ "version": "3.108.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && pnpm run prepack",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@eslint/js": "^9.39.4",
56
- "@marianmeres/random-human-readable": "^1.9.0",
56
+ "@marianmeres/random-human-readable": "^1.10.2",
57
57
  "@sveltejs/adapter-auto": "^4.0.0",
58
58
  "@sveltejs/kit": "^2.61.1",
59
59
  "@sveltejs/package": "^2.5.7",
@@ -70,16 +70,17 @@
70
70
  "prettier-plugin-svelte": "^3.5.2",
71
71
  "publint": "^0.3.21",
72
72
  "svelte": "^5.56.0",
73
- "svelte-check": "^4.4.8",
73
+ "svelte-check": "^4.5.0",
74
74
  "tailwindcss": "^4.3.0",
75
- "tsx": "^4.22.3",
75
+ "tsx": "^4.22.4",
76
76
  "typescript": "^5.9.3",
77
77
  "typescript-eslint": "^8.60.0",
78
- "vite": "^7.3.3",
79
- "vitest": "^3.2.4"
78
+ "vite": "^7.3.5",
79
+ "vitest": "^3.2.6"
80
80
  },
81
81
  "dependencies": {
82
82
  "@marianmeres/clog": "^3.21.0",
83
+ "@marianmeres/countries": "^1.0.1",
83
84
  "@marianmeres/cron": "^2.0.1",
84
85
  "@marianmeres/design-tokens": "^1.8.0",
85
86
  "@marianmeres/icons-fns": "^5.0.0",
@@ -1,20 +0,0 @@
1
- export interface Country {
2
- /** ISO 3166-1 alpha-2 code, e.g. "SK" */
3
- iso: string;
4
- /** Country name in English, e.g. "Slovakia" */
5
- name: string;
6
- /** Dial code without leading +, e.g. "421" */
7
- dialCode: string;
8
- /** Country flag emoji, e.g. "🇸🇰" */
9
- flag: string;
10
- }
11
- export declare const COUNTRIES: Country[];
12
- /** Lookup map: ISO code -> Country */
13
- export declare const ISO_MAP: Map<string, Country>;
14
- /** Lookup map: dial code -> Country[]. Multiple countries can share a code (e.g. +1). */
15
- export declare const DIAL_CODE_MAP: Map<string, Country[]>;
16
- /**
17
- * Sorted list of unique dial codes (longest first) for prefix detection during paste.
18
- * Longest-first ensures we match "+1684" (American Samoa) before "+1" (US).
19
- */
20
- export declare const DIAL_CODES_DESC: string[];
@@ -1,252 +0,0 @@
1
- // prettier-ignore
2
- export const COUNTRIES = [
3
- { iso: "AF", name: "Afghanistan", dialCode: "93", flag: "🇦🇫" },
4
- { iso: "AL", name: "Albania", dialCode: "355", flag: "🇦🇱" },
5
- { iso: "DZ", name: "Algeria", dialCode: "213", flag: "🇩🇿" },
6
- { iso: "AS", name: "American Samoa", dialCode: "1684", flag: "🇦🇸" },
7
- { iso: "AD", name: "Andorra", dialCode: "376", flag: "🇦🇩" },
8
- { iso: "AO", name: "Angola", dialCode: "244", flag: "🇦🇴" },
9
- { iso: "AI", name: "Anguilla", dialCode: "1264", flag: "🇦🇮" },
10
- { iso: "AG", name: "Antigua and Barbuda", dialCode: "1268", flag: "🇦🇬" },
11
- { iso: "AR", name: "Argentina", dialCode: "54", flag: "🇦🇷" },
12
- { iso: "AM", name: "Armenia", dialCode: "374", flag: "🇦🇲" },
13
- { iso: "AW", name: "Aruba", dialCode: "297", flag: "🇦🇼" },
14
- { iso: "AU", name: "Australia", dialCode: "61", flag: "🇦🇺" },
15
- { iso: "AT", name: "Austria", dialCode: "43", flag: "🇦🇹" },
16
- { iso: "AZ", name: "Azerbaijan", dialCode: "994", flag: "🇦🇿" },
17
- { iso: "BS", name: "Bahamas", dialCode: "1242", flag: "🇧🇸" },
18
- { iso: "BH", name: "Bahrain", dialCode: "973", flag: "🇧🇭" },
19
- { iso: "BD", name: "Bangladesh", dialCode: "880", flag: "🇧🇩" },
20
- { iso: "BB", name: "Barbados", dialCode: "1246", flag: "🇧🇧" },
21
- { iso: "BY", name: "Belarus", dialCode: "375", flag: "🇧🇾" },
22
- { iso: "BE", name: "Belgium", dialCode: "32", flag: "🇧🇪" },
23
- { iso: "BZ", name: "Belize", dialCode: "501", flag: "🇧🇿" },
24
- { iso: "BJ", name: "Benin", dialCode: "229", flag: "🇧🇯" },
25
- { iso: "BM", name: "Bermuda", dialCode: "1441", flag: "🇧🇲" },
26
- { iso: "BT", name: "Bhutan", dialCode: "975", flag: "🇧🇹" },
27
- { iso: "BO", name: "Bolivia", dialCode: "591", flag: "🇧🇴" },
28
- { iso: "BA", name: "Bosnia and Herzegovina", dialCode: "387", flag: "🇧🇦" },
29
- { iso: "BW", name: "Botswana", dialCode: "267", flag: "🇧🇼" },
30
- { iso: "BR", name: "Brazil", dialCode: "55", flag: "🇧🇷" },
31
- { iso: "IO", name: "British Indian Ocean Territory", dialCode: "246", flag: "🇮🇴" },
32
- { iso: "VG", name: "British Virgin Islands", dialCode: "1284", flag: "🇻🇬" },
33
- { iso: "BN", name: "Brunei", dialCode: "673", flag: "🇧🇳" },
34
- { iso: "BG", name: "Bulgaria", dialCode: "359", flag: "🇧🇬" },
35
- { iso: "BF", name: "Burkina Faso", dialCode: "226", flag: "🇧🇫" },
36
- { iso: "BI", name: "Burundi", dialCode: "257", flag: "🇧🇮" },
37
- { iso: "KH", name: "Cambodia", dialCode: "855", flag: "🇰🇭" },
38
- { iso: "CM", name: "Cameroon", dialCode: "237", flag: "🇨🇲" },
39
- { iso: "CA", name: "Canada", dialCode: "1", flag: "🇨🇦" },
40
- { iso: "CV", name: "Cape Verde", dialCode: "238", flag: "🇨🇻" },
41
- { iso: "KY", name: "Cayman Islands", dialCode: "1345", flag: "🇰🇾" },
42
- { iso: "CF", name: "Central African Republic", dialCode: "236", flag: "🇨🇫" },
43
- { iso: "TD", name: "Chad", dialCode: "235", flag: "🇹🇩" },
44
- { iso: "CL", name: "Chile", dialCode: "56", flag: "🇨🇱" },
45
- { iso: "CN", name: "China", dialCode: "86", flag: "🇨🇳" },
46
- { iso: "CO", name: "Colombia", dialCode: "57", flag: "🇨🇴" },
47
- { iso: "KM", name: "Comoros", dialCode: "269", flag: "🇰🇲" },
48
- { iso: "CK", name: "Cook Islands", dialCode: "682", flag: "🇨🇰" },
49
- { iso: "CR", name: "Costa Rica", dialCode: "506", flag: "🇨🇷" },
50
- { iso: "HR", name: "Croatia", dialCode: "385", flag: "🇭🇷" },
51
- { iso: "CU", name: "Cuba", dialCode: "53", flag: "🇨🇺" },
52
- { iso: "CW", name: "Curaçao", dialCode: "599", flag: "🇨🇼" },
53
- { iso: "CY", name: "Cyprus", dialCode: "357", flag: "🇨🇾" },
54
- { iso: "CZ", name: "Czech Republic", dialCode: "420", flag: "🇨🇿" },
55
- { iso: "CD", name: "Democratic Republic of the Congo", dialCode: "243", flag: "🇨🇩" },
56
- { iso: "DK", name: "Denmark", dialCode: "45", flag: "🇩🇰" },
57
- { iso: "DJ", name: "Djibouti", dialCode: "253", flag: "🇩🇯" },
58
- { iso: "DM", name: "Dominica", dialCode: "1767", flag: "🇩🇲" },
59
- { iso: "DO", name: "Dominican Republic", dialCode: "1809", flag: "🇩🇴" },
60
- { iso: "EC", name: "Ecuador", dialCode: "593", flag: "🇪🇨" },
61
- { iso: "EG", name: "Egypt", dialCode: "20", flag: "🇪🇬" },
62
- { iso: "SV", name: "El Salvador", dialCode: "503", flag: "🇸🇻" },
63
- { iso: "GQ", name: "Equatorial Guinea", dialCode: "240", flag: "🇬🇶" },
64
- { iso: "ER", name: "Eritrea", dialCode: "291", flag: "🇪🇷" },
65
- { iso: "EE", name: "Estonia", dialCode: "372", flag: "🇪🇪" },
66
- { iso: "SZ", name: "Eswatini", dialCode: "268", flag: "🇸🇿" },
67
- { iso: "ET", name: "Ethiopia", dialCode: "251", flag: "🇪🇹" },
68
- { iso: "FK", name: "Falkland Islands", dialCode: "500", flag: "🇫🇰" },
69
- { iso: "FO", name: "Faroe Islands", dialCode: "298", flag: "🇫🇴" },
70
- { iso: "FJ", name: "Fiji", dialCode: "679", flag: "🇫🇯" },
71
- { iso: "FI", name: "Finland", dialCode: "358", flag: "🇫🇮" },
72
- { iso: "FR", name: "France", dialCode: "33", flag: "🇫🇷" },
73
- { iso: "GF", name: "French Guiana", dialCode: "594", flag: "🇬🇫" },
74
- { iso: "PF", name: "French Polynesia", dialCode: "689", flag: "🇵🇫" },
75
- { iso: "GA", name: "Gabon", dialCode: "241", flag: "🇬🇦" },
76
- { iso: "GM", name: "Gambia", dialCode: "220", flag: "🇬🇲" },
77
- { iso: "GE", name: "Georgia", dialCode: "995", flag: "🇬🇪" },
78
- { iso: "DE", name: "Germany", dialCode: "49", flag: "🇩🇪" },
79
- { iso: "GH", name: "Ghana", dialCode: "233", flag: "🇬🇭" },
80
- { iso: "GI", name: "Gibraltar", dialCode: "350", flag: "🇬🇮" },
81
- { iso: "GR", name: "Greece", dialCode: "30", flag: "🇬🇷" },
82
- { iso: "GL", name: "Greenland", dialCode: "299", flag: "🇬🇱" },
83
- { iso: "GD", name: "Grenada", dialCode: "1473", flag: "🇬🇩" },
84
- { iso: "GP", name: "Guadeloupe", dialCode: "590", flag: "🇬🇵" },
85
- { iso: "GU", name: "Guam", dialCode: "1671", flag: "🇬🇺" },
86
- { iso: "GT", name: "Guatemala", dialCode: "502", flag: "🇬🇹" },
87
- { iso: "GG", name: "Guernsey", dialCode: "44", flag: "🇬🇬" },
88
- { iso: "GN", name: "Guinea", dialCode: "224", flag: "🇬🇳" },
89
- { iso: "GW", name: "Guinea-Bissau", dialCode: "245", flag: "🇬🇼" },
90
- { iso: "GY", name: "Guyana", dialCode: "592", flag: "🇬🇾" },
91
- { iso: "HT", name: "Haiti", dialCode: "509", flag: "🇭🇹" },
92
- { iso: "HN", name: "Honduras", dialCode: "504", flag: "🇭🇳" },
93
- { iso: "HK", name: "Hong Kong", dialCode: "852", flag: "🇭🇰" },
94
- { iso: "HU", name: "Hungary", dialCode: "36", flag: "🇭🇺" },
95
- { iso: "IS", name: "Iceland", dialCode: "354", flag: "🇮🇸" },
96
- { iso: "IN", name: "India", dialCode: "91", flag: "🇮🇳" },
97
- { iso: "ID", name: "Indonesia", dialCode: "62", flag: "🇮🇩" },
98
- { iso: "IR", name: "Iran", dialCode: "98", flag: "🇮🇷" },
99
- { iso: "IQ", name: "Iraq", dialCode: "964", flag: "🇮🇶" },
100
- { iso: "IE", name: "Ireland", dialCode: "353", flag: "🇮🇪" },
101
- { iso: "IM", name: "Isle of Man", dialCode: "44", flag: "🇮🇲" },
102
- { iso: "IL", name: "Israel", dialCode: "972", flag: "🇮🇱" },
103
- { iso: "IT", name: "Italy", dialCode: "39", flag: "🇮🇹" },
104
- { iso: "CI", name: "Ivory Coast", dialCode: "225", flag: "🇨🇮" },
105
- { iso: "JM", name: "Jamaica", dialCode: "1876", flag: "🇯🇲" },
106
- { iso: "JP", name: "Japan", dialCode: "81", flag: "🇯🇵" },
107
- { iso: "JE", name: "Jersey", dialCode: "44", flag: "🇯🇪" },
108
- { iso: "JO", name: "Jordan", dialCode: "962", flag: "🇯🇴" },
109
- { iso: "KZ", name: "Kazakhstan", dialCode: "7", flag: "🇰🇿" },
110
- { iso: "KE", name: "Kenya", dialCode: "254", flag: "🇰🇪" },
111
- { iso: "KI", name: "Kiribati", dialCode: "686", flag: "🇰🇮" },
112
- { iso: "XK", name: "Kosovo", dialCode: "383", flag: "🇽🇰" },
113
- { iso: "KW", name: "Kuwait", dialCode: "965", flag: "🇰🇼" },
114
- { iso: "KG", name: "Kyrgyzstan", dialCode: "996", flag: "🇰🇬" },
115
- { iso: "LA", name: "Laos", dialCode: "856", flag: "🇱🇦" },
116
- { iso: "LV", name: "Latvia", dialCode: "371", flag: "🇱🇻" },
117
- { iso: "LB", name: "Lebanon", dialCode: "961", flag: "🇱🇧" },
118
- { iso: "LS", name: "Lesotho", dialCode: "266", flag: "🇱🇸" },
119
- { iso: "LR", name: "Liberia", dialCode: "231", flag: "🇱🇷" },
120
- { iso: "LY", name: "Libya", dialCode: "218", flag: "🇱🇾" },
121
- { iso: "LI", name: "Liechtenstein", dialCode: "423", flag: "🇱🇮" },
122
- { iso: "LT", name: "Lithuania", dialCode: "370", flag: "🇱🇹" },
123
- { iso: "LU", name: "Luxembourg", dialCode: "352", flag: "🇱🇺" },
124
- { iso: "MO", name: "Macau", dialCode: "853", flag: "🇲🇴" },
125
- { iso: "MG", name: "Madagascar", dialCode: "261", flag: "🇲🇬" },
126
- { iso: "MW", name: "Malawi", dialCode: "265", flag: "🇲🇼" },
127
- { iso: "MY", name: "Malaysia", dialCode: "60", flag: "🇲🇾" },
128
- { iso: "MV", name: "Maldives", dialCode: "960", flag: "🇲🇻" },
129
- { iso: "ML", name: "Mali", dialCode: "223", flag: "🇲🇱" },
130
- { iso: "MT", name: "Malta", dialCode: "356", flag: "🇲🇹" },
131
- { iso: "MH", name: "Marshall Islands", dialCode: "692", flag: "🇲🇭" },
132
- { iso: "MQ", name: "Martinique", dialCode: "596", flag: "🇲🇶" },
133
- { iso: "MR", name: "Mauritania", dialCode: "222", flag: "🇲🇷" },
134
- { iso: "MU", name: "Mauritius", dialCode: "230", flag: "🇲🇺" },
135
- { iso: "YT", name: "Mayotte", dialCode: "262", flag: "🇾🇹" },
136
- { iso: "MX", name: "Mexico", dialCode: "52", flag: "🇲🇽" },
137
- { iso: "FM", name: "Micronesia", dialCode: "691", flag: "🇫🇲" },
138
- { iso: "MD", name: "Moldova", dialCode: "373", flag: "🇲🇩" },
139
- { iso: "MC", name: "Monaco", dialCode: "377", flag: "🇲🇨" },
140
- { iso: "MN", name: "Mongolia", dialCode: "976", flag: "🇲🇳" },
141
- { iso: "ME", name: "Montenegro", dialCode: "382", flag: "🇲🇪" },
142
- { iso: "MS", name: "Montserrat", dialCode: "1664", flag: "🇲🇸" },
143
- { iso: "MA", name: "Morocco", dialCode: "212", flag: "🇲🇦" },
144
- { iso: "MZ", name: "Mozambique", dialCode: "258", flag: "🇲🇿" },
145
- { iso: "MM", name: "Myanmar", dialCode: "95", flag: "🇲🇲" },
146
- { iso: "NA", name: "Namibia", dialCode: "264", flag: "🇳🇦" },
147
- { iso: "NR", name: "Nauru", dialCode: "674", flag: "🇳🇷" },
148
- { iso: "NP", name: "Nepal", dialCode: "977", flag: "🇳🇵" },
149
- { iso: "NL", name: "Netherlands", dialCode: "31", flag: "🇳🇱" },
150
- { iso: "NC", name: "New Caledonia", dialCode: "687", flag: "🇳🇨" },
151
- { iso: "NZ", name: "New Zealand", dialCode: "64", flag: "🇳🇿" },
152
- { iso: "NI", name: "Nicaragua", dialCode: "505", flag: "🇳🇮" },
153
- { iso: "NE", name: "Niger", dialCode: "227", flag: "🇳🇪" },
154
- { iso: "NG", name: "Nigeria", dialCode: "234", flag: "🇳🇬" },
155
- { iso: "NU", name: "Niue", dialCode: "683", flag: "🇳🇺" },
156
- { iso: "NF", name: "Norfolk Island", dialCode: "672", flag: "🇳🇫" },
157
- { iso: "KP", name: "North Korea", dialCode: "850", flag: "🇰🇵" },
158
- { iso: "MK", name: "North Macedonia", dialCode: "389", flag: "🇲🇰" },
159
- { iso: "MP", name: "Northern Mariana Islands", dialCode: "1670", flag: "🇲🇵" },
160
- { iso: "NO", name: "Norway", dialCode: "47", flag: "🇳🇴" },
161
- { iso: "OM", name: "Oman", dialCode: "968", flag: "🇴🇲" },
162
- { iso: "PK", name: "Pakistan", dialCode: "92", flag: "🇵🇰" },
163
- { iso: "PW", name: "Palau", dialCode: "680", flag: "🇵🇼" },
164
- { iso: "PS", name: "Palestine", dialCode: "970", flag: "🇵🇸" },
165
- { iso: "PA", name: "Panama", dialCode: "507", flag: "🇵🇦" },
166
- { iso: "PG", name: "Papua New Guinea", dialCode: "675", flag: "🇵🇬" },
167
- { iso: "PY", name: "Paraguay", dialCode: "595", flag: "🇵🇾" },
168
- { iso: "PE", name: "Peru", dialCode: "51", flag: "🇵🇪" },
169
- { iso: "PH", name: "Philippines", dialCode: "63", flag: "🇵🇭" },
170
- { iso: "PL", name: "Poland", dialCode: "48", flag: "🇵🇱" },
171
- { iso: "PT", name: "Portugal", dialCode: "351", flag: "🇵🇹" },
172
- { iso: "PR", name: "Puerto Rico", dialCode: "1787", flag: "🇵🇷" },
173
- { iso: "QA", name: "Qatar", dialCode: "974", flag: "🇶🇦" },
174
- { iso: "CG", name: "Republic of the Congo", dialCode: "242", flag: "🇨🇬" },
175
- { iso: "RE", name: "Réunion", dialCode: "262", flag: "🇷🇪" },
176
- { iso: "RO", name: "Romania", dialCode: "40", flag: "🇷🇴" },
177
- { iso: "RU", name: "Russia", dialCode: "7", flag: "🇷🇺" },
178
- { iso: "RW", name: "Rwanda", dialCode: "250", flag: "🇷🇼" },
179
- { iso: "KN", name: "Saint Kitts and Nevis", dialCode: "1869", flag: "🇰🇳" },
180
- { iso: "LC", name: "Saint Lucia", dialCode: "1758", flag: "🇱🇨" },
181
- { iso: "PM", name: "Saint Pierre and Miquelon", dialCode: "508", flag: "🇵🇲" },
182
- { iso: "VC", name: "Saint Vincent and the Grenadines", dialCode: "1784", flag: "🇻🇨" },
183
- { iso: "WS", name: "Samoa", dialCode: "685", flag: "🇼🇸" },
184
- { iso: "SM", name: "San Marino", dialCode: "378", flag: "🇸🇲" },
185
- { iso: "ST", name: "São Tomé and Príncipe", dialCode: "239", flag: "🇸🇹" },
186
- { iso: "SA", name: "Saudi Arabia", dialCode: "966", flag: "🇸🇦" },
187
- { iso: "SN", name: "Senegal", dialCode: "221", flag: "🇸🇳" },
188
- { iso: "RS", name: "Serbia", dialCode: "381", flag: "🇷🇸" },
189
- { iso: "SC", name: "Seychelles", dialCode: "248", flag: "🇸🇨" },
190
- { iso: "SL", name: "Sierra Leone", dialCode: "232", flag: "🇸🇱" },
191
- { iso: "SG", name: "Singapore", dialCode: "65", flag: "🇸🇬" },
192
- { iso: "SX", name: "Sint Maarten", dialCode: "1721", flag: "🇸🇽" },
193
- { iso: "SK", name: "Slovakia", dialCode: "421", flag: "🇸🇰" },
194
- { iso: "SI", name: "Slovenia", dialCode: "386", flag: "🇸🇮" },
195
- { iso: "SB", name: "Solomon Islands", dialCode: "677", flag: "🇸🇧" },
196
- { iso: "SO", name: "Somalia", dialCode: "252", flag: "🇸🇴" },
197
- { iso: "ZA", name: "South Africa", dialCode: "27", flag: "🇿🇦" },
198
- { iso: "KR", name: "South Korea", dialCode: "82", flag: "🇰🇷" },
199
- { iso: "SS", name: "South Sudan", dialCode: "211", flag: "🇸🇸" },
200
- { iso: "ES", name: "Spain", dialCode: "34", flag: "🇪🇸" },
201
- { iso: "LK", name: "Sri Lanka", dialCode: "94", flag: "🇱🇰" },
202
- { iso: "SD", name: "Sudan", dialCode: "249", flag: "🇸🇩" },
203
- { iso: "SR", name: "Suriname", dialCode: "597", flag: "🇸🇷" },
204
- { iso: "SE", name: "Sweden", dialCode: "46", flag: "🇸🇪" },
205
- { iso: "CH", name: "Switzerland", dialCode: "41", flag: "🇨🇭" },
206
- { iso: "SY", name: "Syria", dialCode: "963", flag: "🇸🇾" },
207
- { iso: "TW", name: "Taiwan", dialCode: "886", flag: "🇹🇼" },
208
- { iso: "TJ", name: "Tajikistan", dialCode: "992", flag: "🇹🇯" },
209
- { iso: "TZ", name: "Tanzania", dialCode: "255", flag: "🇹🇿" },
210
- { iso: "TH", name: "Thailand", dialCode: "66", flag: "🇹🇭" },
211
- { iso: "TL", name: "Timor-Leste", dialCode: "670", flag: "🇹🇱" },
212
- { iso: "TG", name: "Togo", dialCode: "228", flag: "🇹🇬" },
213
- { iso: "TK", name: "Tokelau", dialCode: "690", flag: "🇹🇰" },
214
- { iso: "TO", name: "Tonga", dialCode: "676", flag: "🇹🇴" },
215
- { iso: "TT", name: "Trinidad and Tobago", dialCode: "1868", flag: "🇹🇹" },
216
- { iso: "TN", name: "Tunisia", dialCode: "216", flag: "🇹🇳" },
217
- { iso: "TR", name: "Turkey", dialCode: "90", flag: "🇹🇷" },
218
- { iso: "TM", name: "Turkmenistan", dialCode: "993", flag: "🇹🇲" },
219
- { iso: "TC", name: "Turks and Caicos Islands", dialCode: "1649", flag: "🇹🇨" },
220
- { iso: "TV", name: "Tuvalu", dialCode: "688", flag: "🇹🇻" },
221
- { iso: "VI", name: "U.S. Virgin Islands", dialCode: "1340", flag: "🇻🇮" },
222
- { iso: "UG", name: "Uganda", dialCode: "256", flag: "🇺🇬" },
223
- { iso: "UA", name: "Ukraine", dialCode: "380", flag: "🇺🇦" },
224
- { iso: "AE", name: "United Arab Emirates", dialCode: "971", flag: "🇦🇪" },
225
- { iso: "GB", name: "United Kingdom", dialCode: "44", flag: "🇬🇧" },
226
- { iso: "US", name: "United States", dialCode: "1", flag: "🇺🇸" },
227
- { iso: "UY", name: "Uruguay", dialCode: "598", flag: "🇺🇾" },
228
- { iso: "UZ", name: "Uzbekistan", dialCode: "998", flag: "🇺🇿" },
229
- { iso: "VU", name: "Vanuatu", dialCode: "678", flag: "🇻🇺" },
230
- { iso: "VA", name: "Vatican City", dialCode: "379", flag: "🇻🇦" },
231
- { iso: "VE", name: "Venezuela", dialCode: "58", flag: "🇻🇪" },
232
- { iso: "VN", name: "Vietnam", dialCode: "84", flag: "🇻🇳" },
233
- { iso: "WF", name: "Wallis and Futuna", dialCode: "681", flag: "🇼🇫" },
234
- { iso: "YE", name: "Yemen", dialCode: "967", flag: "🇾🇪" },
235
- { iso: "ZM", name: "Zambia", dialCode: "260", flag: "🇿🇲" },
236
- { iso: "ZW", name: "Zimbabwe", dialCode: "263", flag: "🇿🇼" },
237
- ];
238
- /** Lookup map: ISO code -> Country */
239
- export const ISO_MAP = new Map();
240
- COUNTRIES.forEach((c) => ISO_MAP.set(c.iso, c));
241
- /** Lookup map: dial code -> Country[]. Multiple countries can share a code (e.g. +1). */
242
- export const DIAL_CODE_MAP = new Map();
243
- COUNTRIES.forEach((c) => {
244
- const arr = DIAL_CODE_MAP.get(c.dialCode) || [];
245
- arr.push(c);
246
- DIAL_CODE_MAP.set(c.dialCode, arr);
247
- });
248
- /**
249
- * Sorted list of unique dial codes (longest first) for prefix detection during paste.
250
- * Longest-first ensures we match "+1684" (American Samoa) before "+1" (US).
251
- */
252
- export const DIAL_CODES_DESC = [...new Set(COUNTRIES.map((c) => c.dialCode))].sort((a, b) => b.length - a.length);