telpick 1.0.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.

Potentially problematic release.


This version of telpick might be problematic. Click here for more details.

Files changed (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/dist/style.css +1 -0
  4. package/dist/telpick.es.js +241 -0
  5. package/dist/telpick.umd.js +1 -0
  6. package/package.json +93 -0
  7. package/src/assets/country-code.json +94 -0
  8. package/src/assets/flags/ad.webp +0 -0
  9. package/src/assets/flags/ae.webp +0 -0
  10. package/src/assets/flags/af.png +0 -0
  11. package/src/assets/flags/ag.webp +0 -0
  12. package/src/assets/flags/ai.webp +0 -0
  13. package/src/assets/flags/al.webp +0 -0
  14. package/src/assets/flags/am.webp +0 -0
  15. package/src/assets/flags/ao.webp +0 -0
  16. package/src/assets/flags/aq.webp +0 -0
  17. package/src/assets/flags/ar.webp +0 -0
  18. package/src/assets/flags/as.webp +0 -0
  19. package/src/assets/flags/at.webp +0 -0
  20. package/src/assets/flags/au.webp +0 -0
  21. package/src/assets/flags/aw.webp +0 -0
  22. package/src/assets/flags/ax.webp +0 -0
  23. package/src/assets/flags/az.webp +0 -0
  24. package/src/assets/flags/ba.webp +0 -0
  25. package/src/assets/flags/bb.webp +0 -0
  26. package/src/assets/flags/bd.webp +0 -0
  27. package/src/assets/flags/be.webp +0 -0
  28. package/src/assets/flags/bf.webp +0 -0
  29. package/src/assets/flags/bg.webp +0 -0
  30. package/src/assets/flags/bh.webp +0 -0
  31. package/src/assets/flags/bi.webp +0 -0
  32. package/src/assets/flags/bj.webp +0 -0
  33. package/src/assets/flags/bl.webp +0 -0
  34. package/src/assets/flags/bm.webp +0 -0
  35. package/src/assets/flags/bn.webp +0 -0
  36. package/src/assets/flags/bo.webp +0 -0
  37. package/src/assets/flags/bq.webp +0 -0
  38. package/src/assets/flags/br.webp +0 -0
  39. package/src/assets/flags/bs.webp +0 -0
  40. package/src/assets/flags/bt.webp +0 -0
  41. package/src/assets/flags/bv.webp +0 -0
  42. package/src/assets/flags/bw.webp +0 -0
  43. package/src/assets/flags/by.webp +0 -0
  44. package/src/assets/flags/bz.webp +0 -0
  45. package/src/assets/flags/ca.webp +0 -0
  46. package/src/assets/flags/cc.webp +0 -0
  47. package/src/assets/flags/cd.webp +0 -0
  48. package/src/assets/flags/cf.webp +0 -0
  49. package/src/assets/flags/cg.webp +0 -0
  50. package/src/assets/flags/ch.webp +0 -0
  51. package/src/assets/flags/ci.webp +0 -0
  52. package/src/assets/flags/ck.webp +0 -0
  53. package/src/assets/flags/cl.webp +0 -0
  54. package/src/assets/flags/cm.webp +0 -0
  55. package/src/assets/flags/cn.webp +0 -0
  56. package/src/assets/flags/co.webp +0 -0
  57. package/src/assets/flags/cr.webp +0 -0
  58. package/src/assets/flags/cu.webp +0 -0
  59. package/src/assets/flags/cv.webp +0 -0
  60. package/src/assets/flags/cw.webp +0 -0
  61. package/src/assets/flags/cx.webp +0 -0
  62. package/src/assets/flags/cy.webp +0 -0
  63. package/src/assets/flags/cz.webp +0 -0
  64. package/src/assets/flags/de.webp +0 -0
  65. package/src/assets/flags/dj.webp +0 -0
  66. package/src/assets/flags/dk.webp +0 -0
  67. package/src/assets/flags/dm.webp +0 -0
  68. package/src/assets/flags/do.webp +0 -0
  69. package/src/assets/flags/dz.webp +0 -0
  70. package/src/assets/flags/ec.webp +0 -0
  71. package/src/assets/flags/ee.webp +0 -0
  72. package/src/assets/flags/eg.webp +0 -0
  73. package/src/assets/flags/eh.webp +0 -0
  74. package/src/assets/flags/er.webp +0 -0
  75. package/src/assets/flags/es.webp +0 -0
  76. package/src/assets/flags/et.webp +0 -0
  77. package/src/assets/flags/fi.webp +0 -0
  78. package/src/assets/flags/fj.webp +0 -0
  79. package/src/assets/flags/fk.webp +0 -0
  80. package/src/assets/flags/fm.webp +0 -0
  81. package/src/assets/flags/fo.webp +0 -0
  82. package/src/assets/flags/fr.webp +0 -0
  83. package/src/assets/flags/ga.webp +0 -0
  84. package/src/assets/flags/gb-eng.webp +0 -0
  85. package/src/assets/flags/gb-nir.webp +0 -0
  86. package/src/assets/flags/gb-sct.webp +0 -0
  87. package/src/assets/flags/gb-wls.webp +0 -0
  88. package/src/assets/flags/gb.webp +0 -0
  89. package/src/assets/flags/gd.webp +0 -0
  90. package/src/assets/flags/ge.webp +0 -0
  91. package/src/assets/flags/gf.webp +0 -0
  92. package/src/assets/flags/gg.webp +0 -0
  93. package/src/assets/flags/gh.webp +0 -0
  94. package/src/assets/flags/gi.webp +0 -0
  95. package/src/assets/flags/gl.webp +0 -0
  96. package/src/assets/flags/gm.webp +0 -0
  97. package/src/assets/flags/gn.webp +0 -0
  98. package/src/assets/flags/gp.webp +0 -0
  99. package/src/assets/flags/gq.webp +0 -0
  100. package/src/assets/flags/gr.webp +0 -0
  101. package/src/assets/flags/gs.webp +0 -0
  102. package/src/assets/flags/gt.webp +0 -0
  103. package/src/assets/flags/gu.webp +0 -0
  104. package/src/assets/flags/gw.webp +0 -0
  105. package/src/assets/flags/gy.webp +0 -0
  106. package/src/assets/flags/hk.webp +0 -0
  107. package/src/assets/flags/hm.webp +0 -0
  108. package/src/assets/flags/hn.webp +0 -0
  109. package/src/assets/flags/hr.webp +0 -0
  110. package/src/assets/flags/ht.webp +0 -0
  111. package/src/assets/flags/hu.webp +0 -0
  112. package/src/assets/flags/id.webp +0 -0
  113. package/src/assets/flags/ie.webp +0 -0
  114. package/src/assets/flags/il.webp +0 -0
  115. package/src/assets/flags/im.webp +0 -0
  116. package/src/assets/flags/in.webp +0 -0
  117. package/src/assets/flags/io.webp +0 -0
  118. package/src/assets/flags/iq.webp +0 -0
  119. package/src/assets/flags/ir.webp +0 -0
  120. package/src/assets/flags/is.webp +0 -0
  121. package/src/assets/flags/it.webp +0 -0
  122. package/src/assets/flags/je.webp +0 -0
  123. package/src/assets/flags/jm.webp +0 -0
  124. package/src/assets/flags/jo.webp +0 -0
  125. package/src/assets/flags/jp.webp +0 -0
  126. package/src/assets/flags/ke.webp +0 -0
  127. package/src/assets/flags/kg.webp +0 -0
  128. package/src/assets/flags/kh.webp +0 -0
  129. package/src/assets/flags/ki.webp +0 -0
  130. package/src/assets/flags/km.webp +0 -0
  131. package/src/assets/flags/kn.webp +0 -0
  132. package/src/assets/flags/kp.webp +0 -0
  133. package/src/assets/flags/kr.webp +0 -0
  134. package/src/assets/flags/kw.webp +0 -0
  135. package/src/assets/flags/ky.webp +0 -0
  136. package/src/assets/flags/kz.webp +0 -0
  137. package/src/assets/flags/la.webp +0 -0
  138. package/src/assets/flags/lb.webp +0 -0
  139. package/src/assets/flags/lc.webp +0 -0
  140. package/src/assets/flags/li.webp +0 -0
  141. package/src/assets/flags/lista.txt +0 -0
  142. package/src/assets/flags/lk.webp +0 -0
  143. package/src/assets/flags/lr.webp +0 -0
  144. package/src/assets/flags/ls.webp +0 -0
  145. package/src/assets/flags/lt.webp +0 -0
  146. package/src/assets/flags/lu.webp +0 -0
  147. package/src/assets/flags/lv.webp +0 -0
  148. package/src/assets/flags/ly.webp +0 -0
  149. package/src/assets/flags/ma.webp +0 -0
  150. package/src/assets/flags/mc.webp +0 -0
  151. package/src/assets/flags/md.webp +0 -0
  152. package/src/assets/flags/me.webp +0 -0
  153. package/src/assets/flags/mf.webp +0 -0
  154. package/src/assets/flags/mg.webp +0 -0
  155. package/src/assets/flags/mh.webp +0 -0
  156. package/src/assets/flags/mk.webp +0 -0
  157. package/src/assets/flags/ml.webp +0 -0
  158. package/src/assets/flags/mm.webp +0 -0
  159. package/src/assets/flags/mn.webp +0 -0
  160. package/src/assets/flags/mo.webp +0 -0
  161. package/src/assets/flags/mp.webp +0 -0
  162. package/src/assets/flags/mq.webp +0 -0
  163. package/src/assets/flags/mr.webp +0 -0
  164. package/src/assets/flags/ms.webp +0 -0
  165. package/src/assets/flags/mt.webp +0 -0
  166. package/src/assets/flags/mu.webp +0 -0
  167. package/src/assets/flags/mv.webp +0 -0
  168. package/src/assets/flags/mw.webp +0 -0
  169. package/src/assets/flags/mx.webp +0 -0
  170. package/src/assets/flags/my.webp +0 -0
  171. package/src/assets/flags/mz.webp +0 -0
  172. package/src/assets/flags/na.webp +0 -0
  173. package/src/assets/flags/nc.webp +0 -0
  174. package/src/assets/flags/ne.webp +0 -0
  175. package/src/assets/flags/nf.webp +0 -0
  176. package/src/assets/flags/ng.webp +0 -0
  177. package/src/assets/flags/ni.webp +0 -0
  178. package/src/assets/flags/nl.webp +0 -0
  179. package/src/assets/flags/no.webp +0 -0
  180. package/src/assets/flags/np.webp +0 -0
  181. package/src/assets/flags/nr.webp +0 -0
  182. package/src/assets/flags/nu.webp +0 -0
  183. package/src/assets/flags/nz.webp +0 -0
  184. package/src/assets/flags/om.webp +0 -0
  185. package/src/assets/flags/pa.webp +0 -0
  186. package/src/assets/flags/pe.webp +0 -0
  187. package/src/assets/flags/pf.webp +0 -0
  188. package/src/assets/flags/pg.webp +0 -0
  189. package/src/assets/flags/ph.webp +0 -0
  190. package/src/assets/flags/pk.webp +0 -0
  191. package/src/assets/flags/pl.webp +0 -0
  192. package/src/assets/flags/pm.webp +0 -0
  193. package/src/assets/flags/pn.webp +0 -0
  194. package/src/assets/flags/pr.webp +0 -0
  195. package/src/assets/flags/ps.webp +0 -0
  196. package/src/assets/flags/pt.webp +0 -0
  197. package/src/assets/flags/pw.webp +0 -0
  198. package/src/assets/flags/py.webp +0 -0
  199. package/src/assets/flags/qa.webp +0 -0
  200. package/src/assets/flags/re.webp +0 -0
  201. package/src/assets/flags/ro.webp +0 -0
  202. package/src/assets/flags/rs.webp +0 -0
  203. package/src/assets/flags/ru.webp +0 -0
  204. package/src/assets/flags/rw.webp +0 -0
  205. package/src/assets/flags/sa.webp +0 -0
  206. package/src/assets/flags/sb.webp +0 -0
  207. package/src/assets/flags/sc.webp +0 -0
  208. package/src/assets/flags/sd.webp +0 -0
  209. package/src/assets/flags/se (1).webp +0 -0
  210. package/src/assets/flags/se.webp +0 -0
  211. package/src/assets/flags/sg.webp +0 -0
  212. package/src/assets/flags/sh.webp +0 -0
  213. package/src/assets/flags/si.webp +0 -0
  214. package/src/assets/flags/sj.webp +0 -0
  215. package/src/assets/flags/sk.webp +0 -0
  216. package/src/assets/flags/sl.webp +0 -0
  217. package/src/assets/flags/sm.webp +0 -0
  218. package/src/assets/flags/sn.webp +0 -0
  219. package/src/assets/flags/so.webp +0 -0
  220. package/src/assets/flags/sr.webp +0 -0
  221. package/src/assets/flags/ss.webp +0 -0
  222. package/src/assets/flags/st.webp +0 -0
  223. package/src/assets/flags/sv.webp +0 -0
  224. package/src/assets/flags/sx.webp +0 -0
  225. package/src/assets/flags/sy.webp +0 -0
  226. package/src/assets/flags/sz.webp +0 -0
  227. package/src/assets/flags/tc.webp +0 -0
  228. package/src/assets/flags/td.webp +0 -0
  229. package/src/assets/flags/tf.webp +0 -0
  230. package/src/assets/flags/tg.webp +0 -0
  231. package/src/assets/flags/th.webp +0 -0
  232. package/src/assets/flags/tj.webp +0 -0
  233. package/src/assets/flags/tk.webp +0 -0
  234. package/src/assets/flags/tl.webp +0 -0
  235. package/src/assets/flags/tm.webp +0 -0
  236. package/src/assets/flags/tn.webp +0 -0
  237. package/src/assets/flags/to.webp +0 -0
  238. package/src/assets/flags/tr.webp +0 -0
  239. package/src/assets/flags/tt.webp +0 -0
  240. package/src/assets/flags/tv.webp +0 -0
  241. package/src/assets/flags/tw.webp +0 -0
  242. package/src/assets/flags/tz.webp +0 -0
  243. package/src/assets/flags/ua.webp +0 -0
  244. package/src/assets/flags/ug.webp +0 -0
  245. package/src/assets/flags/um.webp +0 -0
  246. package/src/assets/flags/us.webp +0 -0
  247. package/src/assets/flags/uy.webp +0 -0
  248. package/src/assets/flags/uz.webp +0 -0
  249. package/src/assets/flags/va.webp +0 -0
  250. package/src/assets/flags/vc.webp +0 -0
  251. package/src/assets/flags/ve.webp +0 -0
  252. package/src/assets/flags/vg.webp +0 -0
  253. package/src/assets/flags/vi.webp +0 -0
  254. package/src/assets/flags/vn.webp +0 -0
  255. package/src/assets/flags/vu.webp +0 -0
  256. package/src/assets/flags/wf.webp +0 -0
  257. package/src/assets/flags/ws.webp +0 -0
  258. package/src/assets/flags/xk.webp +0 -0
  259. package/src/assets/flags/ye.webp +0 -0
  260. package/src/assets/flags/yt.webp +0 -0
  261. package/src/assets/flags/za.webp +0 -0
  262. package/src/assets/flags/zm.webp +0 -0
  263. package/src/assets/flags/zw.webp +0 -0
  264. package/src/telpick.cdn.js +7 -0
  265. package/src/telpick.css +289 -0
  266. package/src/telpick.js +273 -0
  267. package/src/telpick.react.tsx +140 -0
  268. package/src/telpick.ts +157 -0
  269. package/src/telpick.vanilla.js +199 -0
  270. package/src/telpick.vue.ts +144 -0
  271. package/src/telpick.wrappers.jsx +37 -0
package/src/telpick.js ADDED
@@ -0,0 +1,273 @@
1
+ class Telpick {
2
+ constructor({ code = null, onChange = () => {}, styleOverrides = {} } = {}) {
3
+ this.code = code
4
+ this.onChange = onChange
5
+ this.styleOverrides = styleOverrides
6
+ this.codes = []
7
+ this.selectedCode = code
8
+ this.isDropdownOpen = false
9
+ this.searchQuery = ''
10
+ this.container = null
11
+ this.dropdown = null
12
+ this._outsideHandler = null
13
+ }
14
+
15
+ async init(container) {
16
+ this.container = container
17
+ const res = await fetch('src/assets/country-code.json')
18
+ const data = await res.json()
19
+ this.codes = data.sort((a, b) => a.country.localeCompare(b.country, 'es'))
20
+ if (!this.code) {
21
+ const services = [
22
+ async () => {
23
+ try {
24
+ const res = await fetch('https://ip-api.com/json/?fields=countryCode')
25
+ const data = await res.json()
26
+ if (data.countryCode) {
27
+ return this.codes.find(c => c.country_code === data.countryCode)
28
+ }
29
+ } catch {}
30
+ return null
31
+ },
32
+ async () => {
33
+ try {
34
+ const res = await fetch('https://get.geojs.io/v1/ip/country.json')
35
+ const data = await res.json()
36
+ if (data.country) {
37
+ return this.codes.find(c => c.country_code === data.country)
38
+ }
39
+ } catch {}
40
+ return null
41
+ },
42
+ async () => {
43
+ try {
44
+ const res = await fetch('https://ipapi.co/json/')
45
+ const data = await res.json()
46
+ if (data.country_code) {
47
+ return this.codes.find(c => c.country_code === data.country_code)
48
+ }
49
+ } catch {}
50
+ return null
51
+ }
52
+ ]
53
+
54
+ let found = null
55
+ for (const service of services) {
56
+ try {
57
+ found = await Promise.race([
58
+ service(),
59
+ new Promise((resolve) => setTimeout(() => resolve(null), 3000))
60
+ ])
61
+ if (found) break
62
+ } catch {}
63
+ }
64
+
65
+ if (!found) {
66
+ try {
67
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
68
+ const timezoneToCountry = {
69
+ 'America/Lima': 'PE', 'America/Bogota': 'CO', 'America/Mexico_City': 'MX',
70
+ 'America/Argentina/Buenos_Aires': 'AR', 'America/Santiago': 'CL',
71
+ 'America/Caracas': 'VE', 'America/Montevideo': 'UY', 'America/Asuncion': 'PY',
72
+ 'America/La_Paz': 'BO', 'America/Guayaquil': 'EC', 'America/Panama': 'PA',
73
+ 'America/Costa_Rica': 'CR', 'America/Managua': 'NI', 'America/Tegucigalpa': 'HN',
74
+ 'America/Guatemala': 'GT', 'America/El_Salvador': 'SV', 'America/Havana': 'CU',
75
+ 'America/Santo_Domingo': 'DO', 'America/Jamaica': 'JM', 'America/Port-au-Prince': 'HT',
76
+ 'Europe/Madrid': 'ES', 'Europe/London': 'GB', 'Europe/Paris': 'FR',
77
+ 'Europe/Berlin': 'DE', 'Europe/Rome': 'IT', 'Europe/Amsterdam': 'NL',
78
+ 'Europe/Brussels': 'BE', 'Europe/Vienna': 'AT', 'Europe/Zurich': 'CH',
79
+ 'Europe/Stockholm': 'SE', 'Europe/Oslo': 'NO', 'Europe/Copenhagen': 'DK',
80
+ 'Europe/Helsinki': 'FI', 'Europe/Warsaw': 'PL', 'Europe/Prague': 'CZ',
81
+ 'Europe/Bucharest': 'RO', 'Europe/Moscow': 'RU',
82
+ 'America/New_York': 'US', 'America/Chicago': 'US', 'America/Denver': 'US',
83
+ 'America/Los_Angeles': 'US', 'America/Toronto': 'CA', 'America/Vancouver': 'CA',
84
+ 'Asia/Tokyo': 'JP', 'Asia/Shanghai': 'CN', 'Asia/Hong_Kong': 'CN',
85
+ 'Asia/Seoul': 'KR', 'Asia/Singapore': 'SG', 'Asia/Bangkok': 'TH',
86
+ 'Asia/Jakarta': 'ID', 'Asia/Manila': 'PH', 'Asia/Kolkata': 'IN',
87
+ 'Asia/Dubai': 'AE', 'Asia/Riyadh': 'SA',
88
+ 'Australia/Sydney': 'AU', 'Australia/Melbourne': 'AU', 'Pacific/Auckland': 'NZ',
89
+ 'Africa/Cairo': 'EG', 'Africa/Johannesburg': 'ZA', 'Africa/Nairobi': 'KE',
90
+ 'Africa/Lagos': 'NG'
91
+ }
92
+ const countryCode = timezoneToCountry[timezone]
93
+ if (countryCode) {
94
+ found = this.codes.find(c => c.country_code === countryCode)
95
+ }
96
+ } catch {}
97
+ }
98
+
99
+ if (found) {
100
+ this.selectedCode = found.country_code
101
+ } else {
102
+ const defaultCountry = this.codes.find(c => c.country_code === 'CO')
103
+ if (defaultCountry) this.selectedCode = defaultCountry.country_code
104
+ }
105
+ } else {
106
+ const found = this.codes.find(c => c.country_code === this.code)
107
+ if (found) this.selectedCode = found.country_code
108
+ }
109
+ this.render()
110
+ const selected = this.codes.find(c => c.country_code === this.selectedCode)
111
+ if (selected) this.onChange(selected)
112
+ this._setupOutsideClick()
113
+ }
114
+
115
+ render() {
116
+ this.container.innerHTML = ''
117
+ const btn = document.createElement('button')
118
+ btn.className = 'telpick-btn'
119
+ Object.assign(btn.style, this.styleOverrides)
120
+ btn.onclick = () => {
121
+ this.isDropdownOpen = !this.isDropdownOpen
122
+ this.render()
123
+ }
124
+ const flagDiv = document.createElement('div')
125
+ flagDiv.className = 'telpick-flag'
126
+ const selectedCountry = this.codes.find(c => c.country_code === this.selectedCode) || { flag: '', code: '', country: '' }
127
+ if (selectedCountry.flag) {
128
+ const img = document.createElement('img')
129
+ img.src = 'src/assets' + selectedCountry.flag
130
+ img.className = 'w-full h-full object-cover'
131
+ img.alt = 'flag'
132
+ flagDiv.appendChild(img)
133
+ }
134
+ btn.appendChild(flagDiv)
135
+ const codeSpan = document.createElement('span')
136
+ codeSpan.textContent = selectedCountry.code
137
+ btn.appendChild(codeSpan)
138
+ const arrowSpan = document.createElement('span')
139
+ arrowSpan.className = 'ml-auto'
140
+ arrowSpan.textContent = '▼'
141
+ btn.appendChild(arrowSpan)
142
+ this.container.appendChild(btn)
143
+
144
+ if (this.isDropdownOpen) {
145
+ this.dropdown = document.createElement('div')
146
+ this.dropdown.className = 'telpick-dropdown'
147
+ this.dropdown.onclick = e => e.stopPropagation()
148
+ this.dropdown.onmousedown = e => e.stopPropagation()
149
+
150
+ const input = document.createElement('input')
151
+ input.className = 'telpick-search'
152
+ input.type = 'text'
153
+ input.placeholder = 'Buscar país...'
154
+ input.value = this.searchQuery
155
+ input.oninput = e => {
156
+ e.stopPropagation()
157
+ const inputEl = e.target
158
+ const cursorPos = inputEl.selectionStart || 0
159
+ const newValue = inputEl.value
160
+ this.searchQuery = newValue
161
+ const ul = this.dropdown?.querySelector('ul')
162
+ if (ul) {
163
+ ul.innerHTML = ''
164
+ const filtered = !this.searchQuery ? this.codes : this.codes.filter(c => c.country.toLowerCase().includes(this.searchQuery.toLowerCase()))
165
+ filtered.forEach(item => {
166
+ const li = document.createElement('li')
167
+ const isSelected = item.country_code === this.selectedCode && this.selectedCode !== null && this.selectedCode !== undefined
168
+ li.className = `telpick-item ${isSelected ? 'telpick-item-selected' : ''}`
169
+ li.onclick = () => {
170
+ this.selectedCode = item.country_code
171
+ this.onChange(item)
172
+ this.isDropdownOpen = false
173
+ this.searchQuery = ''
174
+ this.render()
175
+ }
176
+ const flag = document.createElement('div')
177
+ flag.className = 'telpick-flag'
178
+ const img = document.createElement('img')
179
+ img.src = 'src/assets' + item.flag
180
+ img.className = 'w-full h-full object-cover'
181
+ img.alt = 'flag'
182
+ flag.appendChild(img)
183
+ li.appendChild(flag)
184
+ const countrySpan = document.createElement('span')
185
+ countrySpan.textContent = item.country
186
+ li.appendChild(countrySpan)
187
+ const codeSpan = document.createElement('span')
188
+ codeSpan.className = 'ml-auto'
189
+ codeSpan.textContent = item.code
190
+ li.appendChild(codeSpan)
191
+ ul.appendChild(li)
192
+ })
193
+ requestAnimationFrame(() => {
194
+ if (input) {
195
+ input.focus()
196
+ const newCursorPos = Math.min(cursorPos + 1, newValue.length)
197
+ input.setSelectionRange(newCursorPos, newCursorPos)
198
+ }
199
+ })
200
+ } else {
201
+ this.render()
202
+ }
203
+ }
204
+ input.onclick = e => e.stopPropagation()
205
+ input.onmousedown = e => e.stopPropagation()
206
+ this.dropdown.appendChild(input)
207
+ const ul = document.createElement('ul')
208
+ ul.style.maxHeight = '130px'
209
+ ul.style.overflowY = 'auto'
210
+ const filtered = !this.searchQuery ? this.codes : this.codes.filter(c => c.country.toLowerCase().includes(this.searchQuery.toLowerCase()))
211
+ filtered.forEach(item => {
212
+ const li = document.createElement('li')
213
+ const isSelected = item.country_code === this.selectedCode && this.selectedCode !== null && this.selectedCode !== undefined
214
+ li.className = `telpick-item ${isSelected ? 'telpick-item-selected' : ''}`
215
+ li.setAttribute('aria-selected', isSelected)
216
+ li.onclick = () => {
217
+ this.selectedCode = item.country_code
218
+ this.onChange(item)
219
+ this.isDropdownOpen = false
220
+ this.searchQuery = ''
221
+ this.render()
222
+ }
223
+ const flag = document.createElement('div')
224
+ flag.className = 'telpick-flag'
225
+ const img = document.createElement('img')
226
+ img.src = 'src/assets' + item.flag
227
+ img.className = 'w-full h-full object-cover'
228
+ img.alt = 'flag'
229
+ flag.appendChild(img)
230
+ li.appendChild(flag)
231
+ const countrySpan = document.createElement('span')
232
+ countrySpan.textContent = item.country
233
+ li.appendChild(countrySpan)
234
+ const codeSpan = document.createElement('span')
235
+ codeSpan.className = 'ml-auto'
236
+ codeSpan.textContent = item.code
237
+ li.appendChild(codeSpan)
238
+ ul.appendChild(li)
239
+ })
240
+ this.dropdown.appendChild(ul)
241
+ this.container.appendChild(this.dropdown)
242
+
243
+ requestAnimationFrame(() => {
244
+ if (input) {
245
+ input.focus()
246
+ }
247
+ })
248
+ }
249
+ }
250
+
251
+ _setupOutsideClick() {
252
+ if (this._outsideHandler) document.removeEventListener('click', this._outsideHandler)
253
+ this._outsideHandler = e => {
254
+ const target = e.target
255
+ if (this.isDropdownOpen && this.container && target) {
256
+ const dropdown = this.container.querySelector('.telpick-dropdown')
257
+ if (!this.container.contains(target) && (!dropdown || !dropdown.contains(target))) {
258
+ this.isDropdownOpen = false
259
+ this.render()
260
+ }
261
+ }
262
+ }
263
+ document.addEventListener('click', this._outsideHandler, true)
264
+ }
265
+
266
+ destroy() {
267
+ if (this._outsideHandler) document.removeEventListener('mousedown', this._outsideHandler)
268
+ this.container.innerHTML = ''
269
+ }
270
+ }
271
+
272
+ window.Telpick = Telpick
273
+ export default Telpick
@@ -0,0 +1,140 @@
1
+ import React, { useState, useEffect, useRef, useMemo } from 'react'
2
+ import { getDefaultCountry, useClickOutside, CountryCode, TelpickProps } from './telpick'
3
+ import './telpick.css'
4
+
5
+ export function TelpickReact({ code, onChange, styleOverrides }: TelpickProps) {
6
+ const [codes, setCodes] = useState<CountryCode[]>([])
7
+ const [selectedCode, setSelectedCode] = useState(code)
8
+ const [isDropdownOpen, setDropdownOpen] = useState(false)
9
+ const [searchQuery, setSearchQuery] = useState('')
10
+ const dropdownRef = useRef<HTMLDivElement>(null)
11
+ const searchInputRef = useRef<HTMLInputElement>(null)
12
+
13
+ useEffect(() => {
14
+ fetch('/resources/api/country-codes.json')
15
+ .then(res => res.json())
16
+ .then(data => {
17
+ const sorted = data.sort((a, b) => a.country.localeCompare(b.country, 'es'))
18
+ setCodes(sorted)
19
+ if (!code) {
20
+ getDefaultCountry(data).then(def => {
21
+ if (def) {
22
+ setSelectedCode(def.country_code)
23
+ onChange && onChange(def)
24
+ }
25
+ })
26
+ } else {
27
+ setSelectedCode(code)
28
+ }
29
+ })
30
+ }, [code, onChange])
31
+
32
+ useEffect(() => {
33
+ if (code !== undefined && code !== selectedCode) {
34
+ setSelectedCode(code)
35
+ }
36
+ }, [code])
37
+
38
+ useEffect(() => {
39
+ if (dropdownRef.current) {
40
+ const remove = useClickOutside(dropdownRef.current, () => setDropdownOpen(false))
41
+ return remove
42
+ }
43
+ }, [dropdownRef])
44
+
45
+ useEffect(() => {
46
+ if (isDropdownOpen && searchInputRef.current) {
47
+ searchInputRef.current.focus()
48
+ }
49
+ }, [isDropdownOpen])
50
+
51
+ const selectCode = (item: CountryCode) => {
52
+ setSelectedCode(item.country_code)
53
+ onChange && onChange(item)
54
+ setDropdownOpen(false)
55
+ setSearchQuery('')
56
+ }
57
+
58
+ const selectedCountry = codes.find(c => c.country_code === selectedCode) || {
59
+ country_code: selectedCode,
60
+ flag: '',
61
+ country: '',
62
+ code: '',
63
+ }
64
+
65
+ const filteredCodes = useMemo(() => {
66
+ if (!searchQuery) return codes
67
+ return codes.filter(c => c.country.toLowerCase().includes(searchQuery.toLowerCase()))
68
+ }, [codes, searchQuery])
69
+
70
+ const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
71
+ const cursorPosition = e.target.selectionStart || 0
72
+ const newValue = e.target.value
73
+ setSearchQuery(newValue)
74
+ requestAnimationFrame(() => {
75
+ if (searchInputRef.current) {
76
+ searchInputRef.current.focus()
77
+ const newCursorPos = Math.min(cursorPosition + 1, newValue.length)
78
+ searchInputRef.current.setSelectionRange(newCursorPos, newCursorPos)
79
+ }
80
+ })
81
+ }
82
+
83
+ return (
84
+ <div className="telpick-wrapper relative" ref={dropdownRef}>
85
+ <button
86
+ onClick={() => setDropdownOpen(!isDropdownOpen)}
87
+ className="telpick-btn"
88
+ style={styleOverrides}
89
+ aria-expanded={isDropdownOpen}
90
+ aria-haspopup="listbox"
91
+ >
92
+ <div className="telpick-flag">
93
+ {selectedCountry.flag && <img src={selectedCountry.flag} alt={selectedCountry.country || 'flag'} />}
94
+ </div>
95
+ <span>{selectedCountry.code}</span>
96
+ <span className="telpick-arrow ml-auto">▼</span>
97
+ </button>
98
+ {isDropdownOpen && (
99
+ <div
100
+ className="telpick-dropdown"
101
+ role="listbox"
102
+ onClick={(e) => e.stopPropagation()}
103
+ onMouseDown={(e) => e.stopPropagation()}
104
+ >
105
+ <input
106
+ ref={searchInputRef}
107
+ value={searchQuery}
108
+ onChange={handleSearchChange}
109
+ onClick={(e) => e.stopPropagation()}
110
+ onMouseDown={(e) => e.stopPropagation()}
111
+ type="text"
112
+ placeholder="Buscar país..."
113
+ className="telpick-search"
114
+ autoFocus
115
+ />
116
+ <ul>
117
+ {filteredCodes.map((item, index) => {
118
+ const isSelected = item.country_code === selectedCode && selectedCode !== null && selectedCode !== undefined
119
+ return (
120
+ <li
121
+ key={`${item.country_code}-${index}`}
122
+ onClick={() => selectCode(item)}
123
+ className={`telpick-item ${isSelected ? 'telpick-item-selected' : ''}`}
124
+ role="option"
125
+ aria-selected={isSelected}
126
+ >
127
+ <div className="telpick-flag">
128
+ <img src={item.flag} alt={item.country} />
129
+ </div>
130
+ <span>{item.country}</span>
131
+ <span className="ml-auto">{item.code}</span>
132
+ </li>
133
+ )
134
+ })}
135
+ </ul>
136
+ </div>
137
+ )}
138
+ </div>
139
+ )
140
+ }
package/src/telpick.ts ADDED
@@ -0,0 +1,157 @@
1
+ export interface CountryCode {
2
+ country_code: string;
3
+ flag: string;
4
+ country: string;
5
+ code: string;
6
+ }
7
+
8
+ export interface TelpickProps {
9
+ code?: string;
10
+ onChange?: (country: CountryCode) => void;
11
+ styleOverrides?: Partial<Record<string, string>>;
12
+ }
13
+
14
+ export async function getDefaultCountry(codes: CountryCode[]): Promise<CountryCode | null> {
15
+ const services = [
16
+ async () => {
17
+ try {
18
+ const res = await fetch('https://ip-api.com/json/?fields=countryCode')
19
+ const data = await res.json()
20
+ if (data.countryCode) {
21
+ return codes.find(c => c.country_code === data.countryCode) || null
22
+ }
23
+ } catch {}
24
+ return null
25
+ },
26
+ async () => {
27
+ try {
28
+ const res = await fetch('https://get.geojs.io/v1/ip/country.json')
29
+ const data = await res.json()
30
+ if (data.country) {
31
+ return codes.find(c => c.country_code === data.country) || null
32
+ }
33
+ } catch {}
34
+ return null
35
+ },
36
+ async () => {
37
+ try {
38
+ const res = await fetch('https://ipapi.co/json/')
39
+ const data = await res.json()
40
+ if (data.country_code) {
41
+ return codes.find(c => c.country_code === data.country_code) || null
42
+ }
43
+ } catch {}
44
+ return null
45
+ },
46
+ async () => {
47
+ try {
48
+ const res = await fetch('https://ip-api.io/json/')
49
+ const data = await res.json()
50
+ if (data.country_code) {
51
+ return codes.find(c => c.country_code === data.country_code) || null
52
+ }
53
+ } catch {}
54
+ return null
55
+ }
56
+ ]
57
+
58
+ for (const service of services) {
59
+ try {
60
+ const result = await Promise.race([
61
+ service(),
62
+ new Promise<null>((resolve) => setTimeout(() => resolve(null), 3000))
63
+ ])
64
+ if (result) return result
65
+ } catch {
66
+ continue
67
+ }
68
+ }
69
+
70
+ try {
71
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
72
+ const timezoneToCountry: Record<string, string> = {
73
+ 'America/Lima': 'PE',
74
+ 'America/Bogota': 'CO',
75
+ 'America/Mexico_City': 'MX',
76
+ 'America/Argentina/Buenos_Aires': 'AR',
77
+ 'America/Santiago': 'CL',
78
+ 'America/Caracas': 'VE',
79
+ 'America/Montevideo': 'UY',
80
+ 'America/Asuncion': 'PY',
81
+ 'America/La_Paz': 'BO',
82
+ 'America/Guayaquil': 'EC',
83
+ 'America/Panama': 'PA',
84
+ 'America/Costa_Rica': 'CR',
85
+ 'America/Managua': 'NI',
86
+ 'America/Tegucigalpa': 'HN',
87
+ 'America/Guatemala': 'GT',
88
+ 'America/El_Salvador': 'SV',
89
+ 'America/Havana': 'CU',
90
+ 'America/Santo_Domingo': 'DO',
91
+ 'America/Jamaica': 'JM',
92
+ 'America/Port-au-Prince': 'HT',
93
+ 'Europe/Madrid': 'ES',
94
+ 'Europe/London': 'GB',
95
+ 'Europe/Paris': 'FR',
96
+ 'Europe/Berlin': 'DE',
97
+ 'Europe/Rome': 'IT',
98
+ 'Europe/Amsterdam': 'NL',
99
+ 'Europe/Brussels': 'BE',
100
+ 'Europe/Vienna': 'AT',
101
+ 'Europe/Zurich': 'CH',
102
+ 'Europe/Stockholm': 'SE',
103
+ 'Europe/Oslo': 'NO',
104
+ 'Europe/Copenhagen': 'DK',
105
+ 'Europe/Helsinki': 'FI',
106
+ 'Europe/Warsaw': 'PL',
107
+ 'Europe/Prague': 'CZ',
108
+ 'Europe/Bucharest': 'RO',
109
+ 'Europe/Moscow': 'RU',
110
+ 'America/New_York': 'US',
111
+ 'America/Chicago': 'US',
112
+ 'America/Denver': 'US',
113
+ 'America/Los_Angeles': 'US',
114
+ 'America/Toronto': 'CA',
115
+ 'America/Vancouver': 'CA',
116
+ 'Asia/Tokyo': 'JP',
117
+ 'Asia/Shanghai': 'CN',
118
+ 'Asia/Hong_Kong': 'CN',
119
+ 'Asia/Seoul': 'KR',
120
+ 'Asia/Singapore': 'SG',
121
+ 'Asia/Bangkok': 'TH',
122
+ 'Asia/Jakarta': 'ID',
123
+ 'Asia/Manila': 'PH',
124
+ 'Asia/Kolkata': 'IN',
125
+ 'Asia/Dubai': 'AE',
126
+ 'Asia/Riyadh': 'SA',
127
+ 'Australia/Sydney': 'AU',
128
+ 'Australia/Melbourne': 'AU',
129
+ 'Pacific/Auckland': 'NZ',
130
+ 'Africa/Cairo': 'EG',
131
+ 'Africa/Johannesburg': 'ZA',
132
+ 'Africa/Nairobi': 'KE',
133
+ 'Africa/Lagos': 'NG'
134
+ }
135
+
136
+ const countryCode = timezoneToCountry[timezone]
137
+ if (countryCode) {
138
+ return codes.find(c => c.country_code === countryCode) || null
139
+ }
140
+ } catch {}
141
+
142
+ return codes.find(c => c.country_code === 'CO') || null
143
+ }
144
+
145
+ export function useClickOutside(ref: HTMLElement, cb: () => void) {
146
+ function handler(e: MouseEvent) {
147
+ const target = e.target as Node
148
+ if (ref && target && !ref.contains(target)) {
149
+ const dropdown = ref.querySelector('.telpick-dropdown')
150
+ if (!dropdown || !dropdown.contains(target)) {
151
+ cb()
152
+ }
153
+ }
154
+ }
155
+ document.addEventListener('click', handler, true)
156
+ return () => document.removeEventListener('click', handler, true)
157
+ }