tyrell-components 1.0.0-TC7

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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/css/tyrell.css +1783 -0
  4. package/dist/tyrell.css +1783 -0
  5. package/dist/tyrell.js +2 -0
  6. package/lib/base/ty-component.d.ts +133 -0
  7. package/lib/base/ty-component.d.ts.map +1 -0
  8. package/lib/base/ty-component.js +297 -0
  9. package/lib/base/ty-component.js.map +1 -0
  10. package/lib/components/button.d.ts +126 -0
  11. package/lib/components/button.d.ts.map +1 -0
  12. package/lib/components/button.js +244 -0
  13. package/lib/components/button.js.map +1 -0
  14. package/lib/components/calendar-month.d.ts +132 -0
  15. package/lib/components/calendar-month.d.ts.map +1 -0
  16. package/lib/components/calendar-month.js +440 -0
  17. package/lib/components/calendar-month.js.map +1 -0
  18. package/lib/components/calendar-navigation.d.ts +137 -0
  19. package/lib/components/calendar-navigation.d.ts.map +1 -0
  20. package/lib/components/calendar-navigation.js +366 -0
  21. package/lib/components/calendar-navigation.js.map +1 -0
  22. package/lib/components/calendar.d.ts +166 -0
  23. package/lib/components/calendar.d.ts.map +1 -0
  24. package/lib/components/calendar.js +774 -0
  25. package/lib/components/calendar.js.map +1 -0
  26. package/lib/components/checkbox.d.ts +189 -0
  27. package/lib/components/checkbox.d.ts.map +1 -0
  28. package/lib/components/checkbox.js +400 -0
  29. package/lib/components/checkbox.js.map +1 -0
  30. package/lib/components/copy.d.ts +180 -0
  31. package/lib/components/copy.d.ts.map +1 -0
  32. package/lib/components/copy.js +393 -0
  33. package/lib/components/copy.js.map +1 -0
  34. package/lib/components/date-picker.d.ts +379 -0
  35. package/lib/components/date-picker.d.ts.map +1 -0
  36. package/lib/components/date-picker.js +1586 -0
  37. package/lib/components/date-picker.js.map +1 -0
  38. package/lib/components/dropdown.d.ts +402 -0
  39. package/lib/components/dropdown.d.ts.map +1 -0
  40. package/lib/components/dropdown.js +1564 -0
  41. package/lib/components/dropdown.js.map +1 -0
  42. package/lib/components/icon.d.ts +107 -0
  43. package/lib/components/icon.d.ts.map +1 -0
  44. package/lib/components/icon.js +230 -0
  45. package/lib/components/icon.js.map +1 -0
  46. package/lib/components/input.d.ts +270 -0
  47. package/lib/components/input.d.ts.map +1 -0
  48. package/lib/components/input.js +721 -0
  49. package/lib/components/input.js.map +1 -0
  50. package/lib/components/modal.d.ts +58 -0
  51. package/lib/components/modal.d.ts.map +1 -0
  52. package/lib/components/modal.js +473 -0
  53. package/lib/components/modal.js.map +1 -0
  54. package/lib/components/multiselect.d.ts +397 -0
  55. package/lib/components/multiselect.d.ts.map +1 -0
  56. package/lib/components/multiselect.js +1580 -0
  57. package/lib/components/multiselect.js.map +1 -0
  58. package/lib/components/option.d.ts +66 -0
  59. package/lib/components/option.d.ts.map +1 -0
  60. package/lib/components/option.js +314 -0
  61. package/lib/components/option.js.map +1 -0
  62. package/lib/components/popup.d.ts +43 -0
  63. package/lib/components/popup.d.ts.map +1 -0
  64. package/lib/components/popup.js +380 -0
  65. package/lib/components/popup.js.map +1 -0
  66. package/lib/components/radio.d.ts +198 -0
  67. package/lib/components/radio.d.ts.map +1 -0
  68. package/lib/components/radio.js +437 -0
  69. package/lib/components/radio.js.map +1 -0
  70. package/lib/components/resize-observer.d.ts +48 -0
  71. package/lib/components/resize-observer.d.ts.map +1 -0
  72. package/lib/components/resize-observer.js +108 -0
  73. package/lib/components/resize-observer.js.map +1 -0
  74. package/lib/components/scroll-container.d.ts +51 -0
  75. package/lib/components/scroll-container.d.ts.map +1 -0
  76. package/lib/components/scroll-container.js +239 -0
  77. package/lib/components/scroll-container.js.map +1 -0
  78. package/lib/components/step.d.ts +26 -0
  79. package/lib/components/step.d.ts.map +1 -0
  80. package/lib/components/step.js +75 -0
  81. package/lib/components/step.js.map +1 -0
  82. package/lib/components/switch.d.ts +111 -0
  83. package/lib/components/switch.d.ts.map +1 -0
  84. package/lib/components/switch.js +240 -0
  85. package/lib/components/switch.js.map +1 -0
  86. package/lib/components/tab.d.ts +23 -0
  87. package/lib/components/tab.d.ts.map +1 -0
  88. package/lib/components/tab.js +76 -0
  89. package/lib/components/tab.js.map +1 -0
  90. package/lib/components/tabs.d.ts +93 -0
  91. package/lib/components/tabs.d.ts.map +1 -0
  92. package/lib/components/tabs.js +653 -0
  93. package/lib/components/tabs.js.map +1 -0
  94. package/lib/components/tag.d.ts +144 -0
  95. package/lib/components/tag.d.ts.map +1 -0
  96. package/lib/components/tag.js +314 -0
  97. package/lib/components/tag.js.map +1 -0
  98. package/lib/components/textarea.d.ts +241 -0
  99. package/lib/components/textarea.d.ts.map +1 -0
  100. package/lib/components/textarea.js +585 -0
  101. package/lib/components/textarea.js.map +1 -0
  102. package/lib/components/tooltip.d.ts +40 -0
  103. package/lib/components/tooltip.d.ts.map +1 -0
  104. package/lib/components/tooltip.js +439 -0
  105. package/lib/components/tooltip.js.map +1 -0
  106. package/lib/components/wizard.d.ts +86 -0
  107. package/lib/components/wizard.d.ts.map +1 -0
  108. package/lib/components/wizard.js +636 -0
  109. package/lib/components/wizard.js.map +1 -0
  110. package/lib/icons/fontawesome/brands.d.ts +557 -0
  111. package/lib/icons/fontawesome/brands.d.ts.map +1 -0
  112. package/lib/icons/fontawesome/brands.js +557 -0
  113. package/lib/icons/fontawesome/brands.js.map +1 -0
  114. package/lib/icons/fontawesome/regular.d.ts +281 -0
  115. package/lib/icons/fontawesome/regular.d.ts.map +1 -0
  116. package/lib/icons/fontawesome/regular.js +281 -0
  117. package/lib/icons/fontawesome/regular.js.map +1 -0
  118. package/lib/icons/fontawesome/solid.d.ts +1992 -0
  119. package/lib/icons/fontawesome/solid.d.ts.map +1 -0
  120. package/lib/icons/fontawesome/solid.js +1992 -0
  121. package/lib/icons/fontawesome/solid.js.map +1 -0
  122. package/lib/icons/heroicons/micro.d.ts +324 -0
  123. package/lib/icons/heroicons/micro.d.ts.map +1 -0
  124. package/lib/icons/heroicons/micro.js +1032 -0
  125. package/lib/icons/heroicons/micro.js.map +1 -0
  126. package/lib/icons/heroicons/mini.d.ts +332 -0
  127. package/lib/icons/heroicons/mini.d.ts.map +1 -0
  128. package/lib/icons/heroicons/mini.js +1038 -0
  129. package/lib/icons/heroicons/mini.js.map +1 -0
  130. package/lib/icons/heroicons/outline.d.ts +332 -0
  131. package/lib/icons/heroicons/outline.d.ts.map +1 -0
  132. package/lib/icons/heroicons/outline.js +993 -0
  133. package/lib/icons/heroicons/outline.js.map +1 -0
  134. package/lib/icons/heroicons/solid.d.ts +332 -0
  135. package/lib/icons/heroicons/solid.d.ts.map +1 -0
  136. package/lib/icons/heroicons/solid.js +1063 -0
  137. package/lib/icons/heroicons/solid.js.map +1 -0
  138. package/lib/icons/lucide.d.ts +1872 -0
  139. package/lib/icons/lucide.d.ts.map +1 -0
  140. package/lib/icons/lucide.js +28212 -0
  141. package/lib/icons/lucide.js.map +1 -0
  142. package/lib/icons/material/filled.d.ts +2180 -0
  143. package/lib/icons/material/filled.d.ts.map +1 -0
  144. package/lib/icons/material/filled.js +14003 -0
  145. package/lib/icons/material/filled.js.map +1 -0
  146. package/lib/icons/material/outlined.d.ts +2142 -0
  147. package/lib/icons/material/outlined.d.ts.map +1 -0
  148. package/lib/icons/material/outlined.js +14545 -0
  149. package/lib/icons/material/outlined.js.map +1 -0
  150. package/lib/icons/material/round.d.ts +2147 -0
  151. package/lib/icons/material/round.d.ts.map +1 -0
  152. package/lib/icons/material/round.js +14779 -0
  153. package/lib/icons/material/round.js.map +1 -0
  154. package/lib/icons/material/sharp.d.ts +2147 -0
  155. package/lib/icons/material/sharp.d.ts.map +1 -0
  156. package/lib/icons/material/sharp.js +14189 -0
  157. package/lib/icons/material/sharp.js.map +1 -0
  158. package/lib/icons/material/two-tone.d.ts +2185 -0
  159. package/lib/icons/material/two-tone.d.ts.map +1 -0
  160. package/lib/icons/material/two-tone.js +17152 -0
  161. package/lib/icons/material/two-tone.js.map +1 -0
  162. package/lib/index.d.ts +78 -0
  163. package/lib/index.d.ts.map +1 -0
  164. package/lib/index.js +71 -0
  165. package/lib/index.js.map +1 -0
  166. package/lib/styles/button.d.ts +14 -0
  167. package/lib/styles/button.d.ts.map +1 -0
  168. package/lib/styles/button.js +457 -0
  169. package/lib/styles/button.js.map +1 -0
  170. package/lib/styles/calendar-month.d.ts +6 -0
  171. package/lib/styles/calendar-month.d.ts.map +1 -0
  172. package/lib/styles/calendar-month.js +229 -0
  173. package/lib/styles/calendar-month.js.map +1 -0
  174. package/lib/styles/calendar-navigation.d.ts +6 -0
  175. package/lib/styles/calendar-navigation.d.ts.map +1 -0
  176. package/lib/styles/calendar-navigation.js +125 -0
  177. package/lib/styles/calendar-navigation.js.map +1 -0
  178. package/lib/styles/calendar.d.ts +6 -0
  179. package/lib/styles/calendar.d.ts.map +1 -0
  180. package/lib/styles/calendar.js +28 -0
  181. package/lib/styles/calendar.js.map +1 -0
  182. package/lib/styles/checkbox.d.ts +9 -0
  183. package/lib/styles/checkbox.d.ts.map +1 -0
  184. package/lib/styles/checkbox.js +19 -0
  185. package/lib/styles/checkbox.js.map +1 -0
  186. package/lib/styles/copy.d.ts +7 -0
  187. package/lib/styles/copy.d.ts.map +1 -0
  188. package/lib/styles/copy.js +94 -0
  189. package/lib/styles/copy.js.map +1 -0
  190. package/lib/styles/custom-scrollbar.d.ts +6 -0
  191. package/lib/styles/custom-scrollbar.d.ts.map +1 -0
  192. package/lib/styles/custom-scrollbar.js +157 -0
  193. package/lib/styles/custom-scrollbar.js.map +1 -0
  194. package/lib/styles/date-picker.d.ts +6 -0
  195. package/lib/styles/date-picker.d.ts.map +1 -0
  196. package/lib/styles/date-picker.js +400 -0
  197. package/lib/styles/date-picker.js.map +1 -0
  198. package/lib/styles/dropdown.d.ts +12 -0
  199. package/lib/styles/dropdown.d.ts.map +1 -0
  200. package/lib/styles/dropdown.js +983 -0
  201. package/lib/styles/dropdown.js.map +1 -0
  202. package/lib/styles/icon.d.ts +6 -0
  203. package/lib/styles/icon.d.ts.map +1 -0
  204. package/lib/styles/icon.js +231 -0
  205. package/lib/styles/icon.js.map +1 -0
  206. package/lib/styles/input.d.ts +7 -0
  207. package/lib/styles/input.d.ts.map +1 -0
  208. package/lib/styles/input.js +685 -0
  209. package/lib/styles/input.js.map +1 -0
  210. package/lib/styles/modal.d.ts +8 -0
  211. package/lib/styles/modal.d.ts.map +1 -0
  212. package/lib/styles/modal.js +134 -0
  213. package/lib/styles/modal.js.map +1 -0
  214. package/lib/styles/multiselect.d.ts +6 -0
  215. package/lib/styles/multiselect.d.ts.map +1 -0
  216. package/lib/styles/multiselect.js +774 -0
  217. package/lib/styles/multiselect.js.map +1 -0
  218. package/lib/styles/option.d.ts +6 -0
  219. package/lib/styles/option.d.ts.map +1 -0
  220. package/lib/styles/option.js +116 -0
  221. package/lib/styles/option.js.map +1 -0
  222. package/lib/styles/popup.d.ts +8 -0
  223. package/lib/styles/popup.d.ts.map +1 -0
  224. package/lib/styles/popup.js +95 -0
  225. package/lib/styles/popup.js.map +1 -0
  226. package/lib/styles/radio.d.ts +8 -0
  227. package/lib/styles/radio.d.ts.map +1 -0
  228. package/lib/styles/radio.js +160 -0
  229. package/lib/styles/radio.js.map +1 -0
  230. package/lib/styles/resize-observer.d.ts +6 -0
  231. package/lib/styles/resize-observer.d.ts.map +1 -0
  232. package/lib/styles/resize-observer.js +18 -0
  233. package/lib/styles/resize-observer.js.map +1 -0
  234. package/lib/styles/scroll-container.d.ts +6 -0
  235. package/lib/styles/scroll-container.d.ts.map +1 -0
  236. package/lib/styles/scroll-container.js +198 -0
  237. package/lib/styles/scroll-container.js.map +1 -0
  238. package/lib/styles/step.d.ts +5 -0
  239. package/lib/styles/step.d.ts.map +1 -0
  240. package/lib/styles/step.js +50 -0
  241. package/lib/styles/step.js.map +1 -0
  242. package/lib/styles/switch.d.ts +9 -0
  243. package/lib/styles/switch.d.ts.map +1 -0
  244. package/lib/styles/switch.js +100 -0
  245. package/lib/styles/switch.js.map +1 -0
  246. package/lib/styles/tab.d.ts +5 -0
  247. package/lib/styles/tab.d.ts.map +1 -0
  248. package/lib/styles/tab.js +51 -0
  249. package/lib/styles/tab.js.map +1 -0
  250. package/lib/styles/tabs.d.ts +13 -0
  251. package/lib/styles/tabs.d.ts.map +1 -0
  252. package/lib/styles/tabs.js +184 -0
  253. package/lib/styles/tabs.js.map +1 -0
  254. package/lib/styles/tag.d.ts +6 -0
  255. package/lib/styles/tag.d.ts.map +1 -0
  256. package/lib/styles/tag.js +415 -0
  257. package/lib/styles/tag.js.map +1 -0
  258. package/lib/styles/textarea.d.ts +6 -0
  259. package/lib/styles/textarea.d.ts.map +1 -0
  260. package/lib/styles/textarea.js +350 -0
  261. package/lib/styles/textarea.js.map +1 -0
  262. package/lib/styles/tooltip.d.ts +9 -0
  263. package/lib/styles/tooltip.d.ts.map +1 -0
  264. package/lib/styles/tooltip.js +136 -0
  265. package/lib/styles/tooltip.js.map +1 -0
  266. package/lib/styles/wizard.d.ts +25 -0
  267. package/lib/styles/wizard.d.ts.map +1 -0
  268. package/lib/styles/wizard.js +325 -0
  269. package/lib/styles/wizard.js.map +1 -0
  270. package/lib/types/common.d.ts +143 -0
  271. package/lib/types/common.d.ts.map +1 -0
  272. package/lib/types/common.js +5 -0
  273. package/lib/types/common.js.map +1 -0
  274. package/lib/utils/calendar-utils.d.ts +176 -0
  275. package/lib/utils/calendar-utils.d.ts.map +1 -0
  276. package/lib/utils/calendar-utils.js +370 -0
  277. package/lib/utils/calendar-utils.js.map +1 -0
  278. package/lib/utils/custom-scrollbar.d.ts +82 -0
  279. package/lib/utils/custom-scrollbar.d.ts.map +1 -0
  280. package/lib/utils/custom-scrollbar.js +320 -0
  281. package/lib/utils/custom-scrollbar.js.map +1 -0
  282. package/lib/utils/icon-registry.d.ts +78 -0
  283. package/lib/utils/icon-registry.d.ts.map +1 -0
  284. package/lib/utils/icon-registry.js +304 -0
  285. package/lib/utils/icon-registry.js.map +1 -0
  286. package/lib/utils/locale.d.ts +136 -0
  287. package/lib/utils/locale.d.ts.map +1 -0
  288. package/lib/utils/locale.js +213 -0
  289. package/lib/utils/locale.js.map +1 -0
  290. package/lib/utils/mobile.d.ts +14 -0
  291. package/lib/utils/mobile.d.ts.map +1 -0
  292. package/lib/utils/mobile.js +21 -0
  293. package/lib/utils/mobile.js.map +1 -0
  294. package/lib/utils/number-format.d.ts +83 -0
  295. package/lib/utils/number-format.d.ts.map +1 -0
  296. package/lib/utils/number-format.js +143 -0
  297. package/lib/utils/number-format.js.map +1 -0
  298. package/lib/utils/parse-boolean.d.ts +39 -0
  299. package/lib/utils/parse-boolean.d.ts.map +1 -0
  300. package/lib/utils/parse-boolean.js +58 -0
  301. package/lib/utils/parse-boolean.js.map +1 -0
  302. package/lib/utils/positioning.d.ts +143 -0
  303. package/lib/utils/positioning.d.ts.map +1 -0
  304. package/lib/utils/positioning.js +308 -0
  305. package/lib/utils/positioning.js.map +1 -0
  306. package/lib/utils/property-capture.d.ts +132 -0
  307. package/lib/utils/property-capture.d.ts.map +1 -0
  308. package/lib/utils/property-capture.js +152 -0
  309. package/lib/utils/property-capture.js.map +1 -0
  310. package/lib/utils/property-manager.d.ts +90 -0
  311. package/lib/utils/property-manager.d.ts.map +1 -0
  312. package/lib/utils/property-manager.js +197 -0
  313. package/lib/utils/property-manager.js.map +1 -0
  314. package/lib/utils/resize-observer.d.ts +42 -0
  315. package/lib/utils/resize-observer.d.ts.map +1 -0
  316. package/lib/utils/resize-observer.js +71 -0
  317. package/lib/utils/resize-observer.js.map +1 -0
  318. package/lib/utils/scroll-lock.d.ts +79 -0
  319. package/lib/utils/scroll-lock.d.ts.map +1 -0
  320. package/lib/utils/scroll-lock.js +197 -0
  321. package/lib/utils/scroll-lock.js.map +1 -0
  322. package/lib/utils/styles.d.ts +27 -0
  323. package/lib/utils/styles.d.ts.map +1 -0
  324. package/lib/utils/styles.js +53 -0
  325. package/lib/utils/styles.js.map +1 -0
  326. package/lib/version.d.ts +8 -0
  327. package/lib/version.d.ts.map +1 -0
  328. package/lib/version.js +11 -0
  329. package/lib/version.js.map +1 -0
  330. package/package.json +159 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Icon Registry System
