@asafarim/shared-i18n 0.6.6 → 0.8.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.
Files changed (80) hide show
  1. package/demo/dist/Icon Dropdown_Limited Languages.png +0 -0
  2. package/demo/dist/Select Dropdown_Text Only.png +0 -0
  3. package/demo/dist/assets/favicon-BZYZvBLo.svg +4 -0
  4. package/demo/dist/assets/index-BdjqKw_N.css +1 -0
  5. package/demo/dist/assets/index-C1Tq1uEr.js +191 -0
  6. package/demo/dist/favicon.svg +4 -0
  7. package/demo/dist/index.html +27 -0
  8. package/demo/dist/logo.svg +24 -0
  9. package/demo/node_modules/.bin/browserslist +21 -0
  10. package/demo/node_modules/.bin/browserslist.CMD +12 -0
  11. package/demo/node_modules/.bin/browserslist.ps1 +41 -0
  12. package/demo/node_modules/.bin/tsc +4 -4
  13. package/demo/node_modules/.bin/tsc.CMD +12 -0
  14. package/demo/node_modules/.bin/tsc.ps1 +41 -0
  15. package/demo/node_modules/.bin/tsserver +4 -4
  16. package/demo/node_modules/.bin/tsserver.CMD +12 -0
  17. package/demo/node_modules/.bin/tsserver.ps1 +41 -0
  18. package/demo/node_modules/.bin/vite +4 -4
  19. package/demo/node_modules/.bin/vite.CMD +12 -0
  20. package/demo/node_modules/.bin/vite.ps1 +41 -0
  21. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js +848 -0
  22. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js.map +7 -0
  23. package/demo/node_modules/.vite/deps/_metadata.json +76 -0
  24. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  25. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  26. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js +1004 -0
  27. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js.map +7 -0
  28. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js +292 -0
  29. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js.map +7 -0
  30. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js +280 -0
  31. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js.map +7 -0
  32. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js +400 -0
  33. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js.map +7 -0
  34. package/demo/node_modules/.vite/deps/i18next.js +2392 -0
  35. package/demo/node_modules/.vite/deps/i18next.js.map +7 -0
  36. package/demo/node_modules/.vite/deps/package.json +3 -0
  37. package/demo/node_modules/.vite/deps/react-dom.js +6 -0
  38. package/demo/node_modules/.vite/deps/react-dom.js.map +7 -0
  39. package/demo/node_modules/.vite/deps/react-dom_client.js +20217 -0
  40. package/demo/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  41. package/demo/node_modules/.vite/deps/react-i18next.js +869 -0
  42. package/demo/node_modules/.vite/deps/react-i18next.js.map +7 -0
  43. package/demo/node_modules/.vite/deps/react.js +5 -0
  44. package/demo/node_modules/.vite/deps/react.js.map +7 -0
  45. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
  46. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  47. package/demo/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
  48. package/demo/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  49. package/demo/package.json +27 -24
  50. package/demo/public/favicon.svg +4 -4
  51. package/demo/public/logo.svg +24 -24
  52. package/demo/src/App.tsx +129 -116
  53. package/demo/src/components/CountryLanguageDemo.tsx +140 -0
  54. package/demo/src/components/GetStartedSection.tsx +56 -56
  55. package/demo/src/components/KeyTable.tsx +29 -29
  56. package/demo/src/components/LanguageBar.tsx +102 -18
  57. package/demo/src/components/LanguageSwitcherDemo.module.css +114 -113
  58. package/demo/src/components/LanguageSwitcherDemo.tsx +256 -202
  59. package/demo/src/components/Logo.tsx +6 -6
  60. package/demo/src/components/OverviewSection.tsx +43 -43
  61. package/demo/src/components/Panel.tsx +15 -15
  62. package/demo/src/components/StatusCard.tsx +109 -109
  63. package/demo/src/index.css +644 -605
  64. package/demo/src/locales/de/demo.json +85 -0
  65. package/demo/src/locales/en/demo.json +85 -85
  66. package/demo/src/locales/fr/demo.json +85 -85
  67. package/demo/src/locales/it/demo.json +85 -0
  68. package/demo/src/locales/nl/demo.json +85 -85
  69. package/demo/src/main.tsx +29 -24
  70. package/demo/tsconfig.json +18 -18
  71. package/demo/tsconfig.node.json +10 -10
  72. package/demo/tsconfig.tsbuildinfo +1 -1
  73. package/demo/vite-env.d.ts +7 -7
  74. package/demo/vite.config.d.ts +2 -2
  75. package/demo/vite.config.js +10 -10
  76. package/dist/components/LanguageSwitcher.module.css +303 -303
  77. package/dist/locales/de/common.json +68 -0
  78. package/dist/locales/it/common.json +68 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +85 -84
