tyrell-components 1.0.0-RC13 → 1.0.0-RC15

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 (358) hide show
  1. package/README.md +10 -2
  2. package/css/tyrell-theme.css +192 -180
  3. package/css/tyrell.css +23 -0
  4. package/dist/tyrell-colors-static.css +1 -316
  5. package/dist/tyrell-theme.css +1 -1454
  6. package/dist/tyrell.css +1 -1371
  7. package/dist/tyrell.js +1 -1
  8. package/lib/base/ty-component.d.ts +20 -10
  9. package/lib/base/ty-component.js +196 -319
  10. package/lib/components/button.d.ts +3 -17
  11. package/lib/components/button.js +261 -295
  12. package/lib/components/calendar-month.d.ts +4 -50
  13. package/lib/components/calendar-month.js +443 -582
  14. package/lib/components/calendar-navigation.d.ts +3 -48
  15. package/lib/components/calendar-navigation.js +239 -362
  16. package/lib/components/calendar.d.ts +8 -83
  17. package/lib/components/calendar.js +616 -829
  18. package/lib/components/checkbox.d.ts +5 -72
  19. package/lib/components/checkbox.js +273 -416
  20. package/lib/components/copy.d.ts +4 -57
  21. package/lib/components/copy.js +280 -397
  22. package/lib/components/date-picker.d.ts +21 -163
  23. package/lib/components/date-picker.js +1143 -1674
  24. package/lib/components/file-upload.d.ts +9 -15
  25. package/lib/components/file-upload.js +404 -426
  26. package/lib/components/icon.d.ts +3 -24
  27. package/lib/components/icon.js +141 -230
  28. package/lib/components/input.d.ts +8 -108
  29. package/lib/components/input.js +593 -784
  30. package/lib/components/modal.d.ts +17 -26
  31. package/lib/components/modal.js +340 -484
  32. package/lib/components/option.d.ts +2 -9
  33. package/lib/components/option.js +168 -216
  34. package/lib/components/popup.d.ts +2 -17
  35. package/lib/components/popup.js +249 -380
  36. package/lib/components/radio.d.ts +5 -25
  37. package/lib/components/radio.js +407 -461
  38. package/lib/components/resize-observer.d.ts +3 -30
  39. package/lib/components/resize-observer.js +69 -103
  40. package/lib/components/scroll-container.d.ts +2 -12
  41. package/lib/components/scroll-container.js +293 -338
  42. package/lib/components/select.d.ts +87 -159
  43. package/lib/components/select.js +1609 -2090
  44. package/lib/components/selected-tags.d.ts +5 -48
  45. package/lib/components/selected-tags.js +188 -276
  46. package/lib/components/step.d.ts +1 -6
  47. package/lib/components/step.js +62 -68
  48. package/lib/components/switch.d.ts +1 -12
  49. package/lib/components/switch.js +214 -247
  50. package/lib/components/tab.d.ts +1 -6
  51. package/lib/components/tab.js +62 -69
  52. package/lib/components/tabs.d.ts +6 -63
  53. package/lib/components/tabs.js +545 -777
  54. package/lib/components/tag.d.ts +3 -37
  55. package/lib/components/tag.js +236 -335
  56. package/lib/components/textarea.d.ts +7 -87
  57. package/lib/components/textarea.js +457 -627
  58. package/lib/components/tooltip.d.ts +3 -11
  59. package/lib/components/tooltip.js +307 -443
  60. package/lib/components/wizard.d.ts +6 -54
  61. package/lib/components/wizard.js +405 -618
  62. package/lib/icons/fontawesome/brands.d.ts +0 -1
  63. package/lib/icons/fontawesome/brands.js +0 -8
  64. package/lib/icons/fontawesome/regular.d.ts +0 -1
  65. package/lib/icons/fontawesome/regular.js +0 -8
  66. package/lib/icons/fontawesome/solid.d.ts +0 -1
  67. package/lib/icons/fontawesome/solid.js +0 -8
  68. package/lib/icons/heroicons/micro.d.ts +0 -1
  69. package/lib/icons/heroicons/micro.js +392 -400
  70. package/lib/icons/heroicons/mini.d.ts +0 -1
  71. package/lib/icons/heroicons/mini.js +382 -390
  72. package/lib/icons/heroicons/outline.d.ts +0 -1
  73. package/lib/icons/heroicons/outline.js +337 -345
  74. package/lib/icons/heroicons/solid.d.ts +0 -1
  75. package/lib/icons/heroicons/solid.js +407 -415
  76. package/lib/icons/lucide.d.ts +0 -1
  77. package/lib/icons/lucide.js +24479 -26351
  78. package/lib/icons/material/filled.d.ts +0 -1
  79. package/lib/icons/material/filled.js +9653 -9663
  80. package/lib/icons/material/outlined.d.ts +0 -1
  81. package/lib/icons/material/outlined.js +10271 -10281
  82. package/lib/icons/material/round.d.ts +0 -1
  83. package/lib/icons/material/round.js +10495 -10505
  84. package/lib/icons/material/sharp.d.ts +0 -1
  85. package/lib/icons/material/sharp.js +9905 -9915
  86. package/lib/icons/material/two-tone.d.ts +0 -1
  87. package/lib/icons/material/two-tone.js +12792 -12802
  88. package/lib/index.d.ts +0 -1
  89. package/lib/index.js +21 -41
  90. package/lib/styles/button.d.ts +2 -20
  91. package/lib/styles/button.js +202 -404
  92. package/lib/styles/calendar-month.d.ts +1 -5
  93. package/lib/styles/calendar-month.js +127 -244
  94. package/lib/styles/calendar-navigation.d.ts +2 -6
  95. package/lib/styles/calendar-navigation.js +64 -104
  96. package/lib/styles/calendar.d.ts +1 -2
  97. package/lib/styles/calendar.js +9 -22
  98. package/lib/styles/checkbox.d.ts +0 -1
  99. package/lib/styles/checkbox.js +45 -94
  100. package/lib/styles/copy.d.ts +0 -1
  101. package/lib/styles/copy.js +67 -106
  102. package/lib/styles/custom-scrollbar.d.ts +1 -2
  103. package/lib/styles/custom-scrollbar.js +80 -115
  104. package/lib/styles/date-picker.d.ts +1 -5
  105. package/lib/styles/date-picker.js +261 -335
  106. package/lib/styles/field-size.d.ts +6 -0
  107. package/lib/styles/field-size.js +91 -0
  108. package/lib/styles/file-upload.d.ts +1 -2
  109. package/lib/styles/file-upload.js +143 -188
  110. package/lib/styles/icon.d.ts +2 -6
  111. package/lib/styles/icon.js +92 -170
  112. package/lib/styles/input.d.ts +1 -6
  113. package/lib/styles/input.js +125 -290
  114. package/lib/styles/modal.d.ts +1 -2
  115. package/lib/styles/modal.js +71 -108
  116. package/lib/styles/option.d.ts +2 -6
  117. package/lib/styles/option.js +49 -64
  118. package/lib/styles/popup.d.ts +1 -2
  119. package/lib/styles/popup.js +42 -77
  120. package/lib/styles/radio.d.ts +0 -1
  121. package/lib/styles/radio.js +69 -110
  122. package/lib/styles/resize-observer.d.ts +1 -2
  123. package/lib/styles/resize-observer.js +2 -12
  124. package/lib/styles/scroll-container.d.ts +1 -2
  125. package/lib/styles/scroll-container.js +100 -149
  126. package/lib/styles/select-base.d.ts +0 -1
  127. package/lib/styles/select-base.js +597 -785
  128. package/lib/styles/select.d.ts +1 -2
  129. package/lib/styles/select.js +140 -236
  130. package/lib/styles/step.d.ts +1 -2
  131. package/lib/styles/step.js +22 -33
  132. package/lib/styles/switch.d.ts +0 -1
  133. package/lib/styles/switch.js +34 -73
  134. package/lib/styles/tab.d.ts +1 -2
  135. package/lib/styles/tab.js +22 -34
  136. package/lib/styles/tabs.d.ts +3 -11
  137. package/lib/styles/tabs.js +190 -222
  138. package/lib/styles/tag.d.ts +1 -5
  139. package/lib/styles/tag.js +136 -218
  140. package/lib/styles/textarea.d.ts +1 -2
  141. package/lib/styles/textarea.js +210 -319
  142. package/lib/styles/tooltip.d.ts +0 -1
  143. package/lib/styles/tooltip.js +3 -13
  144. package/lib/styles/wizard.d.ts +5 -21
  145. package/lib/styles/wizard.js +170 -306
  146. package/lib/types/common.d.ts +2 -2
  147. package/lib/types/common.js +6 -17
  148. package/lib/utils/calendar-utils.d.ts +4 -21
  149. package/lib/utils/calendar-utils.js +100 -273
  150. package/lib/utils/custom-scrollbar.d.ts +6 -20
  151. package/lib/utils/custom-scrollbar.js +273 -317
  152. package/lib/utils/flavor-sheet.d.ts +0 -1
  153. package/lib/utils/flavor-sheet.js +14 -37
  154. package/lib/utils/icon-registry.d.ts +0 -2
  155. package/lib/utils/icon-registry.js +137 -243
  156. package/lib/utils/icons.d.ts +0 -1
  157. package/lib/utils/icons.js +0 -9
  158. package/lib/utils/loader-registry.d.ts +0 -1
  159. package/lib/utils/loader-registry.js +3 -35
  160. package/lib/utils/locale.d.ts +2 -23
  161. package/lib/utils/locale.js +34 -120
  162. package/lib/utils/mobile.d.ts +0 -1
  163. package/lib/utils/mobile.js +1 -18
  164. package/lib/utils/number-format.d.ts +6 -3
  165. package/lib/utils/number-format.js +54 -110
  166. package/lib/utils/positioning.d.ts +50 -7
  167. package/lib/utils/positioning.js +224 -234
  168. package/lib/utils/property-capture.d.ts +5 -27
  169. package/lib/utils/property-capture.js +36 -144
  170. package/lib/utils/property-manager.d.ts +0 -1
  171. package/lib/utils/property-manager.js +126 -196
  172. package/lib/utils/resize-observer.d.ts +0 -1
  173. package/lib/utils/resize-observer.js +23 -59
  174. package/lib/utils/scroll-lock.d.ts +4 -11
  175. package/lib/utils/scroll-lock.js +65 -172
  176. package/lib/utils/styles.d.ts +0 -1
  177. package/lib/utils/styles.js +17 -41
  178. package/lib/version.d.ts +1 -2
  179. package/lib/version.js +1 -11
  180. package/package.json +4 -4
  181. package/lib/base/ty-component.d.ts.map +0 -1
  182. package/lib/base/ty-component.js.map +0 -1
  183. package/lib/components/button.d.ts.map +0 -1
  184. package/lib/components/button.js.map +0 -1
  185. package/lib/components/calendar-month.d.ts.map +0 -1
  186. package/lib/components/calendar-month.js.map +0 -1
  187. package/lib/components/calendar-navigation.d.ts.map +0 -1
  188. package/lib/components/calendar-navigation.js.map +0 -1
  189. package/lib/components/calendar.d.ts.map +0 -1
  190. package/lib/components/calendar.js.map +0 -1
  191. package/lib/components/checkbox.d.ts.map +0 -1
  192. package/lib/components/checkbox.js.map +0 -1
  193. package/lib/components/copy.d.ts.map +0 -1
  194. package/lib/components/copy.js.map +0 -1
  195. package/lib/components/date-picker.d.ts.map +0 -1
  196. package/lib/components/date-picker.js.map +0 -1
  197. package/lib/components/file-upload.d.ts.map +0 -1
  198. package/lib/components/file-upload.js.map +0 -1
  199. package/lib/components/icon.d.ts.map +0 -1
  200. package/lib/components/icon.js.map +0 -1
  201. package/lib/components/input.d.ts.map +0 -1
  202. package/lib/components/input.js.map +0 -1
  203. package/lib/components/modal.d.ts.map +0 -1
  204. package/lib/components/modal.js.map +0 -1
  205. package/lib/components/option.d.ts.map +0 -1
  206. package/lib/components/option.js.map +0 -1
  207. package/lib/components/popup.d.ts.map +0 -1
  208. package/lib/components/popup.js.map +0 -1
  209. package/lib/components/radio.d.ts.map +0 -1
  210. package/lib/components/radio.js.map +0 -1
  211. package/lib/components/resize-observer.d.ts.map +0 -1
  212. package/lib/components/resize-observer.js.map +0 -1
  213. package/lib/components/scroll-container.d.ts.map +0 -1
  214. package/lib/components/scroll-container.js.map +0 -1
  215. package/lib/components/select.d.ts.map +0 -1
  216. package/lib/components/select.js.map +0 -1
  217. package/lib/components/selected-tags.d.ts.map +0 -1
  218. package/lib/components/selected-tags.js.map +0 -1
  219. package/lib/components/step.d.ts.map +0 -1
  220. package/lib/components/step.js.map +0 -1
  221. package/lib/components/switch.d.ts.map +0 -1
  222. package/lib/components/switch.js.map +0 -1
  223. package/lib/components/tab.d.ts.map +0 -1
  224. package/lib/components/tab.js.map +0 -1
  225. package/lib/components/tabs.d.ts.map +0 -1
  226. package/lib/components/tabs.js.map +0 -1
  227. package/lib/components/tag.d.ts.map +0 -1
  228. package/lib/components/tag.js.map +0 -1
  229. package/lib/components/textarea.d.ts.map +0 -1
  230. package/lib/components/textarea.js.map +0 -1
  231. package/lib/components/tooltip.d.ts.map +0 -1
  232. package/lib/components/tooltip.js.map +0 -1
  233. package/lib/components/wizard.d.ts.map +0 -1
  234. package/lib/components/wizard.js.map +0 -1
  235. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  236. package/lib/icons/fontawesome/brands.js.map +0 -1
  237. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  238. package/lib/icons/fontawesome/regular.js.map +0 -1
  239. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  240. package/lib/icons/fontawesome/solid.js.map +0 -1
  241. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  242. package/lib/icons/heroicons/micro.js.map +0 -1
  243. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  244. package/lib/icons/heroicons/mini.js.map +0 -1
  245. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  246. package/lib/icons/heroicons/outline.js.map +0 -1
  247. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  248. package/lib/icons/heroicons/solid.js.map +0 -1
  249. package/lib/icons/lucide.d.ts.map +0 -1
  250. package/lib/icons/lucide.js.map +0 -1
  251. package/lib/icons/material/filled.d.ts.map +0 -1
  252. package/lib/icons/material/filled.js.map +0 -1
  253. package/lib/icons/material/outlined.d.ts.map +0 -1
  254. package/lib/icons/material/outlined.js.map +0 -1
  255. package/lib/icons/material/round.d.ts.map +0 -1
  256. package/lib/icons/material/round.js.map +0 -1
  257. package/lib/icons/material/sharp.d.ts.map +0 -1
  258. package/lib/icons/material/sharp.js.map +0 -1
  259. package/lib/icons/material/two-tone.d.ts.map +0 -1
  260. package/lib/icons/material/two-tone.js.map +0 -1
  261. package/lib/index.d.ts.map +0 -1
  262. package/lib/index.js.map +0 -1
  263. package/lib/styles/button.d.ts.map +0 -1
  264. package/lib/styles/button.js.map +0 -1
  265. package/lib/styles/calendar-month.d.ts.map +0 -1
  266. package/lib/styles/calendar-month.js.map +0 -1
  267. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  268. package/lib/styles/calendar-navigation.js.map +0 -1
  269. package/lib/styles/calendar.d.ts.map +0 -1
  270. package/lib/styles/calendar.js.map +0 -1
  271. package/lib/styles/checkbox.d.ts.map +0 -1
  272. package/lib/styles/checkbox.js.map +0 -1
  273. package/lib/styles/copy.d.ts.map +0 -1
  274. package/lib/styles/copy.js.map +0 -1
  275. package/lib/styles/custom-scrollbar.d.ts.map +0 -1
  276. package/lib/styles/custom-scrollbar.js.map +0 -1
  277. package/lib/styles/date-picker.d.ts.map +0 -1
  278. package/lib/styles/date-picker.js.map +0 -1
  279. package/lib/styles/file-upload.d.ts.map +0 -1
  280. package/lib/styles/file-upload.js.map +0 -1
  281. package/lib/styles/icon.d.ts.map +0 -1
  282. package/lib/styles/icon.js.map +0 -1
  283. package/lib/styles/input.d.ts.map +0 -1
  284. package/lib/styles/input.js.map +0 -1
  285. package/lib/styles/modal.d.ts.map +0 -1
  286. package/lib/styles/modal.js.map +0 -1
  287. package/lib/styles/multiselect.d.ts +0 -8
  288. package/lib/styles/multiselect.d.ts.map +0 -1
  289. package/lib/styles/multiselect.js +0 -849
  290. package/lib/styles/multiselect.js.map +0 -1
  291. package/lib/styles/option.d.ts.map +0 -1
  292. package/lib/styles/option.js.map +0 -1
  293. package/lib/styles/popup.d.ts.map +0 -1
  294. package/lib/styles/popup.js.map +0 -1
  295. package/lib/styles/radio.d.ts.map +0 -1
  296. package/lib/styles/radio.js.map +0 -1
  297. package/lib/styles/resize-observer.d.ts.map +0 -1
  298. package/lib/styles/resize-observer.js.map +0 -1
  299. package/lib/styles/scroll-container.d.ts.map +0 -1
  300. package/lib/styles/scroll-container.js.map +0 -1
  301. package/lib/styles/select-base.d.ts.map +0 -1
  302. package/lib/styles/select-base.js.map +0 -1
  303. package/lib/styles/select.d.ts.map +0 -1
  304. package/lib/styles/select.js.map +0 -1
  305. package/lib/styles/step.d.ts.map +0 -1
  306. package/lib/styles/step.js.map +0 -1
  307. package/lib/styles/switch.d.ts.map +0 -1
  308. package/lib/styles/switch.js.map +0 -1
  309. package/lib/styles/tab.d.ts.map +0 -1
  310. package/lib/styles/tab.js.map +0 -1
  311. package/lib/styles/tabs.d.ts.map +0 -1
  312. package/lib/styles/tabs.js.map +0 -1
  313. package/lib/styles/tag.d.ts.map +0 -1
  314. package/lib/styles/tag.js.map +0 -1
  315. package/lib/styles/textarea.d.ts.map +0 -1
  316. package/lib/styles/textarea.js.map +0 -1
  317. package/lib/styles/tooltip.d.ts.map +0 -1
  318. package/lib/styles/tooltip.js.map +0 -1
  319. package/lib/styles/wizard.d.ts.map +0 -1
  320. package/lib/styles/wizard.js.map +0 -1
  321. package/lib/types/common.d.ts.map +0 -1
  322. package/lib/types/common.js.map +0 -1
  323. package/lib/utils/calendar-utils.d.ts.map +0 -1
  324. package/lib/utils/calendar-utils.js.map +0 -1
  325. package/lib/utils/custom-scrollbar.d.ts.map +0 -1
  326. package/lib/utils/custom-scrollbar.js.map +0 -1
  327. package/lib/utils/flavor-sheet.d.ts.map +0 -1
  328. package/lib/utils/flavor-sheet.js.map +0 -1
  329. package/lib/utils/icon-registry.d.ts.map +0 -1
  330. package/lib/utils/icon-registry.js.map +0 -1
  331. package/lib/utils/icons.d.ts.map +0 -1
  332. package/lib/utils/icons.js.map +0 -1
  333. package/lib/utils/loader-registry.d.ts.map +0 -1
  334. package/lib/utils/loader-registry.js.map +0 -1
  335. package/lib/utils/locale.d.ts.map +0 -1
  336. package/lib/utils/locale.js.map +0 -1
  337. package/lib/utils/mobile.d.ts.map +0 -1
  338. package/lib/utils/mobile.js.map +0 -1
  339. package/lib/utils/number-format.d.ts.map +0 -1
  340. package/lib/utils/number-format.js.map +0 -1
  341. package/lib/utils/parse-boolean.d.ts +0 -39
  342. package/lib/utils/parse-boolean.d.ts.map +0 -1
  343. package/lib/utils/parse-boolean.js +0 -58
  344. package/lib/utils/parse-boolean.js.map +0 -1
  345. package/lib/utils/positioning.d.ts.map +0 -1
  346. package/lib/utils/positioning.js.map +0 -1
  347. package/lib/utils/property-capture.d.ts.map +0 -1
  348. package/lib/utils/property-capture.js.map +0 -1
  349. package/lib/utils/property-manager.d.ts.map +0 -1
  350. package/lib/utils/property-manager.js.map +0 -1
  351. package/lib/utils/resize-observer.d.ts.map +0 -1
  352. package/lib/utils/resize-observer.js.map +0 -1
  353. package/lib/utils/scroll-lock.d.ts.map +0 -1
  354. package/lib/utils/scroll-lock.js.map +0 -1
  355. package/lib/utils/styles.d.ts.map +0 -1
  356. package/lib/utils/styles.js.map +0 -1
  357. package/lib/version.d.ts.map +0 -1
  358. package/lib/version.js.map +0 -1