3
+ * Simple icon storage and retrieval with Cache API persistence
4
+ * PORTED FROM: clj/ty/icons.cljs
5
+ */
6
+ import { VERSION } from '../version.js';
7
+ /** Icon registry - maps icon names to SVG strings */
8
+ const iconRegistry = new Map();
9
+ /** Watchers for registry changes - now receives Set of changed icon names */
10
+ const watchers = new Map();
11
+ /** Track which icons each watcher cares about (for selective notification) */
12
+ const watcherIconNames = new Map();
13
+ /** Pending notifications - batched for performance */
14
+ let pendingNotifications = null;
15
+ let notificationTimer = null;
16
+ /** Cache API configuration */
17
+ const CACHE_NAME = `ty-icons-v${VERSION}`;
18
+ // Use a fake URL base for Cache API (requires valid URL format)
19
+ const CACHE_URL_BASE = 'https://ty-icons.local/';
20
+ /** Track in-flight cache reads to avoid duplicate work */
21
+ const cacheReadPromises = new Map();
22
+ /** Flag to track if old caches have been cleared */
23
+ let oldCachesCleared = false;
24
+ /**
25
+ * Clear old cache versions on first use
26
+ * This runs lazily the first time any icon is requested
27
+ */
28
+ async function clearOldCaches() {
29
+ if (oldCachesCleared || !('caches' in window))
30
+ return;
31
+ oldCachesCleared = true;
32
+ try {
33
+ const cacheNames = await caches.keys();
34
+ const oldCaches = cacheNames.filter(name => name.startsWith('ty-icons-v') && name !== CACHE_NAME);
35
+ await Promise.all(oldCaches.map(name => caches.delete(name)));
36
+ if (oldCaches.length > 0) {
37
+ console.log(`🗑️ Cleared ${oldCaches.length} old icon cache(s)`);
38
+ }
39
+ }
40
+ catch (err) {
41
+ console.warn('[ty-icons] Failed to clear old caches:', err);
42
+ }
43
+ }
44
+ /**
45
+ * Cache an icon in Cache API (non-blocking background operation)
46
+ * @param name Icon name
47
+ * @param svg SVG string
48
+ */
49
+ async function cacheIcon(name, svg) {
50
+ if (!('caches' in window))
51
+ return;
52
+ try {
53
+ const cache = await caches.open(CACHE_NAME);
54
+ const response = new Response(svg, {
55
+ headers: { 'Content-Type': 'image/svg+xml' }
56
+ });
57
+ // Cache API requires valid URLs
58
+ await cache.put(`${CACHE_URL_BASE}${name}`, response);
59
+ }
60
+ catch (err) {
61
+ // Silent fail - caching is an optimization, not critical
62
+ console.warn(`[ty-icons] Failed to cache icon "${name}":`, err);
63
+ }
64
+ }
65
+ /**
66
+ * Read icon directly from cache (for immediate rendering)
67
+ * This is the key function that prevents icon twitching!
68
+ * @param name Icon name
69
+ * @returns Promise<string | null> - SVG string from cache or null
70
+ */
71
+ export async function getCachedIcon(name) {
72
+ // Check if we already have a read in progress for this icon
73
+ const existingPromise = cacheReadPromises.get(name);
74
+ if (existingPromise) {
75
+ return existingPromise;
76
+ }
77
+ // Create new read promise
78
+ const readPromise = (async () => {
79
+ try {
80
+ // Clear old caches on first cache access
81
+ await clearOldCaches();
82
+ const cache = await caches.open(CACHE_NAME);
83
+ const response = await cache.match(`${CACHE_URL_BASE}${name}`);
84
+ if (response) {
85
+ return await response.text();
86
+ }
87
+ }
88
+ catch (err) {
89
+ console.warn(`[ty-icons] Failed to read icon "${name}" from cache:`, err);
90
+ }
91
+ return null;
92
+ })();
93
+ // Store promise to avoid duplicate work
94
+ cacheReadPromises.set(name, readPromise);
95
+ // Clean up promise after completion
96
+ readPromise.finally(() => {
97
+ cacheReadPromises.delete(name);
98
+ });
99
+ return readPromise;
100
+ }
101
+ /**
102
+ * Register multiple icons at once
103
+ * Always updates memory and notifies watchers immediately
104
+ * Cache update happens in background (only if different from cached version)
105
+ * @param icons Object mapping icon names to SVG strings
106
+ */
107
+ export function registerIcons(icons) {
108
+ const changedIcons = new Set();
109
+ Object.entries(icons).forEach(([name, svg]) => {
110
+ // Always set in memory registry (fast, synchronous)
111
+ iconRegistry.set(name, svg);
112
+ changedIcons.add(name);
113
+ // Compare with cache in background to avoid unnecessary cache writes
114
+ /*getCachedIcon(name).then(cachedSvg => {
115
+ // Only write to cache if content actually changed
116
+ if (cachedSvg !== svg) {
117
+ cacheIcon(name, svg).catch(() => {
118
+ // Error already logged in cacheIcon
119
+ })
120
+ }
121
+ // If cachedSvg === svg, skip cache write (already up to date)
122
+ }).catch(() => {
123
+ // No cached version or error reading cache - write new icon
124
+ cacheIcon(name, svg).catch(() => {
125
+ // Error already logged in cacheIcon
126
+ })
127
+ })
128
+ */
129
+ });
130
+ // Notify watchers immediately (synchronous)
131
+ if (changedIcons.size > 0) {
132
+ scheduleNotification(changedIcons);
133
+ }
134
+ }
135
+ /**
136
+ * Register a single icon
137
+ * Always updates memory and notifies watchers immediately
138
+ * Cache update happens in background (only if different from cached version)
139
+ * @param name Icon name
140
+ * @param svg SVG string
141
+ */
142
+ export function registerIcon(name, svg) {
143
+ // Always set in memory registry (fast, synchronous)
144
+ iconRegistry.set(name, svg);
145
+ // Notify watchers immediately (synchronous)
146
+ scheduleNotification(new Set([name]));
147
+ // Compare with cache in background to avoid unnecessary cache writes
148
+ getCachedIcon(name).then(cachedSvg => {
149
+ // Only write to cache if content actually changed
150
+ if (cachedSvg !== svg) {
151
+ cacheIcon(name, svg).catch(() => {
152
+ // Error already logged in cacheIcon
153
+ });
154
+ }
155
+ // If cachedSvg === svg, skip cache write (already up to date)
156
+ }).catch(() => {
157
+ // No cached version or error reading cache - write new icon
158
+ cacheIcon(name, svg).catch(() => {
159
+ // Error already logged in cacheIcon
160
+ });
161
+ });
162
+ }
163
+ /**
164
+ * Lookup an icon by name from memory
165
+ * NOTE: This does NOT check cache - use getCachedIcon() for that
166
+ * @param name Icon name
167
+ * @returns SVG string or undefined
168
+ */
169
+ export function getIcon(name) {
170
+ return iconRegistry.get(name);
171
+ }
172
+ /**
173
+ * Check if an icon exists in memory
174
+ * NOTE: This does NOT check cache
175
+ * @param name Icon name
176
+ * @returns true if icon is registered in memory
177
+ */
178
+ export function hasIcon(name) {
179
+ return iconRegistry.has(name);
180
+ }
181
+ /**
182
+ * Clear all registered icons from memory and cache
183
+ */
184
+ export async function clearIcons() {
185
+ const allIcons = new Set(iconRegistry.keys());
186
+ iconRegistry.clear();
187
+ // Clear cache
188
+ if ('caches' in window) {
189
+ try {
190
+ await caches.delete(CACHE_NAME);
191
+ oldCachesCleared = false; // Allow re-clearing next time
192
+ }
193
+ catch (err) {
194
+ console.warn('[ty-icons] Failed to clear icon cache:', err);
195
+ }
196
+ }
197
+ scheduleNotification(allIcons);
198
+ }
199
+ /**
200
+ * Add a watcher for registry changes
201
+ * @param id Unique watcher ID
202
+ * @param iconName Optional icon name to watch (for selective notification)
203
+ * @param callback Function to call when watched icons change
204
+ */
205
+ export function addWatcher(id, iconName, callback) {
206
+ watchers.set(id, callback);
207
+ if (iconName) {
208
+ watcherIconNames.set(id, iconName);
209
+ }
210
+ }
211
+ /**
212
+ * Remove a watcher
213
+ * @param id Watcher ID to remove
214
+ */
215
+ export function removeWatcher(id) {
216
+ watchers.delete(id);
217
+ watcherIconNames.delete(id);
218
+ }
219
+ /**
220
+ * Schedule notification for changed icons (batched and deferred)
221
+ */
222
+ function scheduleNotification(changedIcons) {
223
+ // Accumulate changed icons
224
+ if (!pendingNotifications) {
225
+ pendingNotifications = new Set();
226
+ }
227
+ changedIcons.forEach(name => pendingNotifications.add(name));
228
+ // Cancel existing timer
229
+ if (notificationTimer !== null) {
230
+ clearTimeout(notificationTimer);
231
+ }
232
+ // Schedule notification using requestIdleCallback or setTimeout
233
+ const scheduleCallback = typeof requestIdleCallback !== 'undefined'
234
+ ? requestIdleCallback
235
+ : (cb) => setTimeout(cb, 0);
236
+ notificationTimer = scheduleCallback(() => {
237
+ const toNotify = pendingNotifications;
238
+ pendingNotifications = null;
239
+ notificationTimer = null;
240
+ // Only notify if we have icons to notify about
241
+ if (toNotify && toNotify.size > 0) {
242
+ notifyWatchers(toNotify);
243
+ }
244
+ });
245
+ }
246
+ /**
247
+ * Notify watchers of changed icons (selective notification)
248
+ */
249
+ function notifyWatchers(changedIcons) {
250
+ watchers.forEach((callback, watcherId) => {
251
+ const watchedIcon = watcherIconNames.get(watcherId);
252
+ // If watcher is watching a specific icon, only notify if that icon changed
253
+ if (watchedIcon) {
254
+ if (changedIcons.has(watchedIcon)) {
255
+ callback(new Set([watchedIcon]));
256
+ }
257
+ }
258
+ else {
259
+ // Watcher wants all changes
260
+ callback(changedIcons);
261
+ }
262
+ });
263
+ }
264
+ /**
265
+ * Get all registered icon names (memory only, not cache)
266
+ * @returns Array of icon names
267
+ */
268
+ export function getIconNames() {
269
+ return Array.from(iconRegistry.keys());
270
+ }
271
+ /**
272
+ * Get registry size (memory only)
273
+ * @returns Number of registered icons in memory
274
+ */
275
+ export function getIconCount() {
276
+ return iconRegistry.size;
277
+ }
278
+ /**
279
+ * Get cache statistics
280
+ * @returns Promise with cache info
281
+ */
282
+ export async function getCacheInfo() {
283
+ const info = {
284
+ version: VERSION,
285
+ cacheName: CACHE_NAME,
286
+ available: 'caches' in window
287
+ };
288
+ if (!info.available)
289
+ return info;
290
+ try {
291
+ const cache = await caches.open(CACHE_NAME);
292
+ const keys = await cache.keys();
293
+ const iconKeys = keys.filter(req => req.url.startsWith(CACHE_URL_BASE));
294
+ return {
295
+ ...info,
296
+ iconCount: iconKeys.length
297
+ };
298
+ }
299
+ catch (err) {
300
+ console.warn('[ty-icons] Failed to get cache info:', err);
301
+ return info;
302
+ }
303
+ }
304
+ //# sourceMappingURL=icon-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-registry.js","sourceRoot":"","sources":["../../src/utils/icon-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,qDAAqD;AACrD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAA;AAE9C,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA+C,CAAA;AAEvE,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAA;AAElD,sDAAsD;AACtD,IAAI,oBAAoB,GAAuB,IAAI,CAAA;AACnD,IAAI,iBAAiB,GAAkB,IAAI,CAAA;AAE3C,8BAA8B;AAC9B,MAAM,UAAU,GAAG,aAAa,OAAO,EAAE,CAAA;AACzC,gEAAgE;AAChE,MAAM,cAAc,GAAG,yBAAyB,CAAA;AAEhD,0DAA0D;AAC1D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkC,CAAA;AAEnE,oDAAoD;AACpD,IAAI,gBAAgB,GAAG,KAAK,CAAA;AAE5B;;;GAGG;AACH,KAAK,UAAU,cAAc;IAC3B,IAAI,gBAAgB,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC;QAAE,OAAM;IAErD,gBAAgB,GAAG,IAAI,CAAA;IAEvB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACtC,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CACzC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,IAAI,KAAK,UAAU,CACrD,CAAA;QAED,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAE7D,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,gBAAgB,SAAS,CAAC,MAAM,oBAAoB,CAAC,CAAA;QACnE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAA;IAC7D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,GAAW;IAChD,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC;QAAE,OAAM;IAEjC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC3C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,GAAG,EAAE;YACjC,OAAO,EAAE,EAAE,cAAc,EAAE,eAAe,EAAE;SAC7C,CAAC,CAAA;QACF,gCAAgC;QAChC,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,cAAc,GAAG,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAA;IACvD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yDAAyD;QACzD,OAAO,CAAC,IAAI,CAAC,oCAAoC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAA;IACjE,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,4DAA4D;IAC5D,MAAM,eAAe,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnD,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,eAAe,CAAA;IACxB,CAAC;IAED,0BAA0B;IAC1B,MAAM,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;QAC9B,IAAI,CAAC;YACH,yCAAyC;YACzC,MAAM,cAAc,EAAE,CAAA;YAEtB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC3C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC,CAAA;YAE9D,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YAC9B,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mCAAmC,IAAI,eAAe,EAAE,GAAG,CAAC,CAAA;QAC3E,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,EAAE,CAAA;IAEJ,wCAAwC;IACxC,iBAAiB,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;IAExC,oCAAoC;IACpC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;QACvB,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,OAAO,WAAW,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAA6B;IACzD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAA;IAEtC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE;QAC5C,oDAAoD;QACpD,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC3B,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAEtB,qEAAqE;QACrE;;;;;;;;;;;;;;UAcE;IACJ,CAAC,CAAC,CAAA;IAEF,4CAA4C;IAC5C,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1B,oBAAoB,CAAC,YAAY,CAAC,CAAA;IACpC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,GAAW;IACpD,oDAAoD;IACpD,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAE3B,4CAA4C;IAC5C,oBAAoB,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAErC,qEAAqE;IACrE,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;QACnC,kDAAkD;QAClD,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC9B,oCAAoC;YACtC,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,8DAA8D;IAChE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACZ,4DAA4D;QAC5D,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YAC9B,oCAAoC;QACtC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7C,YAAY,CAAC,KAAK,EAAE,CAAA;IAEpB,cAAc;IACd,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;YAC/B,gBAAgB,GAAG,KAAK,CAAA,CAAC,8BAA8B;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAED,oBAAoB,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,EAAU,EACV,QAA4B,EAC5B,QAA6C;IAE7C,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;IAC1B,IAAI,QAAQ,EAAE,CAAC;QACb,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;IACpC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACnB,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,YAAyB;IACrD,2BAA2B;IAC3B,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,oBAAoB,GAAG,IAAI,GAAG,EAAE,CAAA;IAClC,CAAC;IAED,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,oBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;IAE7D,wBAAwB;IACxB,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;QAC/B,YAAY,CAAC,iBAAiB,CAAC,CAAA;IACjC,CAAC;IAED,gEAAgE;IAChE,MAAM,gBAAgB,GAAG,OAAO,mBAAmB,KAAK,WAAW;QACjE,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,CAAC,EAAuB,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAElD,iBAAiB,GAAG,gBAAgB,CAAC,GAAG,EAAE;QACxC,MAAM,QAAQ,GAAG,oBAAoB,CAAA;QACrC,oBAAoB,GAAG,IAAI,CAAA;QAC3B,iBAAiB,GAAG,IAAI,CAAA;QAExB,+CAA+C;QAC/C,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAClC,cAAc,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAW,CAAA;AACd,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,YAAyB;IAC/C,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE;QACvC,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAEnD,2EAA2E;QAC3E,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;YAClC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,4BAA4B;YAC5B,QAAQ,CAAC,YAAY,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAA;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,YAAY,CAAC,IAAI,CAAA;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAMhC,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,UAAU;QACrB,SAAS,EAAE,QAAQ,IAAI,MAAM;KAC9B,CAAA;IAED,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAA;IAEhC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC3C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAA;QAEvE,OAAO;YACL,GAAG,IAAI;YACP,SAAS,EAAE,QAAQ,CAAC,MAAM;SAC3B,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAA;QACzD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Locale Resolution Utilities
3
+ *
4
+ * Provides a cascade system for resolving locale/language preferences:
5
+ * 1. Component's explicit `locale` attribute (highest priority)
6
+ * 2. Closest ancestor's `lang` attribute
7
+ * 3. Document root's `lang` attribute
8
+ * 4. Browser's language preference
9
+ * 5. 'en-US' fallback (lowest priority)
10
+ *
11
+ * This allows developers to set locale at any level:
12
+ * - Per-component: <ty-input locale="fr-FR">
13
+ * - Per-section: <div lang="de-DE"><ty-input></ty-input></div>
14
+ * - Per-page: <html lang="es-ES">
15
+ * - Browser default: Uses navigator.language
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // In a component
20
+ * class TyInput extends HTMLElement {
21
+ * get locale(): string {
22
+ * return getEffectiveLocale(this, this.getAttribute('locale'));
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ /**
28
+ * Get the effective locale for an element using the resolution cascade.
29
+ *
30
+ * Resolution order:
31
+ * 1. Explicit locale attribute on the element
32
+ * 2. Closest ancestor element with a `lang` attribute
33
+ * 3. Document root element's `lang` attribute
34
+ * 4. Browser's navigator.language
35
+ * 5. Fallback to 'en-US'
36
+ *
37
+ * @param element - The element to get locale for
38
+ * @param explicitLocale - Optional explicit locale value (from component's locale attribute)
39
+ * @returns The resolved locale string (e.g., 'en-US', 'fr-FR')
40
+ *
41
+ * @example
42
+ * ```html
43
+ * <html lang="en-US">
44
+ * <div lang="fr-FR">
45
+ * <ty-input></ty-input> <!-- Uses 'fr-FR' -->
46
+ * <ty-input locale="de-DE"></ty-input> <!-- Uses 'de-DE' -->
47
+ * </div>
48
+ * <ty-input></ty-input> <!-- Uses 'en-US' -->
49
+ * </html>
50
+ * ```
51
+ */
52
+ export declare function getEffectiveLocale(element: HTMLElement, explicitLocale?: string | null): string;
53
+ /**
54
+ * Normalize a locale string to BCP 47 format.
55
+ * Handles common variations and ensures consistency.
56
+ *
57
+ * @param locale - The locale string to normalize
58
+ * @returns Normalized locale string
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * normalizeLocale('en') // 'en'
63
+ * normalizeLocale('en_US') // 'en-US'
64
+ * normalizeLocale('EN-us') // 'en-US'
65
+ * ```
66
+ */
67
+ export declare function normalizeLocale(locale: string): string;
68
+ /**
69
+ * Check if a locale string is valid BCP 47 format.
70
+ *
71
+ * @param locale - The locale string to validate
72
+ * @returns true if valid, false otherwise
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * isValidLocale('en-US') // true
77
+ * isValidLocale('fr') // true
78
+ * isValidLocale('invalid!') // false
79
+ * ```
80
+ */
81
+ export declare function isValidLocale(locale: string): boolean;
82
+ /**
83
+ * Get the language code from a locale (e.g., 'en' from 'en-US').
84
+ *
85
+ * @param locale - The locale string
86
+ * @returns The language code
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * getLanguageCode('en-US') // 'en'
91
+ * getLanguageCode('fr-FR') // 'fr'
92
+ * getLanguageCode('de') // 'de'
93
+ * ```
94
+ */
95
+ export declare function getLanguageCode(locale: string): string;
96
+ /**
97
+ * Get the region/country code from a locale (e.g., 'US' from 'en-US').
98
+ *
99
+ * @param locale - The locale string
100
+ * @returns The region code, or undefined if not present
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * getRegionCode('en-US') // 'US'
105
+ * getRegionCode('fr-FR') // 'FR'
106
+ * getRegionCode('de') // undefined
107
+ * ```
108
+ */
109
+ export declare function getRegionCode(locale: string): string | undefined;
110
+ /**
111
+ * Create a MutationObserver to watch for lang attribute changes.
112
+ * Useful for components that need to react to dynamic locale changes.
113
+ *
114
+ * @param element - The element to observe
115
+ * @param callback - Function to call when lang changes
116
+ * @returns Cleanup function to disconnect the observer
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * class TyInput extends HTMLElement {
121
+ * private _localeObserver?: () => void;
122
+ *
123
+ * connectedCallback() {
124
+ * this._localeObserver = observeLocaleChanges(this, () => {
125
+ * this.render(); // Re-render when locale changes
126
+ * });
127
+ * }
128
+ *
129
+ * disconnectedCallback() {
130
+ * this._localeObserver?.();
131
+ * }
132
+ * }
133
+ * ```
134
+ */
135
+ export declare function observeLocaleChanges(element: HTMLElement, callback: (newLocale: string) => void): () => void;
136
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EACpB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CA0BR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoBtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOrD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACpC,MAAM,IAAI,CAyBZ"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Locale Resolution Utilities
3
+ *
4
+ * Provides a cascade system for resolving locale/language preferences:
5
+ * 1. Component's explicit `locale` attribute (highest priority)
6
+ * 2. Closest ancestor's `lang` attribute
7
+ * 3. Document root's `lang` attribute
8
+ * 4. Browser's language preference
9
+ * 5. 'en-US' fallback (lowest priority)
10
+ *
11
+ * This allows developers to set locale at any level:
12
+ * - Per-component: <ty-input locale="fr-FR">
13
+ * - Per-section: <div lang="de-DE"><ty-input></ty-input></div>
14
+ * - Per-page: <html lang="es-ES">
15
+ * - Browser default: Uses navigator.language
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // In a component
20
+ * class TyInput extends HTMLElement {
21
+ * get locale(): string {
22
+ * return getEffectiveLocale(this, this.getAttribute('locale'));
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ /**
28
+ * Get the effective locale for an element using the resolution cascade.
29
+ *
30
+ * Resolution order:
31
+ * 1. Explicit locale attribute on the element
32
+ * 2. Closest ancestor element with a `lang` attribute
33
+ * 3. Document root element's `lang` attribute
34
+ * 4. Browser's navigator.language
35
+ * 5. Fallback to 'en-US'
36
+ *
37
+ * @param element - The element to get locale for
38
+ * @param explicitLocale - Optional explicit locale value (from component's locale attribute)
39
+ * @returns The resolved locale string (e.g., 'en-US', 'fr-FR')
40
+ *
41
+ * @example
42
+ * ```html
43
+ * <html lang="en-US">
44
+ * <div lang="fr-FR">
45
+ * <ty-input></ty-input> <!-- Uses 'fr-FR' -->
46
+ * <ty-input locale="de-DE"></ty-input> <!-- Uses 'de-DE' -->
47
+ * </div>
48
+ * <ty-input></ty-input> <!-- Uses 'en-US' -->
49
+ * </html>
50
+ * ```
51
+ */
52
+ export function getEffectiveLocale(element, explicitLocale) {
53
+ // 1. Explicit locale attribute takes highest priority
54
+ if (explicitLocale) {
55
+ return explicitLocale;
56
+ }
57
+ // 2. Check closest ancestor with lang attribute
58
+ // This allows per-section locale overrides
59
+ const langElement = element.closest('[lang]');
60
+ if (langElement) {
61
+ const lang = langElement.getAttribute('lang');
62
+ if (lang)
63
+ return lang;
64
+ }
65
+ // 3. Check document root (html element)
66
+ if (document.documentElement.lang) {
67
+ return document.documentElement.lang;
68
+ }
69
+ // 4. Use browser's language preference
70
+ if (navigator.language) {
71
+ return navigator.language;
72
+ }
73
+ // 5. Ultimate fallback
74
+ return 'en-US';
75
+ }
76
+ /**
77
+ * Normalize a locale string to BCP 47 format.
78
+ * Handles common variations and ensures consistency.
79
+ *
80
+ * @param locale - The locale string to normalize
81
+ * @returns Normalized locale string
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * normalizeLocale('en') // 'en'
86
+ * normalizeLocale('en_US') // 'en-US'
87
+ * normalizeLocale('EN-us') // 'en-US'
88
+ * ```
89
+ */
90
+ export function normalizeLocale(locale) {
91
+ if (!locale)
92
+ return 'en-US';
93
+ // Replace underscores with hyphens (en_US -> en-US)
94
+ let normalized = locale.replace(/_/g, '-');
95
+ // Split into parts
96
+ const parts = normalized.split('-');
97
+ if (parts.length === 0)
98
+ return 'en-US';
99
+ // Lowercase language code (EN -> en)
100
+ parts[0] = parts[0].toLowerCase();
101
+ // Uppercase country code if present (us -> US)
102
+ if (parts.length > 1) {
103
+ parts[1] = parts[1].toUpperCase();
104
+ }
105
+ return parts.join('-');
106
+ }
107
+ /**
108
+ * Check if a locale string is valid BCP 47 format.
109
+ *
110
+ * @param locale - The locale string to validate
111
+ * @returns true if valid, false otherwise
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * isValidLocale('en-US') // true
116
+ * isValidLocale('fr') // true
117
+ * isValidLocale('invalid!') // false
118
+ * ```
119
+ */
120
+ export function isValidLocale(locale) {
121
+ if (!locale)
122
+ return false;
123
+ // BCP 47 language tag pattern
124
+ // Simplified: allows language-region format
125
+ const pattern = /^[a-z]{2,3}(-[A-Z]{2})?$/;
126
+ return pattern.test(locale);
127
+ }
128
+ /**
129
+ * Get the language code from a locale (e.g., 'en' from 'en-US').
130
+ *
131
+ * @param locale - The locale string
132
+ * @returns The language code
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * getLanguageCode('en-US') // 'en'
137
+ * getLanguageCode('fr-FR') // 'fr'
138
+ * getLanguageCode('de') // 'de'
139
+ * ```
140
+ */
141
+ export function getLanguageCode(locale) {
142
+ if (!locale)
143
+ return 'en';
144
+ return locale.split('-')[0].toLowerCase();
145
+ }
146
+ /**
147
+ * Get the region/country code from a locale (e.g., 'US' from 'en-US').
148
+ *
149
+ * @param locale - The locale string
150
+ * @returns The region code, or undefined if not present
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * getRegionCode('en-US') // 'US'
155
+ * getRegionCode('fr-FR') // 'FR'
156
+ * getRegionCode('de') // undefined
157
+ * ```
158
+ */
159
+ export function getRegionCode(locale) {
160
+ if (!locale)
161
+ return undefined;
162
+ const parts = locale.split('-');
163
+ return parts.length > 1 ? parts[1].toUpperCase() : undefined;
164
+ }
165
+ /**
166
+ * Create a MutationObserver to watch for lang attribute changes.
167
+ * Useful for components that need to react to dynamic locale changes.
168
+ *
169
+ * @param element - The element to observe
170
+ * @param callback - Function to call when lang changes
171
+ * @returns Cleanup function to disconnect the observer
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * class TyInput extends HTMLElement {
176
+ * private _localeObserver?: () => void;
177
+ *
178
+ * connectedCallback() {
179
+ * this._localeObserver = observeLocaleChanges(this, () => {
180
+ * this.render(); // Re-render when locale changes
181
+ * });
182
+ * }
183
+ *
184
+ * disconnectedCallback() {
185
+ * this._localeObserver?.();
186
+ * }
187
+ * }
188
+ * ```
189
+ */
190
+ export function observeLocaleChanges(element, callback) {
191
+ let currentLocale = getEffectiveLocale(element);
192
+ // Create observer for lang attribute changes on ancestors
193
+ const observer = new MutationObserver((mutations) => {
194
+ for (const mutation of mutations) {
195
+ if (mutation.type === 'attributes' && mutation.attributeName === 'lang') {
196
+ const newLocale = getEffectiveLocale(element);
197
+ if (newLocale !== currentLocale) {
198
+ currentLocale = newLocale;
199
+ callback(newLocale);
200
+ }
201
+ }
202
+ }
203
+ });
204
+ // Observe document and all ancestors
205
+ observer.observe(document.documentElement, {
206
+ attributes: true,
207
+ attributeFilter: ['lang'],
208
+ subtree: true
209
+ });
210
+ // Return cleanup function
211
+ return () => observer.disconnect();
212
+ }
213
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mobile detection utility
3
+ *
4
+ * Uses CSS media query via matchMedia for reliable detection:
5
+ * - `pointer: coarse` identifies touch-primary devices (not laptops with touchscreens)
6
+ * - Breakpoint is evaluated by the browser, always in sync with CSS
7
+ * - Called on each use (not cached), so it responds to viewport changes and rotation
8
+ */
9
+ /**
10
+ * Detect mobile touch devices.
11
+ * Returns true when the primary pointer is coarse (touch) AND viewport is narrow.
12
+ */
13
+ export declare function isMobileTouch(breakpoint?: number): boolean;
14
+ //# sourceMappingURL=mobile.d.ts.map