@open-mercato/ui 0.6.7-develop.6660.1.90e1e2eef6 โ†’ 0.6.7-develop.6669.1.40b669666b

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.
@@ -30,35 +30,316 @@ export type PhoneCountry = {
30
30
  }
31
31
 
32
32
  /**
33
- * Static country list โ€” focused on EU + Americas markets where Open Mercato
34
- * currently operates. Extend as needed; longest dial codes MUST appear before
35
- * their shorter ancestors (e.g. `+380` before `+38`) to keep prefix matching
36
- * deterministic. The default fallback is the first entry.
33
+ * Emoji flag for an ISO 3166-1 alpha-2 code, assembled from the two regional
34
+ * indicator symbols. Deriving it keeps the (large) dictionary free of hand-typed
35
+ * emoji and guarantees the flag always matches the code.
37
36
  */
38
- export const PHONE_COUNTRIES: PhoneCountry[] = [
39
- { iso2: 'US', dialCode: '+1', label: 'United States', flag: '๐Ÿ‡บ๐Ÿ‡ธ' },
40
- { iso2: 'CA', dialCode: '+1', label: 'Canada', flag: '๐Ÿ‡จ๐Ÿ‡ฆ' },
41
- { iso2: 'GB', dialCode: '+44', label: 'United Kingdom', flag: '๐Ÿ‡ฌ๐Ÿ‡ง' },
42
- { iso2: 'PL', dialCode: '+48', label: 'Poland', flag: '๐Ÿ‡ต๐Ÿ‡ฑ' },
43
- { iso2: 'DE', dialCode: '+49', label: 'Germany', flag: '๐Ÿ‡ฉ๐Ÿ‡ช' },
44
- { iso2: 'FR', dialCode: '+33', label: 'France', flag: '๐Ÿ‡ซ๐Ÿ‡ท' },
45
- { iso2: 'ES', dialCode: '+34', label: 'Spain', flag: '๐Ÿ‡ช๐Ÿ‡ธ' },
46
- { iso2: 'IT', dialCode: '+39', label: 'Italy', flag: '๐Ÿ‡ฎ๐Ÿ‡น' },
47
- { iso2: 'NL', dialCode: '+31', label: 'Netherlands', flag: '๐Ÿ‡ณ๐Ÿ‡ฑ' },
48
- { iso2: 'SE', dialCode: '+46', label: 'Sweden', flag: '๐Ÿ‡ธ๐Ÿ‡ช' },
49
- { iso2: 'AT', dialCode: '+43', label: 'Austria', flag: '๐Ÿ‡ฆ๐Ÿ‡น' },
50
- { iso2: 'CH', dialCode: '+41', label: 'Switzerland', flag: '๐Ÿ‡จ๐Ÿ‡ญ' },
51
- { iso2: 'PT', dialCode: '+351', label: 'Portugal', flag: '๐Ÿ‡ต๐Ÿ‡น' },
52
- { iso2: 'CZ', dialCode: '+420', label: 'Czechia', flag: '๐Ÿ‡จ๐Ÿ‡ฟ' },
53
- { iso2: 'RO', dialCode: '+40', label: 'Romania', flag: '๐Ÿ‡ท๐Ÿ‡ด' },
54
- { iso2: 'UA', dialCode: '+380', label: 'Ukraine', flag: '๐Ÿ‡บ๐Ÿ‡ฆ' },
37
+ function iso2ToFlagEmoji(iso2: string): string {
38
+ const letters = iso2.toUpperCase().replace(/[^A-Z]/g, '')
39
+ if (letters.length !== 2) return ''
40
+ const base = 0x1f1e6
41
+ return String.fromCodePoint(
42
+ base + letters.charCodeAt(0) - 65,
43
+ base + letters.charCodeAt(1) - 65,
44
+ )
45
+ }
46
+
47
+ /**
48
+ * Complete static dictionary of geographic countries and territories that have
49
+ * an ISO 3166-1 alpha-2 code and an international calling code, based on the
50
+ * ITU / Wikipedia list of telephone country codes.
51
+ *
52
+ * Rules baked into this data:
53
+ * - Labels are English; flags are derived from the ISO code (`iso2ToFlagEmoji`).
54
+ * - Non-geographic / international service codes (`+800`, `+808`, `+870`,
55
+ * `+881`, `+882`, โ€ฆ) are intentionally excluded.
56
+ * - North American Numbering Plan territories carry their full `+1<NPA>` dial
57
+ * code (e.g. Bahamas `+1242`) so longest-prefix matching resolves them before
58
+ * the generic `+1`. The US and Canada BOTH use a bare `+1` and cannot be told
59
+ * apart from the prefix alone โ€” auto-detection favours the US.
60
+ * - Several sovereign countries share a calling code with dependent territories
61
+ * (e.g. `+44` UK/Guernsey/Jersey/Isle of Man); `PRIMARY_DIAL_OWNERS` records
62
+ * the country that wins auto-detection for those shared codes.
63
+ */
64
+ const RAW_PHONE_COUNTRIES: Array<Omit<PhoneCountry, 'flag'>> = [
65
+ { iso2: 'US', dialCode: '+1', label: 'United States' },
66
+ { iso2: 'CA', dialCode: '+1', label: 'Canada' },
67
+ { iso2: 'AG', dialCode: '+1268', label: 'Antigua and Barbuda' },
68
+ { iso2: 'AI', dialCode: '+1264', label: 'Anguilla' },
69
+ { iso2: 'AS', dialCode: '+1684', label: 'American Samoa' },
70
+ { iso2: 'BB', dialCode: '+1246', label: 'Barbados' },
71
+ { iso2: 'BM', dialCode: '+1441', label: 'Bermuda' },
72
+ { iso2: 'BS', dialCode: '+1242', label: 'Bahamas' },
73
+ { iso2: 'DM', dialCode: '+1767', label: 'Dominica' },
74
+ { iso2: 'DO', dialCode: '+1809', label: 'Dominican Republic' },
75
+ { iso2: 'GD', dialCode: '+1473', label: 'Grenada' },
76
+ { iso2: 'GU', dialCode: '+1671', label: 'Guam' },
77
+ { iso2: 'JM', dialCode: '+1876', label: 'Jamaica' },
78
+ { iso2: 'KN', dialCode: '+1869', label: 'Saint Kitts and Nevis' },
79
+ { iso2: 'KY', dialCode: '+1345', label: 'Cayman Islands' },
80
+ { iso2: 'LC', dialCode: '+1758', label: 'Saint Lucia' },
81
+ { iso2: 'MP', dialCode: '+1670', label: 'Northern Mariana Islands' },
82
+ { iso2: 'MS', dialCode: '+1664', label: 'Montserrat' },
83
+ { iso2: 'PR', dialCode: '+1787', label: 'Puerto Rico' },
84
+ { iso2: 'SX', dialCode: '+1721', label: 'Sint Maarten' },
85
+ { iso2: 'TC', dialCode: '+1649', label: 'Turks and Caicos Islands' },
86
+ { iso2: 'TT', dialCode: '+1868', label: 'Trinidad and Tobago' },
87
+ { iso2: 'VC', dialCode: '+1784', label: 'Saint Vincent and the Grenadines' },
88
+ { iso2: 'VG', dialCode: '+1284', label: 'British Virgin Islands' },
89
+ { iso2: 'VI', dialCode: '+1340', label: 'U.S. Virgin Islands' },
90
+ { iso2: 'EG', dialCode: '+20', label: 'Egypt' },
91
+ { iso2: 'SS', dialCode: '+211', label: 'South Sudan' },
92
+ { iso2: 'MA', dialCode: '+212', label: 'Morocco' },
93
+ { iso2: 'EH', dialCode: '+212', label: 'Western Sahara' },
94
+ { iso2: 'DZ', dialCode: '+213', label: 'Algeria' },
95
+ { iso2: 'TN', dialCode: '+216', label: 'Tunisia' },
96
+ { iso2: 'LY', dialCode: '+218', label: 'Libya' },
97
+ { iso2: 'GM', dialCode: '+220', label: 'Gambia' },
98
+ { iso2: 'SN', dialCode: '+221', label: 'Senegal' },
99
+ { iso2: 'MR', dialCode: '+222', label: 'Mauritania' },
100
+ { iso2: 'ML', dialCode: '+223', label: 'Mali' },
101
+ { iso2: 'GN', dialCode: '+224', label: 'Guinea' },
102
+ { iso2: 'CI', dialCode: '+225', label: "Cรดte d'Ivoire" },
103
+ { iso2: 'BF', dialCode: '+226', label: 'Burkina Faso' },
104
+ { iso2: 'NE', dialCode: '+227', label: 'Niger' },
105
+ { iso2: 'TG', dialCode: '+228', label: 'Togo' },
106
+ { iso2: 'BJ', dialCode: '+229', label: 'Benin' },
107
+ { iso2: 'MU', dialCode: '+230', label: 'Mauritius' },
108
+ { iso2: 'LR', dialCode: '+231', label: 'Liberia' },
109
+ { iso2: 'SL', dialCode: '+232', label: 'Sierra Leone' },
110
+ { iso2: 'GH', dialCode: '+233', label: 'Ghana' },
111
+ { iso2: 'NG', dialCode: '+234', label: 'Nigeria' },
112
+ { iso2: 'TD', dialCode: '+235', label: 'Chad' },
113
+ { iso2: 'CF', dialCode: '+236', label: 'Central African Republic' },
114
+ { iso2: 'CM', dialCode: '+237', label: 'Cameroon' },
115
+ { iso2: 'CV', dialCode: '+238', label: 'Cape Verde' },
116
+ { iso2: 'ST', dialCode: '+239', label: 'Sรฃo Tomรฉ and Prรญncipe' },
117
+ { iso2: 'GQ', dialCode: '+240', label: 'Equatorial Guinea' },
118
+ { iso2: 'GA', dialCode: '+241', label: 'Gabon' },
119
+ { iso2: 'CG', dialCode: '+242', label: 'Republic of the Congo' },
120
+ { iso2: 'CD', dialCode: '+243', label: 'DR Congo' },
121
+ { iso2: 'AO', dialCode: '+244', label: 'Angola' },
122
+ { iso2: 'GW', dialCode: '+245', label: 'Guinea-Bissau' },
123
+ { iso2: 'IO', dialCode: '+246', label: 'British Indian Ocean Territory' },
124
+ { iso2: 'SC', dialCode: '+248', label: 'Seychelles' },
125
+ { iso2: 'SD', dialCode: '+249', label: 'Sudan' },
126
+ { iso2: 'RW', dialCode: '+250', label: 'Rwanda' },
127
+ { iso2: 'ET', dialCode: '+251', label: 'Ethiopia' },
128
+ { iso2: 'SO', dialCode: '+252', label: 'Somalia' },
129
+ { iso2: 'DJ', dialCode: '+253', label: 'Djibouti' },
130
+ { iso2: 'KE', dialCode: '+254', label: 'Kenya' },
131
+ { iso2: 'TZ', dialCode: '+255', label: 'Tanzania' },
132
+ { iso2: 'UG', dialCode: '+256', label: 'Uganda' },
133
+ { iso2: 'BI', dialCode: '+257', label: 'Burundi' },
134
+ { iso2: 'MZ', dialCode: '+258', label: 'Mozambique' },
135
+ { iso2: 'ZM', dialCode: '+260', label: 'Zambia' },
136
+ { iso2: 'MG', dialCode: '+261', label: 'Madagascar' },
137
+ { iso2: 'RE', dialCode: '+262', label: 'Rรฉunion' },
138
+ { iso2: 'YT', dialCode: '+262', label: 'Mayotte' },
139
+ { iso2: 'ZW', dialCode: '+263', label: 'Zimbabwe' },
140
+ { iso2: 'NA', dialCode: '+264', label: 'Namibia' },
141
+ { iso2: 'MW', dialCode: '+265', label: 'Malawi' },
142
+ { iso2: 'LS', dialCode: '+266', label: 'Lesotho' },
143
+ { iso2: 'BW', dialCode: '+267', label: 'Botswana' },
144
+ { iso2: 'SZ', dialCode: '+268', label: 'Eswatini' },
145
+ { iso2: 'KM', dialCode: '+269', label: 'Comoros' },
146
+ { iso2: 'ZA', dialCode: '+27', label: 'South Africa' },
147
+ { iso2: 'SH', dialCode: '+290', label: 'Saint Helena' },
148
+ { iso2: 'ER', dialCode: '+291', label: 'Eritrea' },
149
+ { iso2: 'AW', dialCode: '+297', label: 'Aruba' },
150
+ { iso2: 'FO', dialCode: '+298', label: 'Faroe Islands' },
151
+ { iso2: 'GL', dialCode: '+299', label: 'Greenland' },
152
+ { iso2: 'GR', dialCode: '+30', label: 'Greece' },
153
+ { iso2: 'NL', dialCode: '+31', label: 'Netherlands' },
154
+ { iso2: 'BE', dialCode: '+32', label: 'Belgium' },
155
+ { iso2: 'FR', dialCode: '+33', label: 'France' },
156
+ { iso2: 'ES', dialCode: '+34', label: 'Spain' },
157
+ { iso2: 'GI', dialCode: '+350', label: 'Gibraltar' },
158
+ { iso2: 'PT', dialCode: '+351', label: 'Portugal' },
159
+ { iso2: 'LU', dialCode: '+352', label: 'Luxembourg' },
160
+ { iso2: 'IE', dialCode: '+353', label: 'Ireland' },
161
+ { iso2: 'IS', dialCode: '+354', label: 'Iceland' },
162
+ { iso2: 'AL', dialCode: '+355', label: 'Albania' },
163
+ { iso2: 'MT', dialCode: '+356', label: 'Malta' },
164
+ { iso2: 'CY', dialCode: '+357', label: 'Cyprus' },
165
+ { iso2: 'FI', dialCode: '+358', label: 'Finland' },
166
+ { iso2: 'AX', dialCode: '+358', label: 'ร…land Islands' },
167
+ { iso2: 'BG', dialCode: '+359', label: 'Bulgaria' },
168
+ { iso2: 'HU', dialCode: '+36', label: 'Hungary' },
169
+ { iso2: 'LT', dialCode: '+370', label: 'Lithuania' },
170
+ { iso2: 'LV', dialCode: '+371', label: 'Latvia' },
171
+ { iso2: 'EE', dialCode: '+372', label: 'Estonia' },
172
+ { iso2: 'MD', dialCode: '+373', label: 'Moldova' },
173
+ { iso2: 'AM', dialCode: '+374', label: 'Armenia' },
174
+ { iso2: 'BY', dialCode: '+375', label: 'Belarus' },
175
+ { iso2: 'AD', dialCode: '+376', label: 'Andorra' },
176
+ { iso2: 'MC', dialCode: '+377', label: 'Monaco' },
177
+ { iso2: 'SM', dialCode: '+378', label: 'San Marino' },
178
+ { iso2: 'VA', dialCode: '+379', label: 'Vatican City' },
179
+ { iso2: 'UA', dialCode: '+380', label: 'Ukraine' },
180
+ { iso2: 'RS', dialCode: '+381', label: 'Serbia' },
181
+ { iso2: 'ME', dialCode: '+382', label: 'Montenegro' },
182
+ { iso2: 'XK', dialCode: '+383', label: 'Kosovo' },
183
+ { iso2: 'HR', dialCode: '+385', label: 'Croatia' },
184
+ { iso2: 'SI', dialCode: '+386', label: 'Slovenia' },
185
+ { iso2: 'BA', dialCode: '+387', label: 'Bosnia and Herzegovina' },
186
+ { iso2: 'MK', dialCode: '+389', label: 'North Macedonia' },
187
+ { iso2: 'IT', dialCode: '+39', label: 'Italy' },
188
+ { iso2: 'RO', dialCode: '+40', label: 'Romania' },
189
+ { iso2: 'CH', dialCode: '+41', label: 'Switzerland' },
190
+ { iso2: 'CZ', dialCode: '+420', label: 'Czechia' },
191
+ { iso2: 'SK', dialCode: '+421', label: 'Slovakia' },
192
+ { iso2: 'LI', dialCode: '+423', label: 'Liechtenstein' },
193
+ { iso2: 'AT', dialCode: '+43', label: 'Austria' },
194
+ { iso2: 'GB', dialCode: '+44', label: 'United Kingdom' },
195
+ { iso2: 'GG', dialCode: '+44', label: 'Guernsey' },
196
+ { iso2: 'JE', dialCode: '+44', label: 'Jersey' },
197
+ { iso2: 'IM', dialCode: '+44', label: 'Isle of Man' },
198
+ { iso2: 'DK', dialCode: '+45', label: 'Denmark' },
199
+ { iso2: 'SE', dialCode: '+46', label: 'Sweden' },
200
+ { iso2: 'NO', dialCode: '+47', label: 'Norway' },
201
+ { iso2: 'SJ', dialCode: '+47', label: 'Svalbard and Jan Mayen' },
202
+ { iso2: 'PL', dialCode: '+48', label: 'Poland' },
203
+ { iso2: 'DE', dialCode: '+49', label: 'Germany' },
204
+ { iso2: 'FK', dialCode: '+500', label: 'Falkland Islands' },
205
+ { iso2: 'BZ', dialCode: '+501', label: 'Belize' },
206
+ { iso2: 'GT', dialCode: '+502', label: 'Guatemala' },
207
+ { iso2: 'SV', dialCode: '+503', label: 'El Salvador' },
208
+ { iso2: 'HN', dialCode: '+504', label: 'Honduras' },
209
+ { iso2: 'NI', dialCode: '+505', label: 'Nicaragua' },
210
+ { iso2: 'CR', dialCode: '+506', label: 'Costa Rica' },
211
+ { iso2: 'PA', dialCode: '+507', label: 'Panama' },
212
+ { iso2: 'PM', dialCode: '+508', label: 'Saint Pierre and Miquelon' },
213
+ { iso2: 'HT', dialCode: '+509', label: 'Haiti' },
214
+ { iso2: 'PE', dialCode: '+51', label: 'Peru' },
215
+ { iso2: 'MX', dialCode: '+52', label: 'Mexico' },
216
+ { iso2: 'CU', dialCode: '+53', label: 'Cuba' },
217
+ { iso2: 'AR', dialCode: '+54', label: 'Argentina' },
218
+ { iso2: 'BR', dialCode: '+55', label: 'Brazil' },
219
+ { iso2: 'CL', dialCode: '+56', label: 'Chile' },
220
+ { iso2: 'CO', dialCode: '+57', label: 'Colombia' },
221
+ { iso2: 'VE', dialCode: '+58', label: 'Venezuela' },
222
+ { iso2: 'GP', dialCode: '+590', label: 'Guadeloupe' },
223
+ { iso2: 'BL', dialCode: '+590', label: 'Saint Barthรฉlemy' },
224
+ { iso2: 'MF', dialCode: '+590', label: 'Saint Martin' },
225
+ { iso2: 'BO', dialCode: '+591', label: 'Bolivia' },
226
+ { iso2: 'GY', dialCode: '+592', label: 'Guyana' },
227
+ { iso2: 'EC', dialCode: '+593', label: 'Ecuador' },
228
+ { iso2: 'GF', dialCode: '+594', label: 'French Guiana' },
229
+ { iso2: 'PY', dialCode: '+595', label: 'Paraguay' },
230
+ { iso2: 'MQ', dialCode: '+596', label: 'Martinique' },
231
+ { iso2: 'SR', dialCode: '+597', label: 'Suriname' },
232
+ { iso2: 'UY', dialCode: '+598', label: 'Uruguay' },
233
+ { iso2: 'CW', dialCode: '+599', label: 'Curaรงao' },
234
+ { iso2: 'BQ', dialCode: '+599', label: 'Caribbean Netherlands' },
235
+ { iso2: 'MY', dialCode: '+60', label: 'Malaysia' },
236
+ { iso2: 'AU', dialCode: '+61', label: 'Australia' },
237
+ { iso2: 'CX', dialCode: '+61', label: 'Christmas Island' },
238
+ { iso2: 'CC', dialCode: '+61', label: 'Cocos (Keeling) Islands' },
239
+ { iso2: 'ID', dialCode: '+62', label: 'Indonesia' },
240
+ { iso2: 'PH', dialCode: '+63', label: 'Philippines' },
241
+ { iso2: 'NZ', dialCode: '+64', label: 'New Zealand' },
242
+ { iso2: 'SG', dialCode: '+65', label: 'Singapore' },
243
+ { iso2: 'TH', dialCode: '+66', label: 'Thailand' },
244
+ { iso2: 'TL', dialCode: '+670', label: 'Timor-Leste' },
245
+ { iso2: 'NF', dialCode: '+672', label: 'Norfolk Island' },
246
+ { iso2: 'BN', dialCode: '+673', label: 'Brunei' },
247
+ { iso2: 'NR', dialCode: '+674', label: 'Nauru' },
248
+ { iso2: 'PG', dialCode: '+675', label: 'Papua New Guinea' },
249
+ { iso2: 'TO', dialCode: '+676', label: 'Tonga' },
250
+ { iso2: 'SB', dialCode: '+677', label: 'Solomon Islands' },
251
+ { iso2: 'VU', dialCode: '+678', label: 'Vanuatu' },
252
+ { iso2: 'FJ', dialCode: '+679', label: 'Fiji' },
253
+ { iso2: 'PW', dialCode: '+680', label: 'Palau' },
254
+ { iso2: 'WF', dialCode: '+681', label: 'Wallis and Futuna' },
255
+ { iso2: 'CK', dialCode: '+682', label: 'Cook Islands' },
256
+ { iso2: 'NU', dialCode: '+683', label: 'Niue' },
257
+ { iso2: 'WS', dialCode: '+685', label: 'Samoa' },
258
+ { iso2: 'KI', dialCode: '+686', label: 'Kiribati' },
259
+ { iso2: 'NC', dialCode: '+687', label: 'New Caledonia' },
260
+ { iso2: 'TV', dialCode: '+688', label: 'Tuvalu' },
261
+ { iso2: 'PF', dialCode: '+689', label: 'French Polynesia' },
262
+ { iso2: 'TK', dialCode: '+690', label: 'Tokelau' },
263
+ { iso2: 'FM', dialCode: '+691', label: 'Micronesia' },
264
+ { iso2: 'MH', dialCode: '+692', label: 'Marshall Islands' },
265
+ { iso2: 'RU', dialCode: '+7', label: 'Russia' },
266
+ { iso2: 'KZ', dialCode: '+7', label: 'Kazakhstan' },
267
+ { iso2: 'JP', dialCode: '+81', label: 'Japan' },
268
+ { iso2: 'KR', dialCode: '+82', label: 'South Korea' },
269
+ { iso2: 'VN', dialCode: '+84', label: 'Vietnam' },
270
+ { iso2: 'KP', dialCode: '+850', label: 'North Korea' },
271
+ { iso2: 'HK', dialCode: '+852', label: 'Hong Kong' },
272
+ { iso2: 'MO', dialCode: '+853', label: 'Macau' },
273
+ { iso2: 'KH', dialCode: '+855', label: 'Cambodia' },
274
+ { iso2: 'LA', dialCode: '+856', label: 'Laos' },
275
+ { iso2: 'CN', dialCode: '+86', label: 'China' },
276
+ { iso2: 'BD', dialCode: '+880', label: 'Bangladesh' },
277
+ { iso2: 'TW', dialCode: '+886', label: 'Taiwan' },
278
+ { iso2: 'TR', dialCode: '+90', label: 'Turkey' },
279
+ { iso2: 'IN', dialCode: '+91', label: 'India' },
280
+ { iso2: 'PK', dialCode: '+92', label: 'Pakistan' },
281
+ { iso2: 'AF', dialCode: '+93', label: 'Afghanistan' },
282
+ { iso2: 'LK', dialCode: '+94', label: 'Sri Lanka' },
283
+ { iso2: 'MM', dialCode: '+95', label: 'Myanmar' },
284
+ { iso2: 'MV', dialCode: '+960', label: 'Maldives' },
285
+ { iso2: 'LB', dialCode: '+961', label: 'Lebanon' },
286
+ { iso2: 'JO', dialCode: '+962', label: 'Jordan' },
287
+ { iso2: 'SY', dialCode: '+963', label: 'Syria' },
288
+ { iso2: 'IQ', dialCode: '+964', label: 'Iraq' },
289
+ { iso2: 'KW', dialCode: '+965', label: 'Kuwait' },
290
+ { iso2: 'SA', dialCode: '+966', label: 'Saudi Arabia' },
291
+ { iso2: 'YE', dialCode: '+967', label: 'Yemen' },
292
+ { iso2: 'OM', dialCode: '+968', label: 'Oman' },
293
+ { iso2: 'PS', dialCode: '+970', label: 'Palestine' },
294
+ { iso2: 'AE', dialCode: '+971', label: 'United Arab Emirates' },
295
+ { iso2: 'IL', dialCode: '+972', label: 'Israel' },
296
+ { iso2: 'BH', dialCode: '+973', label: 'Bahrain' },
297
+ { iso2: 'QA', dialCode: '+974', label: 'Qatar' },
298
+ { iso2: 'BT', dialCode: '+975', label: 'Bhutan' },
299
+ { iso2: 'MN', dialCode: '+976', label: 'Mongolia' },
300
+ { iso2: 'NP', dialCode: '+977', label: 'Nepal' },
301
+ { iso2: 'TJ', dialCode: '+992', label: 'Tajikistan' },
302
+ { iso2: 'TM', dialCode: '+993', label: 'Turkmenistan' },
303
+ { iso2: 'AZ', dialCode: '+994', label: 'Azerbaijan' },
304
+ { iso2: 'GE', dialCode: '+995', label: 'Georgia' },
305
+ { iso2: 'KG', dialCode: '+996', label: 'Kyrgyzstan' },
306
+ { iso2: 'UZ', dialCode: '+998', label: 'Uzbekistan' },
307
+ { iso2: 'IR', dialCode: '+98', label: 'Iran' },
55
308
  ]
56
309
 
57
- const DEFAULT_COUNTRY = PHONE_COUNTRIES[0]
58
- // Match longer prefixes first to avoid e.g. `+1` shadowing `+1234` if any.
59
- const COUNTRIES_BY_DIAL_LENGTH = [...PHONE_COUNTRIES].sort(
60
- (a, b) => b.dialCode.length - a.dialCode.length,
61
- )
310
+ export const PHONE_COUNTRIES: PhoneCountry[] = RAW_PHONE_COUNTRIES
311
+ .map((country) => ({ ...country, flag: iso2ToFlagEmoji(country.iso2) }))
312
+ .sort((a, b) => a.label.localeCompare(b.label, 'en', { sensitivity: 'base' }))
313
+
314
+ /**
315
+ * Sovereign/primary country that wins auto-detection for a calling code shared
316
+ * by several territories. US vs. Canada is the one irreducible ambiguity โ€” both
317
+ * are `+1` โ€” and it resolves to the US.
318
+ */
319
+ const PRIMARY_DIAL_OWNERS: Record<string, string> = {
320
+ '+1': 'US',
321
+ '+7': 'RU',
322
+ '+44': 'GB',
323
+ '+47': 'NO',
324
+ '+61': 'AU',
325
+ '+212': 'MA',
326
+ '+262': 'RE',
327
+ '+358': 'FI',
328
+ '+590': 'GP',
329
+ '+599': 'CW',
330
+ }
331
+
332
+ const DEFAULT_COUNTRY =
333
+ PHONE_COUNTRIES.find((country) => country.iso2 === 'US') ?? PHONE_COUNTRIES[0]
334
+
335
+ // Match longer prefixes first (e.g. `+1242` before `+1`); for equal-length codes
336
+ // shared by several territories, prefer the sovereign in `PRIMARY_DIAL_OWNERS`.
337
+ const COUNTRIES_BY_DIAL_LENGTH = [...PHONE_COUNTRIES].sort((a, b) => {
338
+ if (b.dialCode.length !== a.dialCode.length) return b.dialCode.length - a.dialCode.length
339
+ const aPrimary = PRIMARY_DIAL_OWNERS[a.dialCode] === a.iso2 ? 0 : 1
340
+ const bPrimary = PRIMARY_DIAL_OWNERS[b.dialCode] === b.iso2 ? 0 : 1
341
+ return aPrimary - bPrimary
342
+ })
62
343
 
63
344
  function findCountryByIso(iso2: string): PhoneCountry | undefined {
64
345
  return PHONE_COUNTRIES.find((c) => c.iso2 === iso2)
@@ -22,7 +22,7 @@ jest.mock('@open-mercato/shared/lib/phone', () => ({
22
22
 
23
23
  import * as React from 'react'
24
24
  import { fireEvent, render, screen } from '@testing-library/react'
25
- import { PhoneNumberField } from '../PhoneNumberField'
25
+ import { PHONE_COUNTRIES, PhoneNumberField } from '../PhoneNumberField'
26
26
 
27
27
  function PhoneFieldHarness(props: Partial<React.ComponentProps<typeof PhoneNumberField>>) {
28
28
  const [value, setValue] = React.useState<string | undefined>(undefined)
@@ -76,4 +76,74 @@ describe('PhoneNumberField', () => {
76
76
  expect(screen.queryByText('Use an international phone number.')).not.toBeInTheDocument()
77
77
  expect(input).toHaveAttribute('aria-invalid', 'true')
78
78
  })
79
+
80
+ it('honours a restricted country list via the countries prop', () => {
81
+ render(
82
+ <PhoneFieldHarness
83
+ value="+383 44 123 456"
84
+ countries={[{ iso2: 'PL', dialCode: '+48', label: 'Poland', flag: '๐Ÿ‡ต๐Ÿ‡ฑ' }]}
85
+ />,
86
+ )
87
+
88
+ // The value still parses against the full dictionary (Kosovo), but the
89
+ // picker only offers the restricted list.
90
+ expect(screen.getByText('+383')).toBeInTheDocument()
91
+ })
92
+ })
93
+
94
+ describe('PhoneNumberField country dictionary', () => {
95
+ it('exposes a complete, well-formed geographic dictionary', () => {
96
+ expect(PHONE_COUNTRIES.length).toBeGreaterThan(200)
97
+
98
+ for (const country of PHONE_COUNTRIES) {
99
+ expect(country.iso2).toMatch(/^[A-Z]{2}$/)
100
+ expect(country.dialCode).toMatch(/^\+\d+$/)
101
+ expect(country.label.length).toBeGreaterThan(0)
102
+ expect(country.flag.length).toBeGreaterThan(0)
103
+ }
104
+
105
+ const isoCodes = PHONE_COUNTRIES.map((country) => country.iso2)
106
+ expect(new Set(isoCodes).size).toBe(isoCodes.length)
107
+ })
108
+
109
+ it('includes representative codes from every numbering zone', () => {
110
+ const dialByIso = new Map(PHONE_COUNTRIES.map((country) => [country.iso2, country.dialCode]))
111
+ expect(dialByIso.get('XK')).toBe('+383')
112
+ expect(dialByIso.get('BS')).toBe('+1242')
113
+ expect(dialByIso.get('BR')).toBe('+55')
114
+ expect(dialByIso.get('AU')).toBe('+61')
115
+ expect(dialByIso.get('JP')).toBe('+81')
116
+ expect(dialByIso.get('TR')).toBe('+90')
117
+ expect(dialByIso.get('PS')).toBe('+970')
118
+ })
119
+
120
+ it('excludes non-geographic international service codes', () => {
121
+ for (const code of ['+800', '+808', '+870', '+881', '+882']) {
122
+ expect(PHONE_COUNTRIES.some((country) => country.dialCode === code)).toBe(false)
123
+ }
124
+ })
125
+
126
+ it('derives each flag emoji from the ISO code', () => {
127
+ expect(PHONE_COUNTRIES.find((country) => country.iso2 === 'US')?.flag).toBe('๐Ÿ‡บ๐Ÿ‡ธ')
128
+ expect(PHONE_COUNTRIES.find((country) => country.iso2 === 'XK')?.flag).toBe('๐Ÿ‡ฝ๐Ÿ‡ฐ')
129
+ })
130
+ })
131
+
132
+ describe('PhoneNumberField initial country detection', () => {
133
+ const cases: Array<[string, string]> = [
134
+ ['+1242 555 0100', '+1242'],
135
+ ['+1 212 555 1234', '+1'],
136
+ ['+383 44 123 456', '+383'],
137
+ ['+55 11 91234 5678', '+55'],
138
+ ['+61 2 1234 5678', '+61'],
139
+ ['+81 3 1234 5678', '+81'],
140
+ ['+90 212 123 4567', '+90'],
141
+ ['+970 59 123 4567', '+970'],
142
+ ['+44 20 7946 0000', '+44'],
143
+ ]
144
+
145
+ it.each(cases)('resolves %s to dial code %s (longest prefix, sovereign first)', (value, expected) => {
146
+ render(<PhoneFieldHarness value={value} />)
147
+ expect(screen.getByText(expected)).toBeInTheDocument()
148
+ })
79
149
  })
@@ -24,6 +24,8 @@ export type CustomFieldDefDto = {
24
24
  // attachments-specific config
25
25
  maxAttachmentSizeMb?: number
26
26
  acceptExtensions?: string[]
27
+ // phone-specific config
28
+ defaultCountryIso2?: string
27
29
  // optional validation rules
28
30
  validation?: Array<
29
31
  | { rule: 'required'; message: string }