@@ -1,640 +1,470 @@
1
- /**
2
- * ty-textarea - Enhanced textarea component with auto-resize functionality
3
- *
4
- * Features:
5
- * - Auto-resize based on content
6
- * - Form-associated custom element
7
- * - Min/max height constraints
8
- * - Size variants (xs, sm, md, lg, xl)
9
- * - Validation states (error, required)
10
- * - Manual resize control options
11
- * - Accessibility support
12
- *
13
- * @example
14
- * ```html
15
- * <!-- Basic usage -->
16
- * <ty-textarea label="Description" placeholder="Enter details..."></ty-textarea>
17
- *
18
- * <!-- With validation -->
19
- * <ty-textarea
20
- * label="Bio"
21
- * required
22
- * error="Bio is required"
23
- * size="lg">
24
- * </ty-textarea>
25
- *
26
- * <!-- With height constraints -->
27
- * <ty-textarea
28
- * min-height="100px"
29
- * max-height="300px"
30
- * placeholder="Auto-resizing textarea">
31
- * </ty-textarea>
32
- *
33
- * <!-- Manual resize control -->
34
- * <ty-textarea resize="vertical"></ty-textarea>
35
- * ```
36
- *
37
- * @fires {CustomEvent<{value: string, originalEvent: Event}>} input - Emitted on input
38
- * @fires {CustomEvent<{value: string, originalEvent: Event}>} change - Emitted on change
39
- */
1
+ import { DEFAULT_SIZE } from '../types/common.js';
40
2
  import { TyComponent } from '../base/ty-component.js';
