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,721 @@
1
+ /**
2
+ * TyInput Web Component
3
+ * PORTED FROM: clj/ty/components/input.cljs
4
+ * Phase D: Complete with debounce feature for input and change events
5
+ *
6
+ * Enhanced input component with:
7
+ * - Label, error messages, semantic styling
8
+ * - Icon slots (start/end)
9
+ * - Numeric formatting with shadow values
10
+ * - Currency, percent, compact notation
11
+ * - Format-on-blur / raw-on-focus behavior
12
+ * - Debounce (0-5000ms) for input/change events
13
+ * - Immediate event firing on blur (cancels pending debounce)
14
+ *
15
+ * NOTE: Checkbox functionality is in separate ty-checkbox component
16
+ */
17
+ import { TyComponent } from '../base/ty-component.js';
18
+ import { ensureStyles } from '../utils/styles.js';
19
+ import { inputStyles } from '../styles/input.js';
20
+ import { formatNumber, parseNumericValue, shouldFormat as shouldFormatType } from '../utils/number-format.js';
21
+ import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
22
+ /**
23
+ * Required indicator SVG icon (from Lucide)
24
+ */
25
+ const REQUIRED_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-asterisk-icon lucide-asterisk"><path d="M12 6v12"/><path d="M17.196 9 6.804 15"/><path d="m6.804 9 10.392 6"/></svg>`;
26
+ export class TyInput extends TyComponent {
27
+ constructor() {
28
+ super(); // TyComponent handles attachInternals() and attachShadow()
29
+ // ============================================================================
30
+ // INTERNAL STATE - Not managed by PropertyManager
31
+ // NOTE: _internals provided by TyComponent base class
32
+ // ============================================================================
33
+ // Shadow value - parsed numeric value for formatting
34
+ this._shadowValue = null;
35
+ // Focus state - toggles format/raw display
36
+ this._isFocused = false;
37
+ // Listener setup tracking
38
+ this._listenersSetup = false;
39
+ // Store references to handlers for cleanup
40
+ this._inputHandler = null;
41
+ this._changeHandler = null;
42
+ this._focusHandler = null;
43
+ this._blurHandler = null;
44
+ // Debounce timers
45
+ this._inputDebounceTimer = null;
46
+ this._changeDebounceTimer = null;
47
+ // Apply styles to shadow root
48
+ const shadow = this.shadowRoot;
49
+ ensureStyles(shadow, { css: inputStyles, id: 'ty-input' });
50
+ }
51
+ /**
52
+ * Called when component connects to DOM
53
+ * TyComponent already handled pre-connection property capture
54
+ */
55
+ onConnect() {
56
+ // Initialize shadow value from current value property
57
+ this.initializeShadowValue();
58
+ // NOTE: Event listeners are set up in render() after input element is created
59
+ // setupEventListeners() will be called after initial render
60
+ // Setup locale observer to watch for ancestor lang changes
61
+ this._localeObserver = observeLocaleChanges(this, () => {
62
+ this.render();
63
+ });
64
+ }
65
+ /**
66
+ * Called when component disconnects from DOM
67
+ */
68
+ onDisconnect() {
69
+ // Clean up event listeners
70
+ this.removeEventListeners();
71
+ // Cleanup locale observer
72
+ if (this._localeObserver) {
73
+ this._localeObserver();
74
+ this._localeObserver = undefined;
75
+ }
76
+ // Clear any pending debounce timers
77
+ if (this._inputDebounceTimer !== null) {
78
+ clearTimeout(this._inputDebounceTimer);
79
+ this._inputDebounceTimer = null;
80
+ }
81
+ if (this._changeDebounceTimer !== null) {
82
+ clearTimeout(this._changeDebounceTimer);
83
+ this._changeDebounceTimer = null;
84
+ }
85
+ }
86
+ // ============================================================================
87
+ // TYCOMPONENT LIFECYCLE HOOKS
88
+ // ============================================================================
89
+ /**
90
+ * Handle property changes - called BEFORE render
91
+ * This replaces the old attributeChangedCallback logic
92
+ */
93
+ onPropertiesChanged(changes) {
94
+ for (const { name, newValue } of changes) {
95
+ switch (name) {
96
+ case 'value':
97
+ // Parse to shadow value for numeric types
98
+ this._shadowValue = this.parseShadowValue(newValue || '');
99
+ break;
100
+ case 'type':
101
+ // Re-parse shadow value when type changes
102
+ const currentValue = this.getProperty('value') || '';
103
+ this._shadowValue = this.parseShadowValue(currentValue);
104
+ break;
105
+ case 'error':
106
+ // Auto-set flavor to danger when error is present and flavor is neutral
107
+ if (newValue && this.getProperty('flavor') === 'neutral') {
108
+ // Use setProperty to trigger proper lifecycle
109
+ this.setProperty('flavor', 'danger');
110
+ }
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Hook: Called when form is reset
117
+ * Clear shadow value and focus state
118
+ */
119
+ onFormReset() {
120
+ // Reset shadow value to default (empty)
121
+ this._shadowValue = null;
122
+ // Reset focus state
123
+ this._isFocused = false;
124
+ // Clear any pending debounce timers
125
+ if (this._inputDebounceTimer !== null) {
126
+ clearTimeout(this._inputDebounceTimer);
127
+ this._inputDebounceTimer = null;
128
+ }
129
+ if (this._changeDebounceTimer !== null) {
130
+ clearTimeout(this._changeDebounceTimer);
131
+ this._changeDebounceTimer = null;
132
+ }
133
+ this.render();
134
+ }
135
+ /**
136
+ * Override form value to return shadow value
137
+ * This ensures numeric types submit their parsed values
138
+ */
139
+ getFormValue() {
140
+ const formValue = this._shadowValue !== null ? String(this._shadowValue) : null;
141
+ return formValue;
142
+ }
143
+ /**
144
+ * Initialize shadow value from the initial value attribute
145
+ */
146
+ initializeShadowValue() {
147
+ const currentValue = this.getProperty('value');
148
+ if (currentValue) {
149
+ this._shadowValue = this.parseShadowValue(currentValue);
150
+ // Update form value
151
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
152
+ }
153
+ }
154
+ /**
155
+ * Parse a string value to the appropriate shadow value type
156
+ */
157
+ parseShadowValue(value) {
158
+ // Defensive check: ensure value is actually a string before calling .trim()
159
+ if (!value || typeof value !== 'string' || value.trim() === '')
160
+ return null;
161
+ // For numeric types, parse to number
162
+ if (shouldFormatType(this.type)) {
163
+ const parsed = parseNumericValue(value);
164
+ if (parsed !== null && this.precision === 0) {
165
+ return Math.round(parsed);
166
+ }
167
+ return parsed;
168
+ }
169
+ // For other types, keep as string
170
+ return value;
171
+ }
172
+ /**
173
+ * Check if current input should format numbers
174
+ */
175
+ shouldFormat() {
176
+ return shouldFormatType(this.type) &&
177
+ !this._isFocused &&
178
+ this._shadowValue !== null &&
179
+ typeof this._shadowValue === 'number';
180
+ }
181
+ /**
182
+ * Get the format configuration for current input
183
+ */
184
+ getFormatConfig() {
185
+ return {
186
+ type: this.type,
187
+ locale: this.locale, // Use getter which calls getEffectiveLocale correctly
188
+ currency: this.currency,
189
+ precision: this.precision
190
+ };
191
+ }
192
+ /**
193
+ * Get the display value (formatted or raw)
194
+ */
195
+ getDisplayValue() {
196
+ if (this.shouldFormat()) {
197
+ let valueToFormat = this._shadowValue;
198
+ // For percent: divide by 100 (user enters 15, displays as 15%)
199
+ // This matches ClojureScript behavior
200
+ if (this.type === 'percent') {
201
+ valueToFormat = valueToFormat / 100;
202
+ }
203
+ return formatNumber(valueToFormat, this.getFormatConfig());
204
+ }
205
+ return this._shadowValue !== null ? String(this._shadowValue) : '';
206
+ }
207
+ // ============================================================================
208
+ // PROPERTY ACCESSORS - Simplified with TyComponent
209
+ // NOTE: validateFlavor removed - now handled by property configuration
210
+ // ============================================================================
211
+ // Core properties - simple getProperty/setProperty pattern
212
+ get type() { return this.getProperty('type'); }
213
+ set type(v) { this.setProperty('type', v); }
214
+ // Value - special getter returns shadow value
215
+ get value() {
216
+ return this._shadowValue !== null ? String(this._shadowValue) : '';
217
+ }
218
+ set value(v) { this.setProperty('value', v); }
219
+ get name() { return this.getProperty('name'); }
220
+ set name(v) { this.setProperty('name', v); }
221
+ get placeholder() { return this.getProperty('placeholder'); }
222
+ set placeholder(v) { this.setProperty('placeholder', v); }
223
+ get label() { return this.getProperty('label'); }
224
+ set label(v) { this.setProperty('label', v); }
225
+ get disabled() { return this.getProperty('disabled'); }
226
+ set disabled(v) { this.setProperty('disabled', v); }
227
+ get required() { return this.getProperty('required'); }
228
+ set required(v) { this.setProperty('required', v); }
229
+ get error() { return this.getProperty('error'); }
230
+ set error(v) { this.setProperty('error', v); }
231
+ get size() { return this.getProperty('size'); }
232
+ set size(v) { this.setProperty('size', v); }
233
+ get flavor() { return this.getProperty('flavor'); }
234
+ set flavor(v) { this.setProperty('flavor', v); }
235
+ // Numeric formatting properties
236
+ get currency() { return this.getProperty('currency'); }
237
+ set currency(v) { this.setProperty('currency', v); }
238
+ // Locale - special getter uses getEffectiveLocale()
239
+ get locale() {
240
+ return getEffectiveLocale(this, this.getProperty('locale'));
241
+ }
242
+ set locale(v) { this.setProperty('locale', v); }
243
+ get precision() { return this.getProperty('precision'); }
244
+ set precision(v) { this.setProperty('precision', v); }
245
+ // Debounce property
246
+ get debounce() { return this.getProperty('debounce'); }
247
+ set debounce(v) { this.setProperty('debounce', v); }
248
+ // Form property
249
+ get form() {
250
+ return this._internals.form;
251
+ }
252
+ /**
253
+ * Build CSS class list for input wrapper
254
+ */
255
+ buildClassList() {
256
+ const classes = [this.size, this.flavor];
257
+ if (this.disabled)
258
+ classes.push('disabled');
259
+ if (this.required)
260
+ classes.push('required');
261
+ if (this.error)
262
+ classes.push('error');
263
+ return classes.join(' ');
264
+ }
265
+ /**
266
+ * Dispatch input event (helper method for debouncing)
267
+ */
268
+ dispatchInputEvent(rawValue, originalEvent) {
269
+ this.dispatchEvent(new CustomEvent('input', {
270
+ detail: {
271
+ value: this._shadowValue,
272
+ formattedValue: this.shouldFormat() ? this.getDisplayValue() : null,
273
+ rawValue: rawValue,
274
+ originalEvent: originalEvent
275
+ },
276
+ bubbles: true,
277
+ composed: true
278
+ }));
279
+ }
280
+ /**
281
+ * Dispatch change event (helper method for debouncing)
282
+ */
283
+ dispatchChangeEvent(rawValue, originalEvent) {
284
+ this.dispatchEvent(new CustomEvent('change', {
285
+ detail: {
286
+ value: this._shadowValue,
287
+ formattedValue: this.shouldFormat() ? this.getDisplayValue() : null,
288
+ rawValue: rawValue,
289
+ originalEvent: originalEvent
290
+ },
291
+ bubbles: true,
292
+ composed: true
293
+ }));
294
+ }
295
+ /**
296
+ * Remove event listeners for cleanup
297
+ */
298
+ removeEventListeners() {
299
+ if (!this._listenersSetup)
300
+ return;
301
+ const shadow = this.shadowRoot;
302
+ if (!shadow)
303
+ return;
304
+ const inputEl = shadow.querySelector('input');
305
+ if (!inputEl)
306
+ return;
307
+ // Remove all event listeners using stored handler references
308
+ if (this._inputHandler) {
309
+ inputEl.removeEventListener('input', this._inputHandler);
310
+ this._inputHandler = null;
311
+ }
312
+ if (this._changeHandler) {
313
+ inputEl.removeEventListener('change', this._changeHandler);
314
+ this._changeHandler = null;
315
+ }
316
+ if (this._focusHandler) {
317
+ inputEl.removeEventListener('focus', this._focusHandler);
318
+ this._focusHandler = null;
319
+ }
320
+ if (this._blurHandler) {
321
+ inputEl.removeEventListener('blur', this._blurHandler);
322
+ this._blurHandler = null;
323
+ }
324
+ this._listenersSetup = false;
325
+ }
326
+ /**
327
+ * Setup event listeners for input element
328
+ * IMPORTANT: Only called ONCE, not on every render (like ClojureScript)
329
+ */
330
+ setupEventListeners() {
331
+ if (this._listenersSetup) {
332
+ return; // Already setup
333
+ }
334
+ const shadow = this.shadowRoot;
335
+ const inputEl = shadow.querySelector('input');
336
+ const wrapperEl = shadow.querySelector('.input-wrapper');
337
+ if (!inputEl || !wrapperEl) {
338
+ console.warn('[TyInput.setupEventListeners] ⚠️ Input element not found!');
339
+ return;
340
+ }
341
+ // Create and store handler references for cleanup
342
+ // Input event - update shadow value and form (with debounce support)
343
+ this._inputHandler = (e) => {
344
+ // Stop native event from propagating - only our custom event should bubble
345
+ e.stopPropagation();
346
+ e.stopImmediatePropagation();
347
+ const target = e.target;
348
+ const rawValue = target.value;
349
+ // Parse to shadow value
350
+ this._shadowValue = this.parseShadowValue(rawValue);
351
+ // Update form value with shadow value
352
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
353
+ // Clear existing timer
354
+ if (this._inputDebounceTimer !== null) {
355
+ clearTimeout(this._inputDebounceTimer);
356
+ }
357
+ // If debounce is set, debounce the event
358
+ if (this.debounce > 0) {
359
+ this._inputDebounceTimer = window.setTimeout(() => {
360
+ // Use current value, not the captured rawValue
361
+ this.dispatchInputEvent(inputEl.value, e);
362
+ this._inputDebounceTimer = null;
363
+ }, this.debounce);
364
+ }
365
+ else {
366
+ // Fire immediately if no debounce
367
+ this.dispatchInputEvent(rawValue, e);
368
+ }
369
+ };
370
+ // Change event (with debounce support)
371
+ this._changeHandler = (e) => {
372
+ // Stop native event from propagating - only our custom event should bubble
373
+ e.stopPropagation();
374
+ e.stopImmediatePropagation();
375
+ const target = e.target;
376
+ const rawValue = target.value;
377
+ // Parse to shadow value
378
+ this._shadowValue = this.parseShadowValue(rawValue);
379
+ // Update form value
380
+ this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : null);
381
+ // Clear existing timer
382
+ if (this._changeDebounceTimer !== null) {
383
+ clearTimeout(this._changeDebounceTimer);
384
+ }
385
+ // If debounce is set, debounce the event
386
+ if (this.debounce > 0) {
387
+ this._changeDebounceTimer = window.setTimeout(() => {
388
+ // Use current value, not the captured rawValue
389
+ this.dispatchChangeEvent(inputEl.value, e);
390
+ this._changeDebounceTimer = null;
391
+ }, this.debounce);
392
+ }
393
+ else {
394
+ // Fire immediately if no debounce
395
+ this.dispatchChangeEvent(rawValue, e);
396
+ }
397
+ };
398
+ // Focus event - show raw value for numeric types
399
+ this._focusHandler = (e) => {
400
+ this._isFocused = true;
401
+ wrapperEl.classList.add('focused');
402
+ // For numeric types, show raw shadow value
403
+ if (shouldFormatType(this.type) && this._shadowValue !== null) {
404
+ inputEl.value = String(this._shadowValue);
405
+ }
406
+ this.dispatchEvent(new CustomEvent('focus', {
407
+ detail: { originalEvent: e },
408
+ bubbles: true,
409
+ composed: true
410
+ }));
411
+ };
412
+ // Blur event - fire pending debounced events immediately, then show formatted value
413
+ this._blurHandler = (e) => {
414
+ const target = e.target;
415
+ const rawValue = target.value;
416
+ // Fire any pending debounced input event immediately on blur
417
+ if (this._inputDebounceTimer !== null) {
418
+ clearTimeout(this._inputDebounceTimer);
419
+ this._inputDebounceTimer = null;
420
+ this.dispatchInputEvent(rawValue, e);
421
+ }
422
+ // Fire any pending debounced change event immediately on blur
423
+ if (this._changeDebounceTimer !== null) {
424
+ clearTimeout(this._changeDebounceTimer);
425
+ this._changeDebounceTimer = null;
426
+ this.dispatchChangeEvent(rawValue, e);
427
+ }
428
+ this._isFocused = false;
429
+ wrapperEl.classList.remove('focused');
430
+ // For numeric types, show formatted value
431
+ if (this.shouldFormat()) {
432
+ inputEl.value = this.getDisplayValue();
433
+ }
434
+ this.dispatchEvent(new CustomEvent('blur', {
435
+ detail: { originalEvent: e },
436
+ bubbles: true,
437
+ composed: true
438
+ }));
439
+ };
440
+ // Add event listeners
441
+ inputEl.addEventListener('input', this._inputHandler);
442
+ inputEl.addEventListener('change', this._changeHandler);
443
+ inputEl.addEventListener('focus', this._focusHandler);
444
+ inputEl.addEventListener('blur', this._blurHandler);
445
+ this._listenersSetup = true;
446
+ }
447
+ /**
448
+ * Render the input component with wrapper and slots
449
+ * Phase C: Uses getDisplayValue() for formatted output
450
+ *
451
+ */
452
+ render() {
453
+ const shadow = this.shadowRoot;
454
+ const existingInput = shadow.querySelector('input');
455
+ const existingWrapper = shadow.querySelector('.input-wrapper');
456
+ const existingLabel = shadow.querySelector('.input-label');
457
+ const existingError = shadow.querySelector('.error-message');
458
+ const classes = this.buildClassList();
459
+ // Map input type (all numeric types use 'text' in DOM, others pass through)
460
+ const inputType = ['password', 'email', 'tel', 'url'].includes(this.type)
461
+ ? this.type
462
+ : 'text';
463
+ // Set inputmode for mobile keyboard hint
464
+ const inputMode = shouldFormatType(this.type) ? 'decimal'
465
+ : this.type === 'email' ? 'email'
466
+ : this.type === 'tel' ? 'tel'
467
+ : this.type === 'url' ? 'url'
468
+ : undefined;
469
+ // Get display value (formatted or raw based on focus)
470
+ const displayValue = this.getDisplayValue();
471
+ // If input exists, just update properties (like ClojureScript does)
472
+ if (existingInput && existingWrapper) {
473
+ existingInput.type = inputType;
474
+ existingInput.value = displayValue;
475
+ existingInput.placeholder = this.placeholder;
476
+ existingInput.name = this.name;
477
+ if (inputMode)
478
+ existingInput.inputMode = inputMode;
479
+ else
480
+ existingInput.removeAttribute('inputmode');
481
+ // Update wrapper classes
482
+ existingWrapper.className = `input-wrapper ${classes}`;
483
+ // Set disabled property AND manage attribute
484
+ existingInput.disabled = this.disabled;
485
+ if (this.disabled) {
486
+ existingInput.setAttribute('disabled', '');
487
+ }
488
+ else {
489
+ existingInput.removeAttribute('disabled');
490
+ }
491
+ // Set required property AND manage attribute
492
+ existingInput.required = this.required;
493
+ if (this.required) {
494
+ existingInput.setAttribute('required', '');
495
+ }
496
+ else {
497
+ existingInput.removeAttribute('required');
498
+ }
499
+ // ===== BUG FIX: Dynamic label creation =====
500
+ // Update or CREATE label if needed
501
+ if (this.label) {
502
+ if (existingLabel) {
503
+ // Label exists, just update it
504
+ existingLabel.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
505
+ existingLabel.style.display = 'flex';
506
+ }
507
+ else {
508
+ // Label doesn't exist but we need one - CREATE IT!
509
+ const labelEl = document.createElement('label');
510
+ labelEl.className = 'input-label';
511
+ labelEl.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
512
+ // Insert label BEFORE the input-wrapper
513
+ const container = shadow.querySelector('.input-container');
514
+ if (container) {
515
+ container.insertBefore(labelEl, existingWrapper);
516
+ }
517
+ }
518
+ }
519
+ else if (existingLabel) {
520
+ // No label text, hide existing label
521
+ ;
522
+ existingLabel.style.display = 'none';
523
+ }
524
+ // Update error message
525
+ if (this.error) {
526
+ if (existingError) {
527
+ existingError.textContent = this.error;
528
+ }
529
+ else {
530
+ const errorEl = document.createElement('div');
531
+ errorEl.className = 'error-message';
532
+ errorEl.textContent = this.error;
533
+ shadow.querySelector('.input-container')?.appendChild(errorEl);
534
+ }
535
+ }
536
+ else if (existingError) {
537
+ existingError.remove();
538
+ }
539
+ }
540
+ else {
541
+ // Create initial structure with wrapper and slots
542
+ const labelHtml = this.label ? `
543
+ <label class="input-label">
544
+ ${this.label}
545
+ ${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}
546
+ </label>
547
+ ` : '';
548
+ const errorHtml = this.error ? `
549
+ <div class="error-message">${this.error}</div>
550
+ ` : '';
551
+ shadow.innerHTML = `
552
+ <div class="input-container">
553
+ ${labelHtml}
554
+ <div class="input-wrapper ${classes}">
555
+ <slot name="start"></slot>
556
+ <input
557
+ type="${inputType}"
558
+ value="${displayValue}"
559
+ placeholder="${this.placeholder}"
560
+ name="${this.name}"
561
+ ${inputMode ? `inputmode="${inputMode}"` : ''}
562
+ />
563
+ <slot name="end"></slot>
564
+ </div>
565
+ ${errorHtml}
566
+ </div>
567
+ `;
568
+ // Set boolean properties after creating element
569
+ const inputEl = shadow.querySelector('input');
570
+ inputEl.disabled = this.disabled;
571
+ inputEl.required = this.required;
572
+ // Setup event listeners ONCE
573
+ this.setupEventListeners();
574
+ }
575
+ }
576
+ }
577
+ TyInput.formAssociated = true;
578
+ // ============================================================================
579
+ // PROPERTY CONFIGURATION - Declarative property lifecycle
580
+ // ============================================================================
581
+ TyInput.properties = {
582
+ // Core properties
583
+ type: {
584
+ type: 'string',
585
+ visual: true,
586
+ default: 'text',
587
+ validate: (v) => {
588
+ const validTypes = [
589
+ 'text', 'email', 'password', 'number', 'tel', 'url',
590
+ 'currency', 'percent', 'compact'
591
+ ];
592
+ return validTypes.includes(v);
593
+ },
594
+ coerce: (v) => {
595
+ const validTypes = [
596
+ 'text', 'email', 'password', 'number', 'tel', 'url',
597
+ 'currency', 'percent', 'compact'
598
+ ];
599
+ if (!validTypes.includes(v)) {
600
+ console.warn(`[ty-input] Invalid type '${v}'. Using 'text'.`);
601
+ return 'text';
602
+ }
603
+ return v;
604
+ }
605
+ },
606
+ value: {
607
+ type: 'string',
608
+ visual: true,
609
+ formValue: true,
610
+ emitChange: true,
611
+ default: ''
612
+ },
613
+ name: {
614
+ type: 'string',
615
+ default: ''
616
+ },
617
+ placeholder: {
618
+ type: 'string',
619
+ visual: true,
620
+ default: ''
621
+ },
622
+ label: {
623
+ type: 'string',
624
+ visual: true,
625
+ default: ''
626
+ },
627
+ disabled: {
628
+ type: 'boolean',
629
+ visual: true,
630
+ default: false
631
+ },
632
+ required: {
633
+ type: 'boolean',
634
+ visual: true,
635
+ default: false
636
+ },
637
+ error: {
638
+ type: 'string',
639
+ visual: true,
640
+ default: ''
641
+ },
642
+ size: {
643
+ type: 'string',
644
+ visual: true,
645
+ default: 'md',
646
+ validate: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v),
647
+ coerce: (v) => {
648
+ if (!['xs', 'sm', 'md', 'lg', 'xl'].includes(v)) {
649
+ console.warn(`[ty-input] Invalid size '${v}'. Using 'md'.`);
650
+ return 'md';
651
+ }
652
+ return v;
653
+ }
654
+ },
655
+ flavor: {
656
+ type: 'string',
657
+ visual: true,
658
+ default: 'neutral',
659
+ validate: (v) => {
660
+ const valid = ['primary', 'secondary', 'success', 'danger', 'warning', 'neutral'];
661
+ return valid.includes(v);
662
+ },
663
+ coerce: (v) => {
664
+ const valid = ['primary', 'secondary', 'success', 'danger', 'warning', 'neutral'];
665
+ if (!valid.includes(v)) {
666
+ console.warn(`[ty-input] Invalid flavor '${v}'. Using 'neutral'.`);
667
+ return 'neutral';
668
+ }
669
+ return v;
670
+ }
671
+ },
672
+ // Numeric formatting properties
673
+ currency: {
674
+ type: 'string',
675
+ visual: true,
676
+ default: 'USD'
677
+ },
678
+ locale: {
679
+ type: 'string',
680
+ visual: true,
681
+ default: 'en-US'
682
+ },
683
+ precision: {
684
+ type: 'number',
685
+ visual: true,
686
+ default: undefined,
687
+ validate: (v) => {
688
+ if (v === undefined || v === null)
689
+ return true;
690
+ return typeof v === 'number' && v >= 0 && v <= 10;
691
+ },
692
+ coerce: (v) => {
693
+ if (v === undefined || v === null)
694
+ return undefined;
695
+ const num = Number(v);
696
+ if (isNaN(num))
697
+ return undefined;
698
+ return Math.max(0, Math.min(10, Math.floor(num)));
699
+ }
700
+ },
701
+ // Debounce property
702
+ debounce: {
703
+ type: 'number',
704
+ default: 0,
705
+ validate: (v) => {
706
+ const num = Number(v);
707
+ return !isNaN(num) && num >= 0 && num <= 5000;
708
+ },
709
+ coerce: (v) => {
710
+ const num = Number(v);
711
+ if (isNaN(num))
712
+ return 0;
713
+ return Math.max(0, Math.min(5000, Math.floor(num)));
714
+ }
715
+ }
716
+ };
717
+ // Register the custom element
718
+ if (!customElements.get('ty-input')) {
719
+ customElements.define('ty-input', TyInput);
720
+ }
721
+ //# sourceMappingURL=input.js.map