@@ -0,0 +1,848 @@
1
+ import {
2
+ require_jsx_runtime
3
+ } from "./chunk-E6BG6WAU.js";
4
+ import {
5
+ require_react
6
+ } from "./chunk-B3AHR5EX.js";
7
+ import {
8
+ __toESM
9
+ } from "./chunk-5WRI5ZAA.js";
10
+
11
+ // ../node_modules/.pnpm/@asafarim+country-language-_049cb8b81291b08ccd85a4446ad76c74/node_modules/@asafarim/country-language-selector/dist/index.js
12
+ var import_react = __toESM(require_react());
13
+ var import_jsx_runtime = __toESM(require_jsx_runtime());
14
+ var defaultStrings = {
15
+ ariaLabel: "Select country and language",
16
+ searchPlaceholder: "Search country…",
17
+ emptyResults: "No matching countries",
18
+ chooseCountry: "Country",
19
+ chooseLanguage: "Language",
20
+ backToCountries: "Back",
21
+ close: "Close"
22
+ };
23
+ var L = {
24
+ en: { code: "en", label: "English", nativeLabel: "English" },
25
+ nl: { code: "nl", label: "Dutch", nativeLabel: "Nederlands" },
26
+ fr: { code: "fr", label: "French", nativeLabel: "Français" },
27
+ de: { code: "de", label: "German", nativeLabel: "Deutsch" },
28
+ es: { code: "es", label: "Spanish", nativeLabel: "Español" },
29
+ it: { code: "it", label: "Italian", nativeLabel: "Italiano" },
30
+ pt: { code: "pt", label: "Portuguese", nativeLabel: "Português" },
31
+ sv: { code: "sv", label: "Swedish", nativeLabel: "Svenska" },
32
+ da: { code: "da", label: "Danish", nativeLabel: "Dansk" },
33
+ no: { code: "no", label: "Norwegian", nativeLabel: "Norsk" },
34
+ fi: { code: "fi", label: "Finnish", nativeLabel: "Suomi" },
35
+ pl: { code: "pl", label: "Polish", nativeLabel: "Polski" },
36
+ ja: { code: "ja", label: "Japanese", nativeLabel: "日本語" },
37
+ zh: { code: "zh", label: "Chinese", nativeLabel: "中文" },
38
+ ar: { code: "ar", label: "Arabic", nativeLabel: "العربية" },
39
+ tr: { code: "tr", label: "Turkish", nativeLabel: "Türkçe" },
40
+ ru: { code: "ru", label: "Russian", nativeLabel: "Русский" },
41
+ ko: { code: "ko", label: "Korean", nativeLabel: "한국어" },
42
+ hi: { code: "hi", label: "Hindi", nativeLabel: "हिन्दी" },
43
+ fa: { code: "fa", label: "Persian", nativeLabel: "فارسی" }
44
+ };
45
+ var defaultCountries = [
46
+ {
47
+ code: "BE",
48
+ name: "Belgium",
49
+ nativeName: "België",
50
+ flag: "🇧🇪",
51
+ languages: [L.nl, L.fr, L.de, L.en]
52
+ },
53
+ {
54
+ code: "NL",
55
+ name: "Netherlands",
56
+ nativeName: "Nederland",
57
+ flag: "🇳🇱",
58
+ languages: [L.nl, L.en]
59
+ },
60
+ {
61
+ code: "FR",
62
+ name: "France",
63
+ nativeName: "France",
64
+ flag: "🇫🇷",
65
+ languages: [L.fr, L.en]
66
+ },
67
+ {
68
+ code: "DE",
69
+ name: "Germany",
70
+ nativeName: "Deutschland",
71
+ flag: "🇩🇪",
72
+ languages: [L.de, L.en]
73
+ },
74
+ {
75
+ code: "CH",
76
+ name: "Switzerland",
77
+ nativeName: "Schweiz",
78
+ flag: "🇨🇭",
79
+ languages: [L.de, L.fr, L.it, L.en]
80
+ },
81
+ {
82
+ code: "LU",
83
+ name: "Luxembourg",
84
+ nativeName: "Lëtzebuerg",
85
+ flag: "🇱🇺",
86
+ languages: [L.fr, L.de, L.en]
87
+ },
88
+ {
89
+ code: "CA",
90
+ name: "Canada",
91
+ nativeName: "Canada",
92
+ flag: "🇨🇦",
93
+ languages: [L.en, L.fr]
94
+ },
95
+ {
96
+ code: "US",
97
+ name: "United States",
98
+ flag: "🇺🇸",
99
+ languages: [L.en, L.es]
100
+ },
101
+ {
102
+ code: "GB",
103
+ name: "United Kingdom",
104
+ flag: "🇬🇧",
105
+ languages: [L.en]
106
+ },
107
+ {
108
+ code: "ES",
109
+ name: "Spain",
110
+ nativeName: "España",
111
+ flag: "🇪🇸",
112
+ languages: [L.es, L.en]
113
+ },
114
+ {
115
+ code: "IT",
116
+ name: "Italy",
117
+ nativeName: "Italia",
118
+ flag: "🇮🇹",
119
+ languages: [L.it, L.en]
120
+ },
121
+ {
122
+ code: "PT",
123
+ name: "Portugal",
124
+ flag: "🇵🇹",
125
+ languages: [L.pt, L.en]
126
+ },
127
+ {
128
+ code: "SE",
129
+ name: "Sweden",
130
+ nativeName: "Sverige",
131
+ flag: "🇸🇪",
132
+ languages: [L.sv, L.en]
133
+ },
134
+ {
135
+ code: "DK",
136
+ name: "Denmark",
137
+ nativeName: "Danmark",
138
+ flag: "🇩🇰",
139
+ languages: [L.da, L.en]
140
+ },
141
+ {
142
+ code: "NO",
143
+ name: "Norway",
144
+ nativeName: "Norge",
145
+ flag: "🇳🇴",
146
+ languages: [L.no, L.en]
147
+ },
148
+ {
149
+ code: "FI",
150
+ name: "Finland",
151
+ nativeName: "Suomi",
152
+ flag: "🇫🇮",
153
+ languages: [L.fi, L.sv, L.en]
154
+ },
155
+ {
156
+ code: "PL",
157
+ name: "Poland",
158
+ nativeName: "Polska",
159
+ flag: "🇵🇱",
160
+ languages: [L.pl, L.en]
161
+ },
162
+ {
163
+ code: "JP",
164
+ name: "Japan",
165
+ nativeName: "日本",
166
+ flag: "🇯🇵",
167
+ languages: [L.ja, L.en]
168
+ },
169
+ {
170
+ code: "CN",
171
+ name: "China",
172
+ nativeName: "中国",
173
+ flag: "🇨🇳",
174
+ languages: [L.zh, L.en]
175
+ },
176
+ {
177
+ code: "KR",
178
+ name: "South Korea",
179
+ nativeName: "대한민국",
180
+ flag: "🇰🇷",
181
+ languages: [L.ko, L.en]
182
+ },
183
+ {
184
+ code: "IN",
185
+ name: "India",
186
+ flag: "🇮🇳",
187
+ languages: [L.hi, L.en]
188
+ },
189
+ {
190
+ code: "TR",
191
+ name: "Türkiye",
192
+ nativeName: "Türkiye",
193
+ flag: "🇹🇷",
194
+ languages: [L.tr, L.en]
195
+ },
196
+ {
197
+ code: "RU",
198
+ name: "Russia",
199
+ nativeName: "Россия",
200
+ flag: "🇷🇺",
201
+ languages: [L.ru, L.en]
202
+ },
203
+ {
204
+ code: "IR",
205
+ name: "Iran",
206
+ nativeName: "ایران",
207
+ flag: "🇮🇷",
208
+ languages: [L.fa, L.en]
209
+ },
210
+ {
211
+ code: "SA",
212
+ name: "Saudi Arabia",
213
+ nativeName: "السعودية",
214
+ flag: "🇸🇦",
215
+ languages: [L.ar, L.en]
216
+ },
217
+ {
218
+ code: "AE",
219
+ name: "United Arab Emirates",
220
+ flag: "🇦🇪",
221
+ languages: [L.ar, L.en]
222
+ },
223
+ {
224
+ code: "BR",
225
+ name: "Brazil",
226
+ nativeName: "Brasil",
227
+ flag: "🇧🇷",
228
+ languages: [L.pt, L.en]
229
+ },
230
+ {
231
+ code: "MX",
232
+ name: "Mexico",
233
+ nativeName: "México",
234
+ flag: "🇲🇽",
235
+ languages: [L.es, L.en]
236
+ }
237
+ ];
238
+ var languageCatalogue = L;
239
+ var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
240
+ function readStorage(key) {
241
+ if (!isBrowser()) return null;
242
+ try {
243
+ return window.localStorage.getItem(key);
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ function writeStorage(key, value) {
249
+ if (!isBrowser()) return;
250
+ try {
251
+ window.localStorage.setItem(key, value);
252
+ } catch {
253
+ }
254
+ }
255
+ function isCoarsePointer() {
256
+ var _a;
257
+ if (!isBrowser()) return false;
258
+ return ((_a = window.matchMedia) == null ? void 0 : _a.call(window, "(pointer: coarse)").matches) ?? false;
259
+ }
260
+ function useCountryLanguage(args) {
261
+ const { countries, value, defaultValue, onChange, persistKey } = args;
262
+ const isControlled = value !== void 0;
263
+ const initialRef = (0, import_react.useRef)(null);
264
+ if (initialRef.current === null) {
265
+ initialRef.current = (defaultValue && isValidLocale(defaultValue, countries) ? defaultValue : null) ?? fallbackLocale(countries);
266
+ }
267
+ const [internal, setInternal] = (0, import_react.useState)(initialRef.current);
268
+ const current = isControlled ? value : internal;
269
+ const didInitRef = (0, import_react.useRef)(false);
270
+ (0, import_react.useEffect)(() => {
271
+ if (didInitRef.current) return;
272
+ didInitRef.current = true;
273
+ if (!isControlled && persistKey) {
274
+ const hydrated = readPersistedLocale(persistKey, countries);
275
+ if (hydrated) {
276
+ setInternal(hydrated);
277
+ onChange == null ? void 0 : onChange(hydrated, { reason: "init", previous: initialRef.current });
278
+ return;
279
+ }
280
+ }
281
+ onChange == null ? void 0 : onChange(current, { reason: "init", previous: null });
282
+ }, []);
283
+ const byCode = (0, import_react.useMemo)(() => {
284
+ const map = /* @__PURE__ */ new Map();
285
+ for (const c of countries) map.set(c.code, c);
286
+ return map;
287
+ }, [countries]);
288
+ const country = byCode.get(current.country) ?? countries[0];
289
+ const language = country.languages.find((l) => l.code === current.language) ?? country.languages[0];
290
+ const commit = (0, import_react.useCallback)(
291
+ (next, reason) => {
292
+ const previous = current;
293
+ if (!isControlled) setInternal(next);
294
+ if (persistKey && !isControlled) {
295
+ writeStorage(persistKey, JSON.stringify(next));
296
+ }
297
+ onChange == null ? void 0 : onChange(next, { reason, previous });
298
+ },
299
+ [current, isControlled, onChange, persistKey]
300
+ );
301
+ const setCountry = (0, import_react.useCallback)(
302
+ (code) => {
303
+ const nextCountry = byCode.get(code);
304
+ if (!nextCountry) return;
305
+ const keep = nextCountry.languages.find(
306
+ (l) => l.code === current.language
307
+ );
308
+ const nextLanguage = (keep ?? nextCountry.languages[0]).code;
309
+ commit({ country: nextCountry.code, language: nextLanguage }, "country");
310
+ },
311
+ [byCode, commit, current.language]
312
+ );
313
+ const setLanguage = (0, import_react.useCallback)(
314
+ (code) => {
315
+ if (!country.languages.some((l) => l.code === code)) return;
316
+ commit({ country: country.code, language: code }, "language");
317
+ },
318
+ [commit, country]
319
+ );
320
+ const setLocale = (0, import_react.useCallback)(
321
+ (next, reason = "country") => {
322
+ commit(next, reason);
323
+ },
324
+ [commit]
325
+ );
326
+ return {
327
+ locale: { country: country.code, language: language.code },
328
+ country,
329
+ language,
330
+ setCountry,
331
+ setLanguage,
332
+ setLocale,
333
+ needsLanguageChoice: country.languages.length > 1
334
+ };
335
+ }
336
+ function readPersistedLocale(key, countries) {
337
+ const raw = readStorage(key);
338
+ if (!raw) return null;
339
+ try {
340
+ const parsed = JSON.parse(raw);
341
+ return isValidLocale(parsed, countries) ? parsed : null;
342
+ } catch {
343
+ return null;
344
+ }
345
+ }
346
+ function isValidLocale(locale, countries) {
347
+ const c = countries.find((x) => x.code === locale.country);
348
+ if (!c) return false;
349
+ return c.languages.some((l) => l.code === locale.language);
350
+ }
351
+ function fallbackLocale(countries) {
352
+ const first = countries[0];
353
+ if (!first) {
354
+ throw new Error(
355
+ "[country-language-selector] `countries` must contain at least one entry."
356
+ );
357
+ }
358
+ return { country: first.code, language: first.languages[0].code };
359
+ }
360
+ function useClickOutside(refs, enabled, handler) {
361
+ (0, import_react.useEffect)(() => {
362
+ if (!enabled) return;
363
+ const isInside = (target) => {
364
+ if (!target) return false;
365
+ return refs.some((ref) => {
366
+ const el = ref.current;
367
+ return !!el && el.contains(target);
368
+ });
369
+ };
370
+ const onPointer = (event) => {
371
+ if (!isInside(event.target)) handler(event);
372
+ };
373
+ const onKey = (event) => {
374
+ if (event.key === "Escape") handler(event);
375
+ };
376
+ document.addEventListener("pointerdown", onPointer, true);
377
+ document.addEventListener("keydown", onKey, true);
378
+ return () => {
379
+ document.removeEventListener("pointerdown", onPointer, true);
380
+ document.removeEventListener("keydown", onKey, true);
381
+ };
382
+ }, [enabled, handler, refs]);
383
+ }
384
+ var FOCUSABLE_SELECTOR = [
385
+ "a[href]",
386
+ "button:not([disabled])",
387
+ "input:not([disabled])",
388
+ "textarea:not([disabled])",
389
+ "select:not([disabled])",
390
+ '[tabindex]:not([tabindex="-1"])'
391
+ ].join(",");
392
+ function useFocusTrap(container, enabled) {
393
+ (0, import_react.useEffect)(() => {
394
+ if (!enabled) return;
395
+ const node = container.current;
396
+ if (!node) return;
397
+ const onKey = (event) => {
398
+ if (event.key !== "Tab") return;
399
+ const focusable = Array.from(
400
+ node.querySelectorAll(FOCUSABLE_SELECTOR)
401
+ ).filter((el) => !el.hasAttribute("data-focus-skip"));
402
+ if (focusable.length === 0) {
403
+ event.preventDefault();
404
+ node.focus();
405
+ return;
406
+ }
407
+ const first = focusable[0];
408
+ const last = focusable[focusable.length - 1];
409
+ const active = document.activeElement;
410
+ if (event.shiftKey && (active === first || !node.contains(active))) {
411
+ event.preventDefault();
412
+ last.focus();
413
+ } else if (!event.shiftKey && active === last) {
414
+ event.preventDefault();
415
+ first.focus();
416
+ }
417
+ };
418
+ node.addEventListener("keydown", onKey);
419
+ return () => node.removeEventListener("keydown", onKey);
420
+ }, [container, enabled]);
421
+ }
422
+ function normalize(input) {
423
+ return input.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
424
+ }
425
+ function scoreCountry(country, rawQuery) {
426
+ const q = normalize(rawQuery);
427
+ if (!q) return 0;
428
+ const code = country.code.toLowerCase();
429
+ const name = normalize(country.name);
430
+ const native = country.nativeName ? normalize(country.nativeName) : "";
431
+ if (code === q) return 1e3;
432
+ if (code.startsWith(q)) return 900;
433
+ if (name.startsWith(q) || native.startsWith(q)) return 700;
434
+ for (const lang of country.languages) {
435
+ if (lang.code.startsWith(q)) return 500;
436
+ }
437
+ if (name.includes(q) || native.includes(q)) return 300;
438
+ for (const lang of country.languages) {
439
+ const label = normalize(lang.label);
440
+ const nativeLabel = lang.nativeLabel ? normalize(lang.nativeLabel) : "";
441
+ if (label.includes(q) || nativeLabel.includes(q)) return 100;
442
+ }
443
+ return null;
444
+ }
445
+ function filterCountries(countries, query) {
446
+ if (!query.trim()) return countries;
447
+ return countries.map((c) => ({ c, s: scoreCountry(c, query) })).filter((x) => x.s !== null).sort((a, b) => b.s - a.s || a.c.name.localeCompare(b.c.name)).map((x) => x.c);
448
+ }
449
+ function ChevronDown(props) {
450
+ return (0, import_jsx_runtime.jsx)(
451
+ "svg",
452
+ {
453
+ viewBox: "0 0 16 16",
454
+ fill: "none",
455
+ stroke: "currentColor",
456
+ strokeWidth: "2",
457
+ strokeLinecap: "round",
458
+ strokeLinejoin: "round",
459
+ "aria-hidden": "true",
460
+ ...props,
461
+ children: (0, import_jsx_runtime.jsx)("path", { d: "m4 6 4 4 4-4" })
462
+ }
463
+ );
464
+ }
465
+ function SearchIcon(props) {
466
+ return (0, import_jsx_runtime.jsxs)(
467
+ "svg",
468
+ {
469
+ viewBox: "0 0 16 16",
470
+ fill: "none",
471
+ stroke: "currentColor",
472
+ strokeWidth: "2",
473
+ strokeLinecap: "round",
474
+ strokeLinejoin: "round",
475
+ "aria-hidden": "true",
476
+ ...props,
477
+ children: [
478
+ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "4.5" }),
479
+ (0, import_jsx_runtime.jsx)("path", { d: "m14 14-3.4-3.4" })
480
+ ]
481
+ }
482
+ );
483
+ }
484
+ function CheckIcon(props) {
485
+ return (0, import_jsx_runtime.jsx)(
486
+ "svg",
487
+ {
488
+ viewBox: "0 0 16 16",
489
+ fill: "none",
490
+ stroke: "currentColor",
491
+ strokeWidth: "2.25",
492
+ strokeLinecap: "round",
493
+ strokeLinejoin: "round",
494
+ "aria-hidden": "true",
495
+ ...props,
496
+ children: (0, import_jsx_runtime.jsx)("path", { d: "m3.5 8.5 3 3 6-6" })
497
+ }
498
+ );
499
+ }
500
+ function ArrowLeft(props) {
501
+ return (0, import_jsx_runtime.jsxs)(
502
+ "svg",
503
+ {
504
+ viewBox: "0 0 16 16",
505
+ fill: "none",
506
+ stroke: "currentColor",
507
+ strokeWidth: "2",
508
+ strokeLinecap: "round",
509
+ strokeLinejoin: "round",
510
+ "aria-hidden": "true",
511
+ ...props,
512
+ children: [
513
+ (0, import_jsx_runtime.jsx)("path", { d: "M10 4 6 8l4 4" }),
514
+ (0, import_jsx_runtime.jsx)("path", { d: "M6 8h7" })
515
+ ]
516
+ }
517
+ );
518
+ }
519
+ var CountryLanguageSelector = (0, import_react.forwardRef)(function CountryLanguageSelector2(props, ref) {
520
+ const {
521
+ countries = defaultCountries,
522
+ value,
523
+ defaultValue,
524
+ onChange,
525
+ persistKey,
526
+ ariaLabel,
527
+ triggerVariant = "compact",
528
+ align = "end",
529
+ disabled,
530
+ className,
531
+ popoverClassName,
532
+ renderTrigger,
533
+ strings: stringsProp
534
+ } = props;
535
+ const strings = (0, import_react.useMemo)(
536
+ () => ({ ...defaultStrings, ...stringsProp ?? {} }),
537
+ [stringsProp]
538
+ );
539
+ const { country, language, setCountry, setLanguage } = useCountryLanguage({
540
+ countries,
541
+ value,
542
+ defaultValue,
543
+ onChange,
544
+ persistKey
545
+ });
546
+ const [open, setOpen] = (0, import_react.useState)(false);
547
+ const [step, setStep] = (0, import_react.useState)("country");
548
+ const [query, setQuery] = (0, import_react.useState)("");
549
+ const [activeIndex, setActiveIndex] = (0, import_react.useState)(0);
550
+ const [isMobile, setIsMobile] = (0, import_react.useState)(false);
551
+ const rootRef = (0, import_react.useRef)(null);
552
+ const popoverRef = (0, import_react.useRef)(null);
553
+ const triggerRef = (0, import_react.useRef)(null);
554
+ const searchRef = (0, import_react.useRef)(null);
555
+ const listboxId = (0, import_react.useId)();
556
+ const activeOptionId = `${listboxId}-opt-${activeIndex}`;
557
+ useClickOutside([rootRef, popoverRef], open, () => setOpen(false));
558
+ useFocusTrap(popoverRef, open);
559
+ (0, import_react.useEffect)(() => setIsMobile(isCoarsePointer()), []);
560
+ (0, import_react.useEffect)(() => {
561
+ if (!open) return;
562
+ setStep("country");
563
+ setQuery("");
564
+ setActiveIndex(0);
565
+ const t = window.setTimeout(() => {
566
+ var _a;
567
+ return (_a = searchRef.current) == null ? void 0 : _a.focus();
568
+ }, 10);
569
+ return () => window.clearTimeout(t);
570
+ }, [open]);
571
+ const filtered = (0, import_react.useMemo)(() => filterCountries(countries, query), [countries, query]);
572
+ (0, import_react.useEffect)(() => {
573
+ if (activeIndex > Math.max(0, filtered.length - 1)) setActiveIndex(0);
574
+ }, [filtered.length, activeIndex]);
575
+ const closeAndRefocus = (0, import_react.useCallback)(() => {
576
+ setOpen(false);
577
+ requestAnimationFrame(() => {
578
+ var _a;
579
+ return (_a = triggerRef.current) == null ? void 0 : _a.focus();
580
+ });
581
+ }, []);
582
+ const commitCountry = (0, import_react.useCallback)(
583
+ (c) => {
584
+ setCountry(c.code);
585
+ if (c.languages.length > 1) {
586
+ setStep("language");
587
+ setActiveIndex(0);
588
+ } else {
589
+ closeAndRefocus();
590
+ }
591
+ },
592
+ [setCountry, closeAndRefocus]
593
+ );
594
+ const commitLanguage = (0, import_react.useCallback)(
595
+ (l) => {
596
+ setLanguage(l.code);
597
+ closeAndRefocus();
598
+ },
599
+ [setLanguage, closeAndRefocus]
600
+ );
601
+ const onTriggerKeyDown = (0, import_react.useCallback)(
602
+ (e) => {
603
+ if (disabled) return;
604
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " " || e.key === "ArrowUp") {
605
+ e.preventDefault();
606
+ setOpen(true);
607
+ }
608
+ },
609
+ [disabled]
610
+ );
611
+ const onListKeyDown = (0, import_react.useCallback)(
612
+ (e) => {
613
+ const list = step === "country" ? filtered : country.languages;
614
+ if (list.length === 0) return;
615
+ if (e.key === "ArrowDown") {
616
+ e.preventDefault();
617
+ setActiveIndex((i) => (i + 1) % list.length);
618
+ } else if (e.key === "ArrowUp") {
619
+ e.preventDefault();
620
+ setActiveIndex((i) => (i - 1 + list.length) % list.length);
621
+ } else if (e.key === "Home") {
622
+ e.preventDefault();
623
+ setActiveIndex(0);
624
+ } else if (e.key === "End") {
625
+ e.preventDefault();
626
+ setActiveIndex(list.length - 1);
627
+ } else if (e.key === "Enter") {
628
+ e.preventDefault();
629
+ const item = list[activeIndex];
630
+ if (!item) return;
631
+ if (step === "country") commitCountry(item);
632
+ else commitLanguage(item);
633
+ } else if (e.key === "Escape") {
634
+ e.preventDefault();
635
+ closeAndRefocus();
636
+ } else if (e.key === "Backspace" && step === "language" && !query) {
637
+ e.preventDefault();
638
+ setStep("country");
639
+ setActiveIndex(0);
640
+ }
641
+ },
642
+ [step, filtered, country, activeIndex, commitCountry, commitLanguage, closeAndRefocus, query]
643
+ );
644
+ (0, import_react.useEffect)(() => {
645
+ const el = document.getElementById(activeOptionId);
646
+ if (el) el.scrollIntoView({ block: "nearest" });
647
+ }, [activeOptionId]);
648
+ return (0, import_jsx_runtime.jsxs)(
649
+ "div",
650
+ {
651
+ ref: (el) => {
652
+ rootRef.current = el;
653
+ if (typeof ref === "function") ref(el);
654
+ else if (ref) ref.current = el;
655
+ },
656
+ className: ["cls-root", className].filter(Boolean).join(" "),
657
+ children: [
658
+ (0, import_jsx_runtime.jsxs)(
659
+ "button",
660
+ {
661
+ ref: triggerRef,
662
+ type: "button",
663
+ className: "cls-trigger",
664
+ "aria-haspopup": "dialog",
665
+ "aria-expanded": open,
666
+ "aria-label": ariaLabel ?? strings.ariaLabel,
667
+ disabled,
668
+ onClick: () => !disabled && setOpen((v) => !v),
669
+ onKeyDown: onTriggerKeyDown,
670
+ children: [
671
+ renderTrigger ? renderTrigger({ country, language, open }) : (0, import_jsx_runtime.jsx)(TriggerContent, { variant: triggerVariant, country, language }),
672
+ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "cls-trigger__chev" })
673
+ ]
674
+ }
675
+ ),
676
+ open && isMobile && (0, import_jsx_runtime.jsx)("div", { className: "cls-backdrop", "aria-hidden": "true" }),
677
+ open && (0, import_jsx_runtime.jsx)(
678
+ "div",
679
+ {
680
+ ref: popoverRef,
681
+ role: "dialog",
682
+ "aria-label": strings.ariaLabel,
683
+ "data-align": align,
684
+ tabIndex: -1,
685
+ className: ["cls-popover", popoverClassName].filter(Boolean).join(" "),
686
+ onKeyDown: onListKeyDown,
687
+ children: step === "country" ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
688
+ (0, import_jsx_runtime.jsxs)("div", { className: "cls-search", children: [
689
+ (0, import_jsx_runtime.jsx)(SearchIcon, { className: "cls-search__icon" }),
690
+ (0, import_jsx_runtime.jsx)(
691
+ "input",
692
+ {
693
+ ref: searchRef,
694
+ type: "text",
695
+ role: "combobox",
696
+ "aria-expanded": "true",
697
+ "aria-controls": listboxId,
698
+ "aria-activedescendant": activeOptionId,
699
+ "aria-autocomplete": "list",
700
+ className: "cls-search__input",
701
+ placeholder: strings.searchPlaceholder,
702
+ value: query,
703
+ onChange: (e) => {
704
+ setQuery(e.target.value);
705
+ setActiveIndex(0);
706
+ }
707
+ }
708
+ )
709
+ ] }),
710
+ (0, import_jsx_runtime.jsx)("div", { className: "cls-section-title", children: (0, import_jsx_runtime.jsx)("span", { children: strings.chooseCountry }) }),
711
+ filtered.length === 0 ? (0, import_jsx_runtime.jsx)("div", { className: "cls-empty", children: strings.emptyResults }) : (0, import_jsx_runtime.jsx)(
712
+ "ul",
713
+ {
714
+ id: listboxId,
715
+ role: "listbox",
716
+ "aria-label": strings.chooseCountry,
717
+ className: "cls-list",
718
+ children: filtered.map((c, i) => {
719
+ const selected = c.code === country.code;
720
+ return (0, import_jsx_runtime.jsxs)(
721
+ "li",
722
+ {
723
+ id: `${listboxId}-opt-${i}`,
724
+ role: "option",
725
+ "aria-selected": selected,
726
+ "data-active": i === activeIndex,
727
+ className: "cls-option",
728
+ onMouseEnter: () => setActiveIndex(i),
729
+ onClick: () => commitCountry(c),
730
+ children: [
731
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-option__flag", "aria-hidden": "true", children: c.flag }),
732
+ (0, import_jsx_runtime.jsxs)("span", { className: "cls-option__body", children: [
733
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-option__title", children: c.name }),
734
+ c.nativeName && c.nativeName !== c.name && (0, import_jsx_runtime.jsx)("span", { className: "cls-option__subtitle", children: c.nativeName })
735
+ ] }),
736
+ (0, import_jsx_runtime.jsxs)("span", { className: "cls-option__meta", children: [
737
+ c.languages.length > 1 ? (0, import_jsx_runtime.jsxs)("span", { className: "cls-badge", children: [
738
+ c.languages.length,
739
+ " langs"
740
+ ] }) : (0, import_jsx_runtime.jsx)("span", { className: "cls-badge", children: c.languages[0].code.toUpperCase() }),
741
+ selected && (0, import_jsx_runtime.jsx)(CheckIcon, { className: "cls-check" })
742
+ ] })
743
+ ]
744
+ },
745
+ c.code
746
+ );
747
+ })
748
+ }
749
+ )
750
+ ] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
751
+ (0, import_jsx_runtime.jsxs)("div", { className: "cls-section-title", children: [
752
+ (0, import_jsx_runtime.jsxs)(
753
+ "button",
754
+ {
755
+ type: "button",
756
+ className: "cls-back",
757
+ onClick: () => {
758
+ setStep("country");
759
+ setActiveIndex(0);
760
+ },
761
+ children: [
762
+ (0, import_jsx_runtime.jsx)(ArrowLeft, { width: 12, height: 12 }),
763
+ strings.backToCountries
764
+ ]
765
+ }
766
+ ),
767
+ (0, import_jsx_runtime.jsxs)("span", { children: [
768
+ country.flag,
769
+ " ",
770
+ country.name
771
+ ] })
772
+ ] }),
773
+ (0, import_jsx_runtime.jsx)(
774
+ "ul",
775
+ {
776
+ id: listboxId,
777
+ role: "listbox",
778
+ "aria-label": strings.chooseLanguage,
779
+ className: "cls-list",
780
+ children: country.languages.map((l, i) => {
781
+ const selected = l.code === language.code;
782
+ return (0, import_jsx_runtime.jsxs)(
783
+ "li",
784
+ {
785
+ id: `${listboxId}-opt-${i}`,
786
+ role: "option",
787
+ "aria-selected": selected,
788
+ "data-active": i === activeIndex,
789
+ className: "cls-option",
790
+ onMouseEnter: () => setActiveIndex(i),
791
+ onClick: () => commitLanguage(l),
792
+ children: [
793
+ (0, import_jsx_runtime.jsxs)("span", { className: "cls-option__body", children: [
794
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-option__title", children: l.label }),
795
+ l.nativeLabel && l.nativeLabel !== l.label && (0, import_jsx_runtime.jsx)("span", { className: "cls-option__subtitle", children: l.nativeLabel })
796
+ ] }),
797
+ (0, import_jsx_runtime.jsxs)("span", { className: "cls-option__meta", children: [
798
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-badge", children: l.code.toUpperCase() }),
799
+ selected && (0, import_jsx_runtime.jsx)(CheckIcon, { className: "cls-check" })
800
+ ] })
801
+ ]
802
+ },
803
+ l.code
804
+ );
805
+ })
806
+ }
807
+ )
808
+ ] })
809
+ }
810
+ )
811
+ ]
812
+ }
813
+ );
814
+ });
815
+ function TriggerContent({
816
+ variant,
817
+ country,
818
+ language
819
+ }) {
820
+ if (variant === "flag") {
821
+ return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
822
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__flag", "aria-hidden": "true", children: country.flag }),
823
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__lang", children: language.code })
824
+ ] });
825
+ }
826
+ if (variant === "full") {
827
+ return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
828
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__flag", "aria-hidden": "true", children: country.flag }),
829
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__code", children: country.name }),
830
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__sep", "aria-hidden": "true", children: "·" }),
831
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__lang", children: language.label })
832
+ ] });
833
+ }
834
+ return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
835
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__flag", "aria-hidden": "true", children: country.flag }),
836
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__code", children: country.code }),
837
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__sep", "aria-hidden": "true", children: "·" }),
838
+ (0, import_jsx_runtime.jsx)("span", { className: "cls-trigger__lang", children: language.code })
839
+ ] });
840
+ }
841
+ export {
842
+ CountryLanguageSelector,
843
+ defaultCountries,
844
+ filterCountries,
845
+ languageCatalogue,
846
+ useCountryLanguage
847
+ };
848
+ //# sourceMappingURL=@asafarim_country-language-selector.js.map