41
3
  import { ensureStyles } from '../utils/styles.js';
42
4
  import { textareaStyles } from '../styles/textarea.js';
43
5
  import { CustomScrollbar, isCustomScrollbarEnabled } from '../utils/custom-scrollbar.js';
44
6
  import { customScrollbarStyles } from '../styles/custom-scrollbar.js';
45
- /**
46
- * Required icon SVG (matches input.ts)
47
- */
48
7
  const REQUIRED_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
49
8
  export class TyTextarea extends TyComponent {
50
- constructor() {
51
- super(); // TyComponent handles attachInternals() and attachShadow()
52
- // ============================================================================
53
- // INTERNAL STATE - Not managed by PropertyManager
54
- // NOTE: _internals provided by TyComponent base class
55
- // ============================================================================
56
- // DOM element references
57
- this._textareaEl = null;
58
- this._dummyEl = null;
59
- this._scrollbar = null;
60
- // Focus state
61
- this._isFocused = false;
62
- /**
63
- * Handle textarea input event
64
- */
65
- this.handleInputEvent = (e) => {
66
- const target = e.target;
67
- const newValue = target.value;
68
- // Update value via property (triggers proper lifecycle)
69
- this.value = newValue;
70
- // Trigger resize after state update
71
- if (this._textareaEl && this._dummyEl) {
72
- this.resizeTextarea(this._textareaEl, this._dummyEl);
73
- }
74
- // Emit custom events
75
- this.emitValueEvents(e);
76
- };
77
- /**
78
- * Handle textarea focus event
79
- */
80
- this.handleFocusEvent = () => {
81
- this._isFocused = true;
82
- };
83
- /**
84
- * Handle textarea blur event
85
- */
86
- this.handleBlurEvent = () => {
87
- this._isFocused = false;
88
- };
89
- // Apply styles to shadow root
90
- const shadow = this.shadowRoot;
91
- ensureStyles(shadow, { css: textareaStyles, id: 'ty-textarea' });
92
- // Add custom scrollbar styles
93
- if (isCustomScrollbarEnabled()) {
94
- ensureStyles(shadow, { css: customScrollbarStyles, id: 'ty-custom-scrollbar' });
95
- }
96
- }
97
- // observedAttributes auto-generated by TyComponent from properties config
98
- // ============================================================================
99
- // TYCOMPONENT LIFECYCLE HOOKS
100
- // ============================================================================
101
- /**
102
- * Called when component connects to DOM
103
- * TyComponent already handled pre-connection property capture
104
- */
105
- onConnect() {
106
- // No special initialization needed - TyComponent handles everything
107
- // Just render the component
108
- }
109
- /**
110
- * Called when component disconnects from DOM
111
- */
112
- onDisconnect() {
113
- // Cleanup scrollbar
114
- this._destroyScrollbar();
115
- // Cleanup event listeners if needed
116
- if (this._textareaEl) {
117
- const newTextarea = this._textareaEl.cloneNode(true);
118
- this._textareaEl.parentNode?.replaceChild(newTextarea, this._textareaEl);
119
- this._textareaEl = null;
120
- }
121
- this._dummyEl = null;
122
- }
123
- /**
124
- * Handle property changes - called BEFORE render
125
- * This replaces the old attributeChangedCallback logic
126
- */
127
- onPropertiesChanged(changes) {
128
- // No special handling needed for textarea
129
- // All property changes will trigger render() automatically if marked as visual
130
- }
131
- /**
132
- * Override form value to return current value
133
- */
134
- getFormValue() {
135
- return this.getProperty('value') || null;
136
- }
137
- // ============================================================================
138
- // PROPERTY ACCESSORS - Simplified with TyComponent
139
- // ============================================================================
140
- get value() { return this.getProperty('value'); }
141
- set value(v) { this.setProperty('value', v); }
142
- get name() { return this.getProperty('name'); }
143
- set name(v) { this.setProperty('name', v); }
144
- get placeholder() { return this.getProperty('placeholder'); }
145
- set placeholder(v) { this.setProperty('placeholder', v); }
146
- get label() { return this.getProperty('label'); }
147
- set label(v) { this.setProperty('label', v); }
148
- get disabled() { return this.getProperty('disabled'); }
149
- set disabled(v) { this.setProperty('disabled', v); }
150
- get required() { return this.getProperty('required'); }
151
- set required(v) { this.setProperty('required', v); }
152
- get error() { return this.getProperty('error'); }
153
- set error(v) { this.setProperty('error', v); }
154
- get size() {
155
- return this.getProperty('size');
156
- }
157
- set size(v) {
158
- this.setProperty('size', v);
159
- }
160
- get rows() { return this.getProperty('rows'); }
161
- set rows(v) { this.setProperty('rows', v); }
162
- get cols() { return this.getProperty('cols'); }
163
- set cols(v) { this.setProperty('cols', v); }
164
- get resize() {
165
- return this.getProperty('resize');
166
- }
167
- set resize(v) {
168
- this.setProperty('resize', v);
169
- }
170
- get minHeight() { return this.getProperty('minHeight'); }
171
- set minHeight(v) { this.setProperty('minHeight', v); }
172
- get maxHeight() { return this.getProperty('maxHeight'); }
173
- set maxHeight(v) { this.setProperty('maxHeight', v); }
174
- // ===== HELPER METHODS =====
175
- /**
176
- * Extract font-related CSS properties from element
177
- */
178
- getFontStyle(el) {
179
- const computedStyle = window.getComputedStyle(el);
180
- return {
181
- fontFamily: computedStyle.fontFamily,
182
- fontSize: computedStyle.fontSize,
183
- fontWeight: computedStyle.fontWeight,
184
- lineHeight: computedStyle.lineHeight,
185
- letterSpacing: computedStyle.letterSpacing,
186
- fontStyle: computedStyle.fontStyle
187
- };
188
- }
189
- /**
190
- * Extract spacing-related CSS properties from element
191
- */
192
- getSpacingStyle(el) {
193
- const computedStyle = window.getComputedStyle(el);
194
- return {
195
- padding: computedStyle.padding,
196
- paddingTop: computedStyle.paddingTop,
197
- paddingRight: computedStyle.paddingRight,
198
- paddingBottom: computedStyle.paddingBottom,
199
- paddingLeft: computedStyle.paddingLeft,
200
- margin: computedStyle.margin,
201
- borderWidth: computedStyle.borderWidth
202
- };
203
- }
204
- /**
205
- * Setup the hidden dummy element for height measurement
206
- */
207
- setupDummyElement(textareaEl) {
208
- const root = this.shadowRoot;
209
- const existingDummy = root.querySelector('.textarea-dummy');
210
- // Remove existing dummy if present
211
- if (existingDummy) {
212
- existingDummy.remove();
213
- }
214
- // Create new dummy element
215
- const dummyEl = document.createElement('pre');
216
- const fontStyle = this.getFontStyle(textareaEl);
217
- const spacingStyle = this.getSpacingStyle(textareaEl);
218
- // Set dummy element class
219
- dummyEl.className = 'textarea-dummy';
220
- // Apply styles to match textarea
221
- const style = dummyEl.style;
222
- style.position = 'absolute';
223
- style.top = '0';
224
- style.left = '0';
225
- style.visibility = 'hidden';
226
- style.whiteSpace = 'pre-wrap';
227
- style.wordBreak = 'break-word';
228
- style.boxSizing = 'border-box';
229
- style.overflow = 'hidden';
230
- style.pointerEvents = 'none';
231
- // Apply font styles
232
- Object.entries(fontStyle).forEach(([prop, value]) => {
233
- ;
234
- style[prop] = value;
235
- });
236
- // Apply spacing styles
237
- Object.entries(spacingStyle).forEach(([prop, value]) => {
238
- ;
239
- style[prop] = value;
240
- });
241
- // Append to shadow root
242
- root.appendChild(dummyEl);
243
- this._dummyEl = dummyEl;
244
- return dummyEl;
245
- }
246
- /**
247
- * Resize textarea based on dummy element content with min/max height constraints
248
- */
249
- resizeTextarea(textareaEl, dummyEl) {
250
- if (!textareaEl || !dummyEl)
251
- return;
252
- const value = this.value;
253
- const placeholder = this.placeholder;
254
- const content = (value === '' && placeholder) ? placeholder : value + ' ';
255
- const currentHeight = textareaEl.clientHeight;
256
- // Set dummy content
257
- dummyEl.textContent = content;
258
- // Set dummy width to match textarea
259
- dummyEl.style.width = `${textareaEl.clientWidth}px`;
260
- // Get measured height
261
- const measuredHeight = dummyEl.scrollHeight;
262
- // Parse min/max heights
263
- const minHeight = this.minHeight ? parseInt(this.minHeight.replace('px', ''), 10) : null;
264
- const maxHeight = this.maxHeight ? parseInt(this.maxHeight.replace('px', ''), 10) : null;
265
- // Apply constraints
266
- let constrainedHeight = measuredHeight;
267
- if (minHeight !== null)
268
- constrainedHeight = Math.max(constrainedHeight, minHeight);
269
- if (maxHeight !== null)
270
- constrainedHeight = Math.min(constrainedHeight, maxHeight);
271
- // Only update if height changed significantly (avoid micro-adjustments)
272
- if (Math.abs(constrainedHeight - currentHeight) > 2) {
273
- textareaEl.style.height = `${constrainedHeight}px`;
274
- // Set overflow based on whether we hit max-height
275
- const needsScroll = maxHeight !== null && measuredHeight >= maxHeight;
276
- if (needsScroll) {
277
- textareaEl.style.overflowY = 'auto';
278
- this._setupScrollbar(textareaEl);
279
- }
280
- else {
281
- textareaEl.style.overflowY = 'hidden';
282
- this._destroyScrollbar();
283
- }
284
- }
285
- // Keep scrollbar thumb in sync after resize
286
- this._scrollbar?.update();
287
- }
288
- // ============ Custom Scrollbar ============
289
- _setupScrollbar(textareaEl) {
290
- if (this._scrollbar || !isCustomScrollbarEnabled())
291
- return;
292
- // Hide native scrollbar (Firefox + Chrome/Safari via data attribute)
293
- textareaEl.style.scrollbarWidth = 'none';
294
- this.setAttribute('data-custom-scroll', '');
295
- this._scrollbar = new CustomScrollbar(textareaEl, {
296
- vertical: true,
297
- horizontal: false
298
- });
299
- // Append track to the scroll region (position: relative) so it tracks the
300
- // textarea edges and never overlaps the header/footer.
301
- const scroll = this.shadowRoot.querySelector('.textarea-scroll');
302
- if (scroll && this._scrollbar.trackY) {
303
- scroll.appendChild(this._scrollbar.trackY);
304
- }
305
- }
306
- _destroyScrollbar() {
307
- if (!this._scrollbar)
308
- return;
309
- this._scrollbar.trackY?.remove();
310
- this._scrollbar.destroy();
311
- this._scrollbar = null;
312
- // Restore native scrollbar
313
- this.removeAttribute('data-custom-scroll');
314
- if (this._textareaEl) {
315
- this._textareaEl.style.scrollbarWidth = '';
316
- }
317
- }
318
- /**
319
- * Emit custom input and change events
320
- */
321
- emitValueEvents(originalEvent) {
322
- const value = this.value;
323
- // Update form internals
324
- this._internals.setFormValue(value || '');
325
- this.updateValidity();
326
- // Emit custom events
327
- const data = {
328
- bubbles: true,
329
- composed: true,
330
- detail: {
331
- value,
332
- originalEvent
333
- }
334
- };
335
- // Use setTimeout to ensure these fire after standard events
336
- setTimeout(() => {
337
- this.dispatchEvent(new CustomEvent('input', data));
338
- this.dispatchEvent(new CustomEvent('change', data));
339
- }, 0);
340
- }
341
- /**
342
- * Build class list for textarea element
343
- */
344
- buildClassList() {
345
- const classes = [this.size];
346
- if (this.disabled)
347
- classes.push('disabled');
348
- if (this.required)
349
- classes.push('required');
350
- if (this.error)
351
- classes.push('error');
352
- if (this.resize !== 'none')
353
- classes.push(`resize-${this.resize}`);
354
- const customClass = this.getAttribute('class');
355
- if (customClass)
356
- classes.push(customClass);
357
- return classes.join(' ');
358
- }
359
- /**
360
- * Apply min/max height constraints to textarea element
361
- */
362
- applyHeightConstraints(textareaEl) {
363
- if (this.minHeight) {
364
- textareaEl.style.minHeight = this.minHeight;
365
- }
366
- if (this.maxHeight) {
367
- textareaEl.style.maxHeight = this.maxHeight;
368
- }
369
- }
370
- /**
371
- * Setup event listeners for textarea
372
- */
373
- setupTextareaEvents(textareaEl) {
374
- textareaEl.addEventListener('input', this.handleInputEvent);
375
- textareaEl.addEventListener('change', this.handleInputEvent);
376
- textareaEl.addEventListener('focus', this.handleFocusEvent);
377
- textareaEl.addEventListener('blur', this.handleBlurEvent);
378
- }
379
- // ===== RENDER =====
380
- render() {
381
- const root = this.shadowRoot;
382
- const existingContainer = root.querySelector('.textarea-container');
383
- const existingLabel = root.querySelector('label');
384
- const existingTextarea = root.querySelector('textarea');
385
- const existingError = root.querySelector('.error-message');
386
- if (existingContainer && existingTextarea) {
387
- // Update existing elements
388
- // Update label
389
- if (this.label) {
390
- if (existingLabel) {
391
- existingLabel.innerHTML = this.label +
392
- (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
393
- existingLabel.style.display = 'block';
394
- }
395
- }
396
- else if (existingLabel) {
397
- existingLabel.style.display = 'none';
398
- }
399
- // Update textarea
400
- existingTextarea.value = this.value || '';
401
- existingTextarea.placeholder = this.placeholder || '';
402
- existingTextarea.disabled = this.disabled;
403
- existingTextarea.required = this.required;
404
- existingTextarea.className = this.buildClassList();
405
- // a11y: shadow <label> isn't linked to the control; name it directly.
406
- if (this.label)
407
- existingTextarea.setAttribute('aria-label', this.label);
408
- else
409
- existingTextarea.removeAttribute('aria-label');
410
- // Set name for form association
411
- if (this.name) {
412
- existingTextarea.setAttribute('name', this.name);
413
- }
414
- else {
415
- existingTextarea.removeAttribute('name');
416
- }
417
- // Set rows/cols
418
- if (this.rows)
419
- existingTextarea.setAttribute('rows', this.rows);
420
- if (this.cols)
421
- existingTextarea.setAttribute('cols', this.cols);
422
- // Apply height constraints
423
- this.applyHeightConstraints(existingTextarea);
424
- // Update error message
425
- if (this.error) {
426
- if (!existingError) {
427
- const errorEl = document.createElement('div');
428
- errorEl.className = 'error-message';
429
- existingContainer.appendChild(errorEl);
430
- }
431
- const errorEl = root.querySelector('.error-message');
432
- if (errorEl)
433
- errorEl.textContent = this.error;
434
- }
435
- else if (existingError) {
436
- existingError.remove();
437
- }
438
- // Setup dummy element and trigger resize
439
- const dummyEl = this.setupDummyElement(existingTextarea);
440
- setTimeout(() => this.resizeTextarea(existingTextarea, dummyEl), 0);
441
- this._textareaEl = existingTextarea;
442
- }
443
- else {
444
- // Create new structure
445
- const container = document.createElement('div');
446
- container.className = 'textarea-container';
447
- const labelEl = document.createElement('label');
448
- labelEl.className = 'ty-field-label';
449
- if (this.label) {
450
- labelEl.innerHTML = this.label +
451
- (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
452
- labelEl.style.display = 'block';
453
- }
454
- else {
455
- labelEl.style.display = 'none';
456
- }
457
- const textareaEl = document.createElement('textarea');
458
- textareaEl.value = this.value || '';
459
- textareaEl.placeholder = this.placeholder || '';
460
- textareaEl.disabled = this.disabled;
461
- textareaEl.required = this.required;
462
- textareaEl.className = this.buildClassList();
463
- if (this.label)
464
- textareaEl.setAttribute('aria-label', this.label); // a11y: name the control
465
- // Set name for form association
466
- if (this.name) {
467
- textareaEl.setAttribute('name', this.name);
468
- }
469
- // Set default or specified rows/cols
470
- textareaEl.setAttribute('rows', this.rows || '3');
471
- if (this.cols)
472
- textareaEl.setAttribute('cols', this.cols);
473
- // Apply height constraints
474
- this.applyHeightConstraints(textareaEl);
475
- // Setup event listeners
476
- this.setupTextareaEvents(textareaEl);
477
- // Build composer structure:
478
- // .textarea-wrapper (border) > [.textarea-header > slot] · .textarea-scroll(textarea) · [.textarea-footer > slot]
479
- const wrapperEl = document.createElement('div');
480
- wrapperEl.className = 'textarea-wrapper';
481
- const headerEl = document.createElement('div');
482
- headerEl.className = 'textarea-header';
483
- const headerSlot = document.createElement('slot');
484
- headerSlot.setAttribute('name', 'header');
485
- headerEl.appendChild(headerSlot);
486
- // Scroll region wraps only the textarea so the custom scrollbar can't
487
- // overlap header/footer.
488
- const scrollEl = document.createElement('div');
489
- scrollEl.className = 'textarea-scroll';
490
- scrollEl.appendChild(textareaEl);
491
- const footerEl = document.createElement('div');
492
- footerEl.className = 'textarea-footer';
493
- const footerSlot = document.createElement('slot');
494
- footerSlot.setAttribute('name', 'footer');
495
- footerEl.appendChild(footerSlot);
496
- wrapperEl.appendChild(headerEl);
497
- wrapperEl.appendChild(scrollEl);
498
- wrapperEl.appendChild(footerEl);
499
- // Collapse empty slots (no padding/divider) by toggling .has-content.
500
- // slotchange fires on initial assignment too.
501
- const syncSlot = (slot, region) => {
502
- const update = () => region.classList.toggle('has-content', slot.assignedNodes({ flatten: true }).length > 0);
503
- slot.addEventListener('slotchange', update);
504
- update();
505
- };
506
- syncSlot(headerSlot, headerEl);
507
- syncSlot(footerSlot, footerEl);
508
- container.appendChild(labelEl);
509
- container.appendChild(wrapperEl);
510
- // Add error message if present
511
- if (this.error) {
512
- const errorEl = document.createElement('div');
513
- errorEl.className = 'error-message';
514
- errorEl.textContent = this.error;
515
- container.appendChild(errorEl);
516
- }
517
- root.appendChild(container);
518
- // Setup dummy element and trigger initial resize
519
- const dummyEl = this.setupDummyElement(textareaEl);
520
- setTimeout(() => this.resizeTextarea(textareaEl, dummyEl), 0);
521
- this._textareaEl = textareaEl;
522
- }
523
- // Reflect constraint validation to the owning <form>. Without this `required`
524
- // is cosmetic — the inner shadow <textarea> can't block the outer form.
525
- this.updateValidity();
526
- }
527
- /** required + empty => valueMissing; otherwise valid. Disabled fields are
528
- * barred from constraint validation. */
529
- updateValidity() {
530
- const ta = this.shadowRoot?.querySelector('textarea');
531
- if (this.disabled) {
532
- this._internals.setValidity({});
533
- return;
534
- }
535
- const current = ta ? ta.value : String(this.value ?? '');
536
- if (this.required && current.trim() === '') {
537
- this._internals.setValidity({ valueMissing: true }, 'Please fill out this field', ta ?? undefined);
538
- }
539
- else {
540
- this._internals.setValidity({});
541
- }
542
- }
9
+ constructor() {
10
+ super();
11
+ this._textareaEl = null;
12
+ this._dummyEl = null;
13
+ this._scrollbar = null;
14
+ this._isFocused = false;
15
+ this.handleInputEvent = (e) => {
16
+ const target = e.target;
17
+ const newValue = target.value;
18
+ this.value = newValue;
19
+ if (this._textareaEl && this._dummyEl) {
20
+ this.resizeTextarea(this._textareaEl, this._dummyEl);
21
+ }
22
+ this.emitValueEvents(e);
23
+ };
24
+ this.handleFocusEvent = () => {
25
+ this._isFocused = true;
26
+ };
27
+ this.handleBlurEvent = () => {
28
+ this._isFocused = false;
29
+ };
30
+ const shadow = this.shadowRoot;
31
+ ensureStyles(shadow, { css: textareaStyles, id: 'ty-textarea' });
32
+ if (isCustomScrollbarEnabled()) {
33
+ ensureStyles(shadow, { css: customScrollbarStyles, id: 'ty-custom-scrollbar' });
34
+ }
35
+ }
36
+ onConnect() {
37
+ }
38
+ onDisconnect() {
39
+ this._destroyScrollbar();
40
+ if (this._textareaEl) {
41
+ const newTextarea = this._textareaEl.cloneNode(true);
42
+ this._textareaEl.parentNode?.replaceChild(newTextarea, this._textareaEl);
43
+ this._textareaEl = null;
44
+ }
45
+ this._dummyEl = null;
46
+ }
47
+ onPropertiesChanged(changes) {
48
+ }
49
+ getFormValue() {
50
+ return this.getProperty('value') || null;
51
+ }
52
+ get value() { return this.getProperty('value'); }
53
+ set value(v) { this.setProperty('value', v); }
54
+ get name() { return this.getProperty('name'); }
55
+ set name(v) { this.setProperty('name', v); }
56
+ get placeholder() { return this.getProperty('placeholder'); }
57
+ set placeholder(v) { this.setProperty('placeholder', v); }
58
+ get label() { return this.getProperty('label'); }
59
+ set label(v) { this.setProperty('label', v); }
60
+ get disabled() { return this.getProperty('disabled'); }
61
+ set disabled(v) { this.setProperty('disabled', v); }
62
+ get required() { return this.getProperty('required'); }
63
+ set required(v) { this.setProperty('required', v); }
64
+ get error() { return this.getProperty('error'); }
65
+ set error(v) { this.setProperty('error', v); }
66
+ get size() {
67
+ return this.getProperty('size');
68
+ }
69
+ set size(v) {
70
+ this.setProperty('size', v);
71
+ }
72
+ get rows() { return this.getProperty('rows'); }
73
+ set rows(v) { this.setProperty('rows', v); }
74
+ get cols() { return this.getProperty('cols'); }
75
+ set cols(v) { this.setProperty('cols', v); }
76
+ get resize() {
77
+ return this.getProperty('resize');
78
+ }
79
+ set resize(v) {
80
+ this.setProperty('resize', v);
81
+ }
82
+ get minHeight() { return this.getProperty('minHeight'); }
83
+ set minHeight(v) { this.setProperty('minHeight', v); }
84
+ get maxHeight() { return this.getProperty('maxHeight'); }
85
+ set maxHeight(v) { this.setProperty('maxHeight', v); }
86
+ getFontStyle(el) {
87
+ const computedStyle = window.getComputedStyle(el);
88
+ return {
89
+ fontFamily: computedStyle.fontFamily,
90
+ fontSize: computedStyle.fontSize,
91
+ fontWeight: computedStyle.fontWeight,
92
+ lineHeight: computedStyle.lineHeight,
93
+ letterSpacing: computedStyle.letterSpacing,
94
+ fontStyle: computedStyle.fontStyle
95
+ };
96
+ }
97
+ getSpacingStyle(el) {
98
+ const computedStyle = window.getComputedStyle(el);
99
+ return {
100
+ padding: computedStyle.padding,
101
+ paddingTop: computedStyle.paddingTop,
102
+ paddingRight: computedStyle.paddingRight,
103
+ paddingBottom: computedStyle.paddingBottom,
104
+ paddingLeft: computedStyle.paddingLeft,
105
+ margin: computedStyle.margin,
106
+ borderWidth: computedStyle.borderWidth
107
+ };
108
+ }
109
+ setupDummyElement(textareaEl) {
110
+ const root = this.shadowRoot;
111
+ const existingDummy = root.querySelector('.textarea-dummy');
112
+ if (existingDummy) {
113
+ existingDummy.remove();
114
+ }
115
+ const dummyEl = document.createElement('pre');
116
+ const fontStyle = this.getFontStyle(textareaEl);
117
+ const spacingStyle = this.getSpacingStyle(textareaEl);
118
+ dummyEl.className = 'textarea-dummy';
119
+ const style = dummyEl.style;
120
+ style.position = 'absolute';
121
+ style.top = '0';
122
+ style.left = '0';
123
+ style.visibility = 'hidden';
124
+ style.whiteSpace = 'pre-wrap';
125
+ style.wordBreak = 'break-word';
126
+ style.boxSizing = 'border-box';
127
+ style.overflow = 'hidden';
128
+ style.pointerEvents = 'none';
129
+ Object.entries(fontStyle).forEach(([prop, value]) => {
130
+ ;
131
+ style[prop] = value;
132
+ });
133
+ Object.entries(spacingStyle).forEach(([prop, value]) => {
134
+ ;
135
+ style[prop] = value;
136
+ });
137
+ root.appendChild(dummyEl);
138
+ this._dummyEl = dummyEl;
139
+ return dummyEl;
140
+ }
141
+ resizeTextarea(textareaEl, dummyEl) {
142
+ if (!textareaEl || !dummyEl)
143
+ return;
144
+ const value = this.value;
145
+ const placeholder = this.placeholder;
146
+ const content = (value === '' && placeholder) ? placeholder : value + ' ';
147
+ const currentHeight = textareaEl.clientHeight;
148
+ dummyEl.textContent = content;
149
+ dummyEl.style.width = `${textareaEl.clientWidth}px`;
150
+ const measuredHeight = dummyEl.scrollHeight;
151
+ const minHeight = this.minHeight ? parseInt(this.minHeight.replace('px', ''), 10) : null;
152
+ const maxHeight = this.maxHeight ? parseInt(this.maxHeight.replace('px', ''), 10) : null;
153
+ let constrainedHeight = measuredHeight;
154
+ if (minHeight !== null)
155
+ constrainedHeight = Math.max(constrainedHeight, minHeight);
156
+ if (maxHeight !== null)
157
+ constrainedHeight = Math.min(constrainedHeight, maxHeight);
158
+ if (Math.abs(constrainedHeight - currentHeight) > 2) {
159
+ textareaEl.style.height = `${constrainedHeight}px`;
160
+ const needsScroll = maxHeight !== null && measuredHeight >= maxHeight;
161
+ if (needsScroll) {
162
+ textareaEl.style.overflowY = 'auto';
163
+ this._setupScrollbar(textareaEl);
164
+ }
165
+ else {
166
+ textareaEl.style.overflowY = 'hidden';
167
+ this._destroyScrollbar();
168
+ }
169
+ }
170
+ this._scrollbar?.update();
171
+ }
172
+ _setupScrollbar(textareaEl) {
173
+ if (this._scrollbar || !isCustomScrollbarEnabled())
174
+ return;
175
+ textareaEl.style.scrollbarWidth = 'none';
176
+ this.setAttribute('data-custom-scroll', '');
177
+ this._scrollbar = new CustomScrollbar(textareaEl, {
178
+ vertical: true,
179
+ horizontal: false
180
+ });
181
+ const scroll = this.shadowRoot.querySelector('.textarea-scroll');
182
+ if (scroll && this._scrollbar.trackY) {
183
+ scroll.appendChild(this._scrollbar.trackY);
184
+ }
185
+ }
186
+ _destroyScrollbar() {
187
+ if (!this._scrollbar)
188
+ return;
189
+ this._scrollbar.trackY?.remove();
190
+ this._scrollbar.destroy();
191
+ this._scrollbar = null;
192
+ this.removeAttribute('data-custom-scroll');
193
+ if (this._textareaEl) {
194
+ this._textareaEl.style.scrollbarWidth = '';
195
+ }
196
+ }
197
+ emitValueEvents(originalEvent) {
198
+ const value = this.value;
199
+ this._internals.setFormValue(value || '');
200
+ this.updateValidity();
201
+ const data = {
202
+ bubbles: true,
203
+ composed: true,
204
+ detail: {
205
+ value,
206
+ originalEvent
207
+ }
208
+ };
209
+ setTimeout(() => {
210
+ this.dispatchEvent(new CustomEvent('input', data));
211
+ this.dispatchEvent(new CustomEvent('change', data));
212
+ }, 0);
213
+ }
214
+ buildClassList() {
215
+ const classes = [this.size];
216
+ if (this.disabled)
217
+ classes.push('disabled');
218
+ if (this.required)
219
+ classes.push('required');
220
+ if (this.error)
221
+ classes.push('error');
222
+ if (this.resize !== 'none')
223
+ classes.push(`resize-${this.resize}`);
224
+ const customClass = this.getAttribute('class');
225
+ if (customClass)
226
+ classes.push(customClass);
227
+ return classes.join(' ');
228
+ }
229
+ applyHeightConstraints(textareaEl) {
230
+ if (this.minHeight) {
231
+ textareaEl.style.minHeight = this.minHeight;
232
+ }
233
+ if (this.maxHeight) {
234
+ textareaEl.style.maxHeight = this.maxHeight;
235
+ }
236
+ }
237
+ setupTextareaEvents(textareaEl) {
238
+ textareaEl.addEventListener('input', this.handleInputEvent);
239
+ textareaEl.addEventListener('change', this.handleInputEvent);
240
+ textareaEl.addEventListener('focus', this.handleFocusEvent);
241
+ textareaEl.addEventListener('blur', this.handleBlurEvent);
242
+ }
243
+ render() {
244
+ const root = this.shadowRoot;
245
+ const existingContainer = root.querySelector('.textarea-container');
246
+ const existingLabel = root.querySelector('label');
247
+ const existingTextarea = root.querySelector('textarea');
248
+ const existingError = root.querySelector('.error-message');
249
+ if (existingContainer && existingTextarea) {
250
+ if (this.label) {
251
+ if (existingLabel) {
252
+ existingLabel.innerHTML = this.label +
253
+ (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
254
+ existingLabel.style.display = 'block';
255
+ }
256
+ }
257
+ else if (existingLabel) {
258
+ existingLabel.style.display = 'none';
259
+ }
260
+ existingTextarea.value = this.value || '';
261
+ existingTextarea.placeholder = this.placeholder || '';
262
+ existingTextarea.disabled = this.disabled;
263
+ existingTextarea.required = this.required;
264
+ existingTextarea.className = this.buildClassList();
265
+ if (this.label)
266
+ existingTextarea.setAttribute('aria-label', this.label);
267
+ else
268
+ existingTextarea.removeAttribute('aria-label');
269
+ if (this.name) {
270
+ existingTextarea.setAttribute('name', this.name);
271
+ }
272
+ else {
273
+ existingTextarea.removeAttribute('name');
274
+ }
275
+ if (this.rows)
276
+ existingTextarea.setAttribute('rows', this.rows);
277
+ if (this.cols)
278
+ existingTextarea.setAttribute('cols', this.cols);
279
+ this.applyHeightConstraints(existingTextarea);
280
+ if (this.error) {
281
+ if (!existingError) {
282
+ const errorEl = document.createElement('div');
283
+ errorEl.className = 'error-message';
284
+ existingContainer.appendChild(errorEl);
285
+ }
286
+ const errorEl = root.querySelector('.error-message');
287
+ if (errorEl)
288
+ errorEl.textContent = this.error;
289
+ }
290
+ else if (existingError) {
291
+ existingError.remove();
292
+ }
293
+ const dummyEl = this.setupDummyElement(existingTextarea);
294
+ setTimeout(() => this.resizeTextarea(existingTextarea, dummyEl), 0);
295
+ this._textareaEl = existingTextarea;
296
+ }
297
+ else {
298
+ const container = document.createElement('div');
299
+ container.className = 'textarea-container';
300
+ const labelEl = document.createElement('label');
301
+ labelEl.className = 'ty-field-label';
302
+ if (this.label) {
303
+ labelEl.innerHTML = this.label +
304
+ (this.required ? ` <span class="required-icon">${REQUIRED_ICON}</span>` : '');
305
+ labelEl.style.display = 'block';
306
+ }
307
+ else {
308
+ labelEl.style.display = 'none';
309
+ }
310
+ const textareaEl = document.createElement('textarea');
311
+ textareaEl.value = this.value || '';
312
+ textareaEl.placeholder = this.placeholder || '';
313
+ textareaEl.disabled = this.disabled;
314
+ textareaEl.required = this.required;
315
+ textareaEl.className = this.buildClassList();
316
+ if (this.label)
317
+ textareaEl.setAttribute('aria-label', this.label);
318
+ if (this.name) {
319
+ textareaEl.setAttribute('name', this.name);
320
+ }
321
+ textareaEl.setAttribute('rows', this.rows || '3');
322
+ if (this.cols)
323
+ textareaEl.setAttribute('cols', this.cols);
324
+ this.applyHeightConstraints(textareaEl);
325
+ this.setupTextareaEvents(textareaEl);
326
+ const wrapperEl = document.createElement('div');
327
+ wrapperEl.className = 'textarea-wrapper';
328
+ const headerEl = document.createElement('div');
329
+ headerEl.className = 'textarea-header';
330
+ const headerSlot = document.createElement('slot');
331
+ headerSlot.setAttribute('name', 'header');
332
+ headerEl.appendChild(headerSlot);
333
+ const scrollEl = document.createElement('div');
334
+ scrollEl.className = 'textarea-scroll';
335
+ scrollEl.appendChild(textareaEl);
336
+ const footerEl = document.createElement('div');
337
+ footerEl.className = 'textarea-footer';
338
+ const footerSlot = document.createElement('slot');
339
+ footerSlot.setAttribute('name', 'footer');
340
+ footerEl.appendChild(footerSlot);
341
+ wrapperEl.appendChild(headerEl);
342
+ wrapperEl.appendChild(scrollEl);
343
+ wrapperEl.appendChild(footerEl);
344
+ const syncSlot = (slot, region) => {
345
+ const update = () => region.classList.toggle('has-content', slot.assignedNodes({ flatten: true }).length > 0);
346
+ slot.addEventListener('slotchange', update);
347
+ update();
348
+ };
349
+ syncSlot(headerSlot, headerEl);
350
+ syncSlot(footerSlot, footerEl);
351
+ container.appendChild(labelEl);
352
+ container.appendChild(wrapperEl);
353
+ if (this.error) {
354
+ const errorEl = document.createElement('div');
355
+ errorEl.className = 'error-message';
356
+ errorEl.textContent = this.error;
357
+ container.appendChild(errorEl);
358
+ }
359
+ root.appendChild(container);
360
+ const dummyEl = this.setupDummyElement(textareaEl);
361
+ setTimeout(() => this.resizeTextarea(textareaEl, dummyEl), 0);
362
+ this._textareaEl = textareaEl;
363
+ }
364
+ this.updateValidity();
365
+ }
366
+ updateValidity() {
367
+ const ta = this.shadowRoot?.querySelector('textarea');
368
+ if (this.disabled) {
369
+ this._internals.setValidity({});
370
+ return;
371
+ }
372
+ const current = ta ? ta.value : String(this.value ?? '');
373
+ if (this.required && current.trim() === '') {
374
+ this._internals.setValidity({ valueMissing: true }, 'Please fill out this field', ta ?? undefined);
375
+ }
376
+ else {
377
+ this._internals.setValidity({});
378
+ }
379
+ }
543
380
  }
544
381
  TyTextarea.formAssociated = true;
545
- // ============================================================================
546
- // PROPERTY CONFIGURATION - Declarative property lifecycle
547
- // ============================================================================
548
382
  TyTextarea.properties = {
549
- // Core properties
550
- value: {
551
- type: 'string',
552
- visual: true,
553
- formValue: true,
554
- emitChange: true,
555
- default: ''
556
- },
557
- name: {
558
- type: 'string',
559
- default: ''
560
- },
561
- placeholder: {
562
- type: 'string',
563
- visual: true,
564
- default: ''
565
- },
566
- label: {
567
- type: 'string',
568
- visual: true,
569
- default: ''
570
- },
571
- disabled: {
572
- type: 'boolean',
573
- visual: true,
574
- default: false
575
- },
576
- required: {
577
- type: 'boolean',
578
- visual: true,
579
- default: false
580
- },
581
- error: {
582
- type: 'string',
583
- visual: true,
584
- default: ''
585
- },
586
- size: {
587
- type: 'string',
588
- visual: true,
589
- default: 'md',
590
- validate: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v),
591
- coerce: (v) => {
592
- if (!['xs', 'sm', 'md', 'lg', 'xl'].includes(v)) {
593
- console.warn(`[ty-textarea] Invalid size '${v}'. Using 'md'.`);
594
- return 'md';
595
- }
596
- return v;
597
- }
598
- },
599
- // Layout properties
600
- rows: {
601
- type: 'string',
602
- visual: true,
603
- default: '3',
604
- coerce: (v) => String(v)
605
- },
606
- cols: {
607
- type: 'string',
608
- visual: true,
609
- default: '',
610
- coerce: (v) => v ? String(v) : ''
611
- },
612
- resize: {
613
- type: 'string',
614
- visual: true,
615
- default: 'none',
616
- validate: (v) => ['none', 'both', 'horizontal', 'vertical'].includes(v),
617
- coerce: (v) => {
618
- if (!['none', 'both', 'horizontal', 'vertical'].includes(v)) {
619
- console.warn(`[ty-textarea] Invalid resize '${v}'. Using 'none'.`);
620
- return 'none';
621
- }
622
- return v;
623
- }
624
- },
625
- minHeight: {
626
- type: 'string',
627
- visual: true,
628
- default: ''
629
- },
630
- maxHeight: {
631
- type: 'string',
632
- visual: true,
633
- default: ''
634
- }
383
+ value: {
384
+ type: 'string',
385
+ visual: true,
386
+ formValue: true,
387
+ emitChange: true,
388
+ default: ''
389
+ },
390
+ name: {
391
+ type: 'string',
392
+ default: ''
393
+ },
394
+ placeholder: {
395
+ type: 'string',
396
+ visual: true,
397
+ default: ''
398
+ },
399
+ label: {
400
+ type: 'string',
401
+ visual: true,
402
+ default: ''
403
+ },
404
+ disabled: {
405
+ type: 'boolean',
406
+ visual: true,
407
+ default: false
408
+ },
409
+ required: {
410
+ type: 'boolean',
411
+ visual: true,
412
+ default: false
413
+ },
414
+ error: {
415
+ type: 'string',
416
+ visual: true,
417
+ default: ''
418
+ },
419
+ size: {
420
+ type: 'string',
421
+ visual: true,
422
+ default: DEFAULT_SIZE,
423
+ validate: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v),
424
+ coerce: (v) => {
425
+ if (!['xs', 'sm', 'md', 'lg', 'xl'].includes(v)) {
426
+ console.warn(`[ty-textarea] Invalid size '${v}'. Using '${DEFAULT_SIZE}'.`);
427
+ return DEFAULT_SIZE;
428
+ }
429
+ return v;
430
+ }
431
+ },
432
+ rows: {
433
+ type: 'string',
434
+ visual: true,
435
+ default: '3',
436
+ coerce: (v) => String(v)
437
+ },
438
+ cols: {
439
+ type: 'string',
440
+ visual: true,
441
+ default: '',
442
+ coerce: (v) => v ? String(v) : ''
443
+ },
444
+ resize: {
445
+ type: 'string',
446
+ visual: true,
447
+ default: 'none',
448
+ validate: (v) => ['none', 'both', 'horizontal', 'vertical'].includes(v),
449
+ coerce: (v) => {
450
+ if (!['none', 'both', 'horizontal', 'vertical'].includes(v)) {
451
+ console.warn(`[ty-textarea] Invalid resize '${v}'. Using 'none'.`);
452
+ return 'none';
453
+ }
454
+ return v;
455
+ }
456
+ },
457
+ minHeight: {
458
+ type: 'string',
459
+ visual: true,
460
+ default: ''
461
+ },
462
+ maxHeight: {
463
+ type: 'string',
464
+ visual: true,
465
+ default: ''
466
+ }
635
467
  };
636
- // Register the component
637
468
  if (!customElements.get('ty-textarea')) {
638
- customElements.define('ty-textarea', TyTextarea);
469
+ customElements.define('ty-textarea', TyTextarea);
639
470
  }
640
- //# sourceMappingURL=textarea.js.map