tyrell-components 1.0.0-RC13 → 1.0.0-RC14

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 (355) hide show
  1. package/README.md +1 -1
  2. package/css/tyrell-theme.css +167 -178
  3. package/css/tyrell.css +19 -0
  4. package/dist/tyrell-theme.css +167 -178
  5. package/dist/tyrell.css +19 -0
  6. package/dist/tyrell.js +1 -1
  7. package/lib/base/ty-component.d.ts +20 -10
  8. package/lib/base/ty-component.js +21 -144
  9. package/lib/components/button.d.ts +2 -16
  10. package/lib/components/button.js +0 -36
  11. package/lib/components/calendar-month.d.ts +2 -48
  12. package/lib/components/calendar-month.js +4 -166
  13. package/lib/components/calendar-navigation.d.ts +3 -48
  14. package/lib/components/calendar-navigation.js +3 -126
  15. package/lib/components/calendar.d.ts +3 -80
  16. package/lib/components/calendar.js +8 -230
  17. package/lib/components/checkbox.d.ts +4 -71
  18. package/lib/components/checkbox.js +7 -151
  19. package/lib/components/copy.d.ts +3 -56
  20. package/lib/components/copy.js +1 -120
  21. package/lib/components/date-picker.d.ts +13 -162
  22. package/lib/components/date-picker.js +41 -592
  23. package/lib/components/file-upload.d.ts +9 -15
  24. package/lib/components/file-upload.js +19 -41
  25. package/lib/components/icon.d.ts +3 -24
  26. package/lib/components/icon.js +1 -90
  27. package/lib/components/input.d.ts +7 -107
  28. package/lib/components/input.js +5 -193
  29. package/lib/components/modal.d.ts +17 -26
  30. package/lib/components/modal.js +57 -201
  31. package/lib/components/option.d.ts +2 -9
  32. package/lib/components/option.js +0 -48
  33. package/lib/components/popup.d.ts +2 -17
  34. package/lib/components/popup.js +8 -139
  35. package/lib/components/radio.d.ts +3 -23
  36. package/lib/components/radio.js +10 -65
  37. package/lib/components/resize-observer.d.ts +3 -30
  38. package/lib/components/resize-observer.js +12 -46
  39. package/lib/components/scroll-container.d.ts +2 -12
  40. package/lib/components/scroll-container.js +3 -46
  41. package/lib/components/select.d.ts +22 -153
  42. package/lib/components/select.js +33 -590
  43. package/lib/components/selected-tags.d.ts +4 -47
  44. package/lib/components/selected-tags.js +1 -95
  45. package/lib/components/step.d.ts +1 -6
  46. package/lib/components/step.js +0 -6
  47. package/lib/components/switch.d.ts +0 -11
  48. package/lib/components/switch.js +2 -36
  49. package/lib/components/tab.d.ts +1 -6
  50. package/lib/components/tab.js +0 -7
  51. package/lib/components/tabs.d.ts +5 -63
  52. package/lib/components/tabs.js +20 -283
  53. package/lib/components/tag.d.ts +2 -36
  54. package/lib/components/tag.js +5 -105
  55. package/lib/components/textarea.d.ts +6 -86
  56. package/lib/components/textarea.js +2 -173
  57. package/lib/components/tooltip.d.ts +3 -11
  58. package/lib/components/tooltip.js +24 -160
  59. package/lib/components/wizard.d.ts +6 -54
  60. package/lib/components/wizard.js +15 -228
  61. package/lib/icons/fontawesome/brands.d.ts +0 -1
  62. package/lib/icons/fontawesome/brands.js +0 -8
  63. package/lib/icons/fontawesome/regular.d.ts +0 -1
  64. package/lib/icons/fontawesome/regular.js +0 -8
  65. package/lib/icons/fontawesome/solid.d.ts +0 -1
  66. package/lib/icons/fontawesome/solid.js +0 -8
  67. package/lib/icons/heroicons/micro.d.ts +0 -1
  68. package/lib/icons/heroicons/micro.js +0 -8
  69. package/lib/icons/heroicons/mini.d.ts +0 -1
  70. package/lib/icons/heroicons/mini.js +0 -8
  71. package/lib/icons/heroicons/outline.d.ts +0 -1
  72. package/lib/icons/heroicons/outline.js +0 -8
  73. package/lib/icons/heroicons/solid.d.ts +0 -1
  74. package/lib/icons/heroicons/solid.js +0 -8
  75. package/lib/icons/lucide.d.ts +0 -1
  76. package/lib/icons/lucide.js +0 -1872
  77. package/lib/icons/material/filled.d.ts +0 -1
  78. package/lib/icons/material/filled.js +0 -10
  79. package/lib/icons/material/outlined.d.ts +0 -1
  80. package/lib/icons/material/outlined.js +0 -10
  81. package/lib/icons/material/round.d.ts +0 -1
  82. package/lib/icons/material/round.js +0 -10
  83. package/lib/icons/material/sharp.d.ts +0 -1
  84. package/lib/icons/material/sharp.js +0 -10
  85. package/lib/icons/material/two-tone.d.ts +0 -1
  86. package/lib/icons/material/two-tone.js +0 -10
  87. package/lib/index.d.ts +0 -1
  88. package/lib/index.js +0 -20
  89. package/lib/styles/button.d.ts +2 -20
  90. package/lib/styles/button.js +22 -122
  91. package/lib/styles/calendar-month.d.ts +1 -5
  92. package/lib/styles/calendar-month.js +4 -61
  93. package/lib/styles/calendar-navigation.d.ts +2 -6
  94. package/lib/styles/calendar-navigation.js +2 -27
  95. package/lib/styles/calendar.d.ts +1 -2
  96. package/lib/styles/calendar.js +1 -14
  97. package/lib/styles/checkbox.d.ts +0 -1
  98. package/lib/styles/checkbox.js +1 -23
  99. package/lib/styles/copy.d.ts +0 -1
  100. package/lib/styles/copy.js +2 -17
  101. package/lib/styles/custom-scrollbar.d.ts +1 -2
  102. package/lib/styles/custom-scrollbar.js +3 -18
  103. package/lib/styles/date-picker.d.ts +1 -5
  104. package/lib/styles/date-picker.js +35 -39
  105. package/lib/styles/file-upload.d.ts +1 -2
  106. package/lib/styles/file-upload.js +1 -16
  107. package/lib/styles/icon.d.ts +2 -6
  108. package/lib/styles/icon.js +7 -21
  109. package/lib/styles/input.d.ts +1 -6
  110. package/lib/styles/input.js +5 -54
  111. package/lib/styles/modal.d.ts +1 -2
  112. package/lib/styles/modal.js +21 -23
  113. package/lib/styles/option.d.ts +2 -6
  114. package/lib/styles/option.js +17 -12
  115. package/lib/styles/popup.d.ts +1 -2
  116. package/lib/styles/popup.js +26 -30
  117. package/lib/styles/radio.d.ts +0 -1
  118. package/lib/styles/radio.js +2 -16
  119. package/lib/styles/resize-observer.d.ts +1 -2
  120. package/lib/styles/resize-observer.js +0 -7
  121. package/lib/styles/scroll-container.d.ts +1 -2
  122. package/lib/styles/scroll-container.js +0 -19
  123. package/lib/styles/select-base.d.ts +0 -1
  124. package/lib/styles/select-base.js +68 -79
  125. package/lib/styles/select.d.ts +1 -2
  126. package/lib/styles/select.js +7 -15
  127. package/lib/styles/step.d.ts +1 -2
  128. package/lib/styles/step.js +0 -4
  129. package/lib/styles/switch.d.ts +0 -1
  130. package/lib/styles/switch.js +2 -20
  131. package/lib/styles/tab.d.ts +1 -2
  132. package/lib/styles/tab.js +0 -5
  133. package/lib/styles/tabs.d.ts +3 -11
  134. package/lib/styles/tabs.js +10 -48
  135. package/lib/styles/tag.d.ts +1 -5
  136. package/lib/styles/tag.js +2 -31
  137. package/lib/styles/textarea.d.ts +1 -2
  138. package/lib/styles/textarea.js +2 -45
  139. package/lib/styles/tooltip.d.ts +0 -1
  140. package/lib/styles/tooltip.js +0 -10
  141. package/lib/styles/wizard.d.ts +5 -21
  142. package/lib/styles/wizard.js +8 -88
  143. package/lib/types/common.d.ts +0 -2
  144. package/lib/types/common.js +0 -12
  145. package/lib/utils/calendar-utils.d.ts +4 -21
  146. package/lib/utils/calendar-utils.js +4 -177
  147. package/lib/utils/custom-scrollbar.d.ts +6 -20
  148. package/lib/utils/custom-scrollbar.js +1 -45
  149. package/lib/utils/flavor-sheet.d.ts +0 -1
  150. package/lib/utils/flavor-sheet.js +0 -23
  151. package/lib/utils/icon-registry.d.ts +0 -2
  152. package/lib/utils/icon-registry.js +1 -107
  153. package/lib/utils/icons.d.ts +0 -1
  154. package/lib/utils/icons.js +0 -9
  155. package/lib/utils/loader-registry.d.ts +0 -1
  156. package/lib/utils/loader-registry.js +0 -32
  157. package/lib/utils/locale.d.ts +2 -23
  158. package/lib/utils/locale.js +0 -86
  159. package/lib/utils/mobile.d.ts +0 -1
  160. package/lib/utils/mobile.js +0 -17
  161. package/lib/utils/number-format.d.ts +6 -3
  162. package/lib/utils/number-format.js +11 -67
  163. package/lib/utils/positioning.d.ts +50 -7
  164. package/lib/utils/positioning.js +52 -62
  165. package/lib/utils/property-capture.d.ts +5 -27
  166. package/lib/utils/property-capture.js +0 -108
  167. package/lib/utils/property-manager.d.ts +0 -1
  168. package/lib/utils/property-manager.js +0 -70
  169. package/lib/utils/resize-observer.d.ts +0 -1
  170. package/lib/utils/resize-observer.js +0 -36
  171. package/lib/utils/scroll-lock.d.ts +4 -11
  172. package/lib/utils/scroll-lock.js +0 -107
  173. package/lib/utils/styles.d.ts +0 -1
  174. package/lib/utils/styles.js +0 -24
  175. package/lib/version.d.ts +1 -2
  176. package/lib/version.js +1 -11
  177. package/package.json +2 -2
  178. package/lib/base/ty-component.d.ts.map +0 -1
  179. package/lib/base/ty-component.js.map +0 -1
  180. package/lib/components/button.d.ts.map +0 -1
  181. package/lib/components/button.js.map +0 -1
  182. package/lib/components/calendar-month.d.ts.map +0 -1
  183. package/lib/components/calendar-month.js.map +0 -1
  184. package/lib/components/calendar-navigation.d.ts.map +0 -1
  185. package/lib/components/calendar-navigation.js.map +0 -1
  186. package/lib/components/calendar.d.ts.map +0 -1
  187. package/lib/components/calendar.js.map +0 -1
  188. package/lib/components/checkbox.d.ts.map +0 -1
  189. package/lib/components/checkbox.js.map +0 -1
  190. package/lib/components/copy.d.ts.map +0 -1
  191. package/lib/components/copy.js.map +0 -1
  192. package/lib/components/date-picker.d.ts.map +0 -1
  193. package/lib/components/date-picker.js.map +0 -1
  194. package/lib/components/file-upload.d.ts.map +0 -1
  195. package/lib/components/file-upload.js.map +0 -1
  196. package/lib/components/icon.d.ts.map +0 -1
  197. package/lib/components/icon.js.map +0 -1
  198. package/lib/components/input.d.ts.map +0 -1
  199. package/lib/components/input.js.map +0 -1
  200. package/lib/components/modal.d.ts.map +0 -1
  201. package/lib/components/modal.js.map +0 -1
  202. package/lib/components/option.d.ts.map +0 -1
  203. package/lib/components/option.js.map +0 -1
  204. package/lib/components/popup.d.ts.map +0 -1
  205. package/lib/components/popup.js.map +0 -1
  206. package/lib/components/radio.d.ts.map +0 -1
  207. package/lib/components/radio.js.map +0 -1
  208. package/lib/components/resize-observer.d.ts.map +0 -1
  209. package/lib/components/resize-observer.js.map +0 -1
  210. package/lib/components/scroll-container.d.ts.map +0 -1
  211. package/lib/components/scroll-container.js.map +0 -1
  212. package/lib/components/select.d.ts.map +0 -1
  213. package/lib/components/select.js.map +0 -1
  214. package/lib/components/selected-tags.d.ts.map +0 -1
  215. package/lib/components/selected-tags.js.map +0 -1
  216. package/lib/components/step.d.ts.map +0 -1
  217. package/lib/components/step.js.map +0 -1
  218. package/lib/components/switch.d.ts.map +0 -1
  219. package/lib/components/switch.js.map +0 -1
  220. package/lib/components/tab.d.ts.map +0 -1
  221. package/lib/components/tab.js.map +0 -1
  222. package/lib/components/tabs.d.ts.map +0 -1
  223. package/lib/components/tabs.js.map +0 -1
  224. package/lib/components/tag.d.ts.map +0 -1
  225. package/lib/components/tag.js.map +0 -1
  226. package/lib/components/textarea.d.ts.map +0 -1
  227. package/lib/components/textarea.js.map +0 -1
  228. package/lib/components/tooltip.d.ts.map +0 -1
  229. package/lib/components/tooltip.js.map +0 -1
  230. package/lib/components/wizard.d.ts.map +0 -1
  231. package/lib/components/wizard.js.map +0 -1
  232. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  233. package/lib/icons/fontawesome/brands.js.map +0 -1
  234. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  235. package/lib/icons/fontawesome/regular.js.map +0 -1
  236. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  237. package/lib/icons/fontawesome/solid.js.map +0 -1
  238. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  239. package/lib/icons/heroicons/micro.js.map +0 -1
  240. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  241. package/lib/icons/heroicons/mini.js.map +0 -1
  242. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  243. package/lib/icons/heroicons/outline.js.map +0 -1
  244. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  245. package/lib/icons/heroicons/solid.js.map +0 -1
  246. package/lib/icons/lucide.d.ts.map +0 -1
  247. package/lib/icons/lucide.js.map +0 -1
  248. package/lib/icons/material/filled.d.ts.map +0 -1
  249. package/lib/icons/material/filled.js.map +0 -1
  250. package/lib/icons/material/outlined.d.ts.map +0 -1
  251. package/lib/icons/material/outlined.js.map +0 -1
  252. package/lib/icons/material/round.d.ts.map +0 -1
  253. package/lib/icons/material/round.js.map +0 -1
  254. package/lib/icons/material/sharp.d.ts.map +0 -1
  255. package/lib/icons/material/sharp.js.map +0 -1
  256. package/lib/icons/material/two-tone.d.ts.map +0 -1
  257. package/lib/icons/material/two-tone.js.map +0 -1
  258. package/lib/index.d.ts.map +0 -1
  259. package/lib/index.js.map +0 -1
  260. package/lib/styles/button.d.ts.map +0 -1
  261. package/lib/styles/button.js.map +0 -1
  262. package/lib/styles/calendar-month.d.ts.map +0 -1
  263. package/lib/styles/calendar-month.js.map +0 -1
  264. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  265. package/lib/styles/calendar-navigation.js.map +0 -1
  266. package/lib/styles/calendar.d.ts.map +0 -1
  267. package/lib/styles/calendar.js.map +0 -1
  268. package/lib/styles/checkbox.d.ts.map +0 -1
  269. package/lib/styles/checkbox.js.map +0 -1
  270. package/lib/styles/copy.d.ts.map +0 -1
  271. package/lib/styles/copy.js.map +0 -1
  272. package/lib/styles/custom-scrollbar.d.ts.map +0 -1
  273. package/lib/styles/custom-scrollbar.js.map +0 -1
  274. package/lib/styles/date-picker.d.ts.map +0 -1
  275. package/lib/styles/date-picker.js.map +0 -1
  276. package/lib/styles/file-upload.d.ts.map +0 -1
  277. package/lib/styles/file-upload.js.map +0 -1
  278. package/lib/styles/icon.d.ts.map +0 -1
  279. package/lib/styles/icon.js.map +0 -1
  280. package/lib/styles/input.d.ts.map +0 -1
  281. package/lib/styles/input.js.map +0 -1
  282. package/lib/styles/modal.d.ts.map +0 -1
  283. package/lib/styles/modal.js.map +0 -1
  284. package/lib/styles/multiselect.d.ts +0 -8
  285. package/lib/styles/multiselect.d.ts.map +0 -1
  286. package/lib/styles/multiselect.js +0 -849
  287. package/lib/styles/multiselect.js.map +0 -1
  288. package/lib/styles/option.d.ts.map +0 -1
  289. package/lib/styles/option.js.map +0 -1
  290. package/lib/styles/popup.d.ts.map +0 -1
  291. package/lib/styles/popup.js.map +0 -1
  292. package/lib/styles/radio.d.ts.map +0 -1
  293. package/lib/styles/radio.js.map +0 -1
  294. package/lib/styles/resize-observer.d.ts.map +0 -1
  295. package/lib/styles/resize-observer.js.map +0 -1
  296. package/lib/styles/scroll-container.d.ts.map +0 -1
  297. package/lib/styles/scroll-container.js.map +0 -1
  298. package/lib/styles/select-base.d.ts.map +0 -1
  299. package/lib/styles/select-base.js.map +0 -1
  300. package/lib/styles/select.d.ts.map +0 -1
  301. package/lib/styles/select.js.map +0 -1
  302. package/lib/styles/step.d.ts.map +0 -1
  303. package/lib/styles/step.js.map +0 -1
  304. package/lib/styles/switch.d.ts.map +0 -1
  305. package/lib/styles/switch.js.map +0 -1
  306. package/lib/styles/tab.d.ts.map +0 -1
  307. package/lib/styles/tab.js.map +0 -1
  308. package/lib/styles/tabs.d.ts.map +0 -1
  309. package/lib/styles/tabs.js.map +0 -1
  310. package/lib/styles/tag.d.ts.map +0 -1
  311. package/lib/styles/tag.js.map +0 -1
  312. package/lib/styles/textarea.d.ts.map +0 -1
  313. package/lib/styles/textarea.js.map +0 -1
  314. package/lib/styles/tooltip.d.ts.map +0 -1
  315. package/lib/styles/tooltip.js.map +0 -1
  316. package/lib/styles/wizard.d.ts.map +0 -1
  317. package/lib/styles/wizard.js.map +0 -1
  318. package/lib/types/common.d.ts.map +0 -1
  319. package/lib/types/common.js.map +0 -1
  320. package/lib/utils/calendar-utils.d.ts.map +0 -1
  321. package/lib/utils/calendar-utils.js.map +0 -1
  322. package/lib/utils/custom-scrollbar.d.ts.map +0 -1
  323. package/lib/utils/custom-scrollbar.js.map +0 -1
  324. package/lib/utils/flavor-sheet.d.ts.map +0 -1
  325. package/lib/utils/flavor-sheet.js.map +0 -1
  326. package/lib/utils/icon-registry.d.ts.map +0 -1
  327. package/lib/utils/icon-registry.js.map +0 -1
  328. package/lib/utils/icons.d.ts.map +0 -1
  329. package/lib/utils/icons.js.map +0 -1
  330. package/lib/utils/loader-registry.d.ts.map +0 -1
  331. package/lib/utils/loader-registry.js.map +0 -1
  332. package/lib/utils/locale.d.ts.map +0 -1
  333. package/lib/utils/locale.js.map +0 -1
  334. package/lib/utils/mobile.d.ts.map +0 -1
  335. package/lib/utils/mobile.js.map +0 -1
  336. package/lib/utils/number-format.d.ts.map +0 -1
  337. package/lib/utils/number-format.js.map +0 -1
  338. package/lib/utils/parse-boolean.d.ts +0 -39
  339. package/lib/utils/parse-boolean.d.ts.map +0 -1
  340. package/lib/utils/parse-boolean.js +0 -58
  341. package/lib/utils/parse-boolean.js.map +0 -1
  342. package/lib/utils/positioning.d.ts.map +0 -1
  343. package/lib/utils/positioning.js.map +0 -1
  344. package/lib/utils/property-capture.d.ts.map +0 -1
  345. package/lib/utils/property-capture.js.map +0 -1
  346. package/lib/utils/property-manager.d.ts.map +0 -1
  347. package/lib/utils/property-manager.js.map +0 -1
  348. package/lib/utils/resize-observer.d.ts.map +0 -1
  349. package/lib/utils/resize-observer.js.map +0 -1
  350. package/lib/utils/scroll-lock.d.ts.map +0 -1
  351. package/lib/utils/scroll-lock.js.map +0 -1
  352. package/lib/utils/styles.d.ts.map +0 -1
  353. package/lib/utils/styles.js.map +0 -1
  354. package/lib/version.d.ts.map +0 -1
  355. package/lib/version.js.map +0 -1
@@ -1,79 +1,7 @@
1
- /**
2
- * Tabs Component
3
- *
4
- * A carousel-based tabs component with smooth animations and fixed container dimensions.
5
- * Features horizontal sliding transitions, smart positioning marker, and independent panel scrolling.
6
- *
7
- * Features:
8
- * - Carousel animation with smooth sliding transitions
9
- * - Fixed dimensions prevent layout shift between tabs
10
- * - Animated marker that follows active tab button
11
- * - Rich label support via slots (icons, badges, custom content)
12
- * - Custom marker slot for complete active tab styling control
13
- * - Independent panel scrolling with scroll position reset
14
- * - ResizeObserver for responsive percentage widths
15
- * - Smart rendering - only updates DOM when necessary
16
- * - Accessibility with ARIA roles and attributes
17
- * - Top/bottom placement for tab buttons
18
- *
19
- * @example
20
- * <!-- Basic tabs with text labels -->
21
- * <ty-tabs width="800px" height="600px" active="general">
22
- * <ty-tab id="general" label="General Settings">
23
- * <div class="p-6">General content...</div>
24
- * </ty-tab>
25
- * <ty-tab id="advanced" label="Advanced Settings">
26
- * <div class="p-6">Advanced content...</div>
27
- * </ty-tab>
28
- * </ty-tabs>
29
- *
30
- * @example
31
- * <!-- Rich labels with icons and badges -->
32
- * <ty-tabs width="800px" height="600px">
33
- * <!-- Rich label as direct child -->
34
- * <span slot="label-profile" class="flex items-center gap-2">
35
- * <ty-icon name="user" size="sm"></ty-icon>
36
- * Profile
37
- * </span>
38
- *
39
- * <span slot="label-notifications" class="flex items-center gap-2">
40
- * <ty-icon name="bell" size="sm"></ty-icon>
41
- * Notifications
42
- * <span class="ty-bg-danger ty-text-danger++ px-2 py-0.5 rounded-full text-xs">5</span>
43
- * </span>
44
- *
45
- * <!-- Tab panels -->
46
- * <ty-tab id="profile">...</ty-tab>
47
- * <ty-tab id="notifications">...</ty-tab>
48
- * </ty-tabs>
49
- *
50
- * @example
51
- * <!-- Custom marker styling -->
52
- * <ty-tabs width="100%" height="400px">
53
- * <!-- Gradient marker slot -->
54
- * <div slot="marker" style="
55
- * background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
56
- * border-radius: 0.5rem;
57
- * box-shadow: 0 2px 8px rgba(0,0,0,0.15);
58
- * "></div>
59
- *
60
- * <ty-tab id="tab1" label="Dashboard">...</ty-tab>
61
- * <ty-tab id="tab2" label="Analytics">...</ty-tab>
62
- * </ty-tabs>
63
- */
64
1
  import { ensureStyles } from '../utils/styles.js';
65
2
  import { tabsStyles } from '../styles/tabs.js';
66
- // ============================================================================
67
- // WeakMaps for State Management
68
- // ============================================================================
69
3
  const eventHandlers = new WeakMap();
70
4
  const resizeObservers = new WeakMap();
71
- // ============================================================================
72
- // Helper Functions - Attribute Parsing
73
- // ============================================================================
74
- /**
75
- * Extract tabs configuration from element attributes
76
- */
77
5
  function getTabsAttributes(el) {
78
6
  return {
79
7
  width: el.getAttribute('width') || '100%',
@@ -82,29 +10,15 @@ function getTabsAttributes(el) {
82
10
  placement: (el.getAttribute('placement') || 'top'),
83
11
  };
84
12
  }
85
- /**
86
- * Get all ty-tab child elements
87
- */
88
13
  function getChildTabs(el) {
89
14
  return Array.from(el.querySelectorAll('ty-tab'));
90
15
  }
91
- /**
92
- * Get ID from a ty-tab element
93
- */
94
16
  function getTabId(tab) {
95
17
  return tab.getAttribute('id');
96
18
  }
97
- /**
98
- * Check if ty-tabs has a direct child label slot for this tab-id.
99
- * Looks in ty-tabs' light DOM for slot='label-{tab-id}' elements.
100
- */
101
19
  function hasSlotLabel(tabsEl, tabId) {
102
20
  return tabsEl.querySelector(`[slot='label-${tabId}']`) !== null;
103
21
  }
104
- /**
105
- * Determine label type: 'slot' (rich content) or 'text' (simple attribute).
106
- * Checks ty-tabs element for slot='label-{tab-id}' direct children.
107
- */
108
22
  function getTabLabelType(tabsEl, tab) {
109
23
  const tabId = getTabId(tab);
110
24
  if (tabId && hasSlotLabel(tabsEl, tabId)) {
@@ -112,51 +26,29 @@ function getTabLabelType(tabsEl, tab) {
112
26
  }
113
27
  return 'text';
114
28
  }
115
- /**
116
- * Check if tab is disabled
117
- */
118
29
  function isTabDisabled(tab) {
119
30
  return tab.hasAttribute('disabled');
120
31
  }
121
- /**
122
- * Check if user provided custom marker content via slot
123
- */
124
32
  function hasCustomMarker(el) {
125
33
  return el.querySelector('[slot="marker"]') !== null;
126
34
  }
127
- // ============================================================================
128
- // Helper Functions - Active Tab Management
129
- // ============================================================================
130
- /**
131
- * Find index of tab with given ID
132
- */
133
35
  function findTabIndex(tabs, tabId) {
134
36
  const index = tabs.findIndex(tab => getTabId(tab) === tabId);
135
37
  return index >= 0 ? index : undefined;
136
38
  }
137
- /**
138
- * Get the active tab ID, defaulting to first tab if not specified
139
- */
140
39
  function getActiveTabId(el, tabs) {
141
40
  const activeAttr = el.getAttribute('active');
142
41
  if (activeAttr && findTabIndex(tabs, activeAttr) !== undefined) {
143
42
  return activeAttr;
144
43
  }
145
- // Default to first tab
146
44
  if (tabs.length > 0) {
147
45
  return getTabId(tabs[0]);
148
46
  }
149
47
  return null;
150
48
  }
151
- /**
152
- * Set the active tab by ID
153
- */
154
49
  function setActiveTab(el, tabId) {
155
50
  el.setAttribute('active', tabId);
156
51
  }
157
- /**
158
- * Dispatch ty-tab-change event
159
- */
160
52
  function dispatchTabChangeEvent(el, activeId, activeIndex, previousId, previousIndex) {
161
53
  const event = new CustomEvent('ty-tab-change', {
162
54
  detail: {
@@ -170,21 +62,11 @@ function dispatchTabChangeEvent(el, activeId, activeIndex, previousId, previousI
170
62
  });
171
63
  el.dispatchEvent(event);
172
64
  }
173
- // ============================================================================
174
- // Event Handlers - Tab Button Click
175
- // ============================================================================
176
- /**
177
- * Handle tab button click
178
- */
179
65
  function handleTabClick(el, tabId, event) {
180
66
  event.preventDefault();
181
67
  event.stopPropagation();
182
- // Set the active attribute (which will trigger state update)
183
68
  setActiveTab(el, tabId);
184
69
  }
185
- /**
186
- * Cleanup existing event listeners
187
- */
188
70
  function cleanupEventListeners(el) {
189
71
  const handlers = eventHandlers.get(el);
190
72
  if (!handlers)
@@ -192,7 +74,6 @@ function cleanupEventListeners(el) {
192
74
  const shadowRoot = el.shadowRoot;
193
75
  if (!shadowRoot)
194
76
  return;
195
- // Remove all tab click handlers
196
77
  handlers.tabClickHandlers.forEach((handler, tabId) => {
197
78
  const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
198
79
  if (button) {
@@ -208,18 +89,12 @@ function cleanupEventListeners(el) {
208
89
  handlers.tabClickHandlers.clear();
209
90
  handlers.tabKeyClickHandlers.clear();
210
91
  }
211
- /**
212
- * Setup event listeners for tab button clicks
213
- */
214
92
  function setupEventListeners(el, shadowRoot, tabs) {
215
- // Clean up any existing listeners first
216
93
  cleanupEventListeners(el);
217
- // Initialize handlers storage
218
94
  const handlers = {
219
95
  tabClickHandlers: new Map(),
220
96
  tabKeyClickHandlers: new Map(),
221
97
  };
222
- // Add click listener for each tab button
223
98
  tabs.forEach((tab) => {
224
99
  const tabId = getTabId(tab);
225
100
  if (!tabId)
@@ -227,13 +102,6 @@ function setupEventListeners(el, shadowRoot, tabs) {
227
102
  const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
228
103
  if (button) {
229
104
  const handler = (e) => handleTabClick(el, tabId, e);
230
- // pointerdown: snappy mouse/touch activation (no native click delay).
231
- // click, gated to event.detail === 0: native <button> keyboard
232
- // activation (Enter/Space) fires 'click' with detail 0 — real mouse
233
- // clicks report detail >= 1 — so this covers keyboard users without
234
- // double-firing handleTabClick on an actual pointer click (which
235
- // already fired via pointerdown above). Without this, keyboard-only
236
- // users could Tab to a tab button but had no way to activate it.
237
105
  const keyClickHandler = (e) => {
238
106
  if (e.detail === 0)
239
107
  handleTabClick(el, tabId, e);
@@ -244,15 +112,8 @@ function setupEventListeners(el, shadowRoot, tabs) {
244
112
  handlers.tabKeyClickHandlers.set(tabId, keyClickHandler);
245
113
  }
246
114
  });
247
- // Store handlers for cleanup
248
115
  eventHandlers.set(el, handlers);
249
116
  }
250
- // ============================================================================
251
- // Transform & Positioning Updates
252
- // ============================================================================
253
- /**
254
- * Update the transform on panels-wrapper based on active index and measured width
255
- */
256
117
  function updateTransform(el, activeIndex) {
257
118
  const shadowRoot = el.shadowRoot;
258
119
  if (!shadowRoot)
@@ -260,15 +121,10 @@ function updateTransform(el, activeIndex) {
260
121
  const panelsWrapper = shadowRoot.querySelector('.panels-wrapper');
261
122
  if (!panelsWrapper)
262
123
  return;
263
- // Measure the actual width of the container
264
124
  const containerWidth = el.offsetWidth;
265
125
  const offsetPx = activeIndex * containerWidth;
266
- // Apply transform directly in pixels
267
126
  panelsWrapper.style.transform = `translateX(-${offsetPx}px)`;
268
127
  }
269
- /**
270
- * Update ARIA attributes on tab buttons without re-rendering
271
- */
272
128
  function updateAriaAttributes(el, shadowRoot, activeId) {
273
129
  const tabs = getChildTabs(el);
274
130
  tabs.forEach((tab, idx) => {
@@ -280,9 +136,7 @@ function updateAriaAttributes(el, shadowRoot, activeId) {
280
136
  if (button) {
281
137
  button.setAttribute('aria-selected', String(isActive));
282
138
  button.setAttribute('tabindex', isActive ? '0' : '-1');
283
- // Add data-active attribute for styling slotted content
284
139
  button.setAttribute('data-active', String(isActive));
285
- // Also set data-active on the slotted label element in light DOM
286
140
  const slottedLabel = el.querySelector(`[slot='label-${tabId}']`);
287
141
  if (slottedLabel) {
288
142
  slottedLabel.setAttribute('data-active', String(isActive));
@@ -290,9 +144,6 @@ function updateAriaAttributes(el, shadowRoot, activeId) {
290
144
  }
291
145
  });
292
146
  }
293
- /**
294
- * Update pointer-events, opacity, and data-active on tab panels without re-rendering
295
- */
296
147
  function updatePanelInteraction(el, activeId) {
297
148
  const tabs = getChildTabs(el);
298
149
  tabs.forEach((tab) => {
@@ -300,7 +151,6 @@ function updatePanelInteraction(el, activeId) {
300
151
  if (!tabId)
301
152
  return;
302
153
  const isActive = tabId === activeId;
303
- // Set data-active attribute for framework conditional rendering
304
154
  tab.setAttribute('data-active', String(isActive));
305
155
  if (isActive) {
306
156
  tab.style.pointerEvents = 'auto';
@@ -312,17 +162,12 @@ function updatePanelInteraction(el, activeId) {
312
162
  }
313
163
  });
314
164
  }
315
- /**
316
- * Calculate marker position and dimensions based on active tab button.
317
- * Returns position object with left, top, width, height in pixels, or null if button not found.
318
- */
319
165
  function calculateMarkerPosition(el, shadowRoot, activeId) {
320
166
  const button = shadowRoot.querySelector(`[data-tab-id='${activeId}']`);
321
167
  const buttonsContainer = shadowRoot.querySelector('.tab-buttons');
322
168
  if (!button || !buttonsContainer)
323
169
  return null;
324
170
  const buttonRect = button.getBoundingClientRect();
325
- // Use offset properties for position relative to container (accounts for padding)
326
171
  const left = button.offsetLeft;
327
172
  const top = button.offsetTop;
328
173
  return {
@@ -332,9 +177,6 @@ function calculateMarkerPosition(el, shadowRoot, activeId) {
332
177
  height: buttonRect.height,
333
178
  };
334
179
  }
335
- /**
336
- * Update marker wrapper position and dimensions to match active tab button
337
- */
338
180
  function updateMarker(el, activeId) {
339
181
  const shadowRoot = el.shadowRoot;
340
182
  if (!shadowRoot)
@@ -345,9 +187,6 @@ function updateMarker(el, activeId) {
345
187
  const position = calculateMarkerPosition(el, shadowRoot, activeId);
346
188
  if (!position)
347
189
  return;
348
- // Snap without animating when the marker was never positioned, or was
349
- // positioned while the tabs were hidden (zero-size rects) — otherwise it
350
- // visibly glides in from 0,0 on first display.
351
190
  const snap = !marker.style.left || marker.offsetWidth === 0;
352
191
  if (snap)
353
192
  marker.style.transition = 'none';
@@ -356,20 +195,10 @@ function updateMarker(el, activeId) {
356
195
  marker.style.width = `${position.width}px`;
357
196
  marker.style.height = `${position.height}px`;
358
197
  if (snap) {
359
- marker.offsetWidth; // flush styles so the snap isn't animated
198
+ marker.offsetWidth;
360
199
  marker.style.transition = '';
361
200
  }
362
201
  }
363
- /**
364
- * Update only the active tab state without re-rendering DOM.
365
- * Called from `attributeChangedCallback` when the `active` attribute changes.
366
- *
367
- * `previousId` MUST be the value the attribute held *before* it was changed —
368
- * never call `getActiveTabId(el, ...)` here, because the attribute has already
369
- * been updated to `tabId` by the time this runs. Reading it back would always
370
- * yield the new value, making the change-detection guard below trip on every
371
- * click and silently suppress `ty-tab-change` dispatch.
372
- */
373
202
  function updateActiveTabState(el, tabId, previousId) {
374
203
  const tabs = getChildTabs(el);
375
204
  const shadowRoot = el.shadowRoot;
@@ -377,56 +206,36 @@ function updateActiveTabState(el, tabId, previousId) {
377
206
  return;
378
207
  const currentIndex = previousId ? findTabIndex(tabs, previousId) : undefined;
379
208
  const newIndex = findTabIndex(tabs, tabId);
380
- // Only update if different tab and valid
381
209
  if (previousId === tabId || newIndex === undefined)
382
210
  return;
383
- // Update CSS variable for transform
384
211
  el.style.setProperty('--active-index', String(newIndex));
385
- // Update transform directly
386
212
  updateTransform(el, newIndex);
387
- // Update ARIA attributes on buttons
388
213
  updateAriaAttributes(el, shadowRoot, tabId);
389
- // Update pointer-events on panels
390
214
  updatePanelInteraction(el, tabId);
391
- // Update marker position to match new active tab
392
215
  updateMarker(el, tabId);
393
- // Reset scroll position of new active panel
394
216
  const newPanel = tabs[newIndex];
395
217
  if (newPanel?.resetScroll) {
396
218
  newPanel.resetScroll();
397
219
  }
398
- // Dispatch change event
399
220
  dispatchTabChangeEvent(el, tabId, newIndex, previousId, currentIndex ?? null);
400
221
  }
401
- // ============================================================================
402
- // ResizeObserver for Responsive Width
403
- // ============================================================================
404
- /**
405
- * Setup ResizeObserver for percentage widths and marker updates
406
- */
407
222
  function setupResizeObserver(el) {
408
- // Clean up old observer
409
223
  const oldObserver = resizeObservers.get(el);
410
224
  if (oldObserver) {
411
225
  oldObserver.disconnect();
412
226
  }
413
- // Observe unconditionally (not just for '%' widths) — overflow can happen
414
- // at any fixed width too, and needs to react to container resizes.
415
227
  const observer = new ResizeObserver((entries) => {
416
228
  const entry = entries[0];
417
229
  const { width } = getTabsAttributes(el);
418
230
  const tabs = getChildTabs(el);
419
231
  const activeId = getActiveTabId(el, tabs);
420
232
  const activeIndex = activeId ? findTabIndex(tabs, activeId) : 0;
421
- // Update CSS variable with measured width (percentage widths only)
422
233
  if (width.includes('%')) {
423
- el.style.setProperty('--tabs-width', `${entry.contentRect.width}px`);
234
+ el.style.setProperty('--tabs-page-width', `${entry.contentRect.width}px`);
424
235
  }
425
- // Update transform with new width
426
236
  if (activeIndex !== undefined) {
427
237
  updateTransform(el, activeIndex);
428
238
  }
429
- // Update marker position (tab button positions may have changed)
430
239
  if (activeId) {
431
240
  updateMarker(el, activeId);
432
241
  }
@@ -435,9 +244,6 @@ function setupResizeObserver(el) {
435
244
  observer.observe(el);
436
245
  resizeObservers.set(el, observer);
437
246
  }
438
- /**
439
- * Cleanup ResizeObserver
440
- */
441
247
  function cleanupResizeObserver(el) {
442
248
  const observer = resizeObservers.get(el);
443
249
  if (observer) {
@@ -445,14 +251,6 @@ function cleanupResizeObserver(el) {
445
251
  resizeObservers.delete(el);
446
252
  }
447
253
  }
448
- // ============================================================================
449
- // Rendering Functions
450
- // ============================================================================
451
- /**
452
- * Generate HTML for tab buttons using slots for rich labels.
453
- * Slots are looked up as direct children of tabs-el with slot='label-{tab-id}'.
454
- * Also renders marker wrapper with optional default underline (only if no custom marker).
455
- */
456
254
  function renderTabButtons(tabsEl, tabs, activeId) {
457
255
  const buttons = tabs.map((tab) => {
458
256
  const tabId = getTabId(tab);
@@ -485,10 +283,6 @@ function renderTabButtons(tabsEl, tabs, activeId) {
485
283
  </div>
486
284
  `;
487
285
  }
488
- /**
489
- * Generate HTML for ONLY the button elements (no marker wrapper).
490
- * Used during smart updates to preserve the marker wrapper.
491
- */
492
286
  function renderButtonsOnly(tabsEl, tabs, activeId) {
493
287
  return tabs.map((tab) => {
494
288
  const tabId = getTabId(tab);
@@ -512,11 +306,7 @@ function renderButtonsOnly(tabsEl, tabs, activeId) {
512
306
  </button>`;
513
307
  }).join('');
514
308
  }
515
- // ============================================================================
516
- // Overflow Handling (collapse tabs that don't fit into a "more" menu)
517
- // ============================================================================
518
309
  const OVERFLOW_TRIGGER_WIDTH = 40;
519
- /** Build a display label for a hidden tab's overflow-menu entry. */
520
310
  function getTabMenuLabel(tabsEl, tab) {
521
311
  const tabId = getTabId(tab);
522
312
  if (tabId && getTabLabelType(tabsEl, tab) === 'slot') {
@@ -525,15 +315,9 @@ function getTabMenuLabel(tabsEl, tab) {
525
315
  }
526
316
  return tab.getAttribute('label') || 'Tab';
527
317
  }
528
- /** Remove the "more" trigger + its popup, if present. */
529
318
  function removeOverflowMenu(shadowRoot) {
530
319
  shadowRoot.querySelector('.tab-overflow-trigger')?.remove();
531
320
  }
532
- /**
533
- * Build the "more" trigger button + its ty-popup menu for the given
534
- * (already-hidden) tab buttons, and append it to the buttons container.
535
- * Reuses ty-popup instead of a second floating-menu implementation.
536
- */
537
321
  function renderOverflowMenu(el, container, hiddenButtons, allTabs) {
538
322
  if (hiddenButtons.length === 0)
539
323
  return;
@@ -573,12 +357,6 @@ function renderOverflowMenu(el, container, hiddenButtons, allTabs) {
573
357
  trigger.appendChild(popup);
574
358
  container.appendChild(trigger);
575
359
  }
576
- /**
577
- * Measure the tab buttons against the available width and collapse whatever
578
- * doesn't fit behind a "more" trigger + popup menu. The active tab is always
579
- * kept visible — if it would be the one to overflow, the last visible button
580
- * is hidden in its place instead.
581
- */
582
360
  function updateOverflow(el) {
583
361
  const shadowRoot = el.shadowRoot;
584
362
  if (!shadowRoot)
@@ -592,6 +370,8 @@ function updateOverflow(el) {
592
370
  if (buttons.length === 0)
593
371
  return;
594
372
  const available = container.clientWidth;
373
+ if (available <= 0)
374
+ return;
595
375
  let total = 0;
596
376
  let cutoff = buttons.length;
597
377
  for (let i = 0; i < buttons.length; i++) {
@@ -602,8 +382,7 @@ function updateOverflow(el) {
602
382
  }
603
383
  }
604
384
  if (cutoff >= buttons.length)
605
- return; // everything fits
606
- // Recompute the cutoff leaving room for the "more" trigger itself.
385
+ return;
607
386
  total = 0;
608
387
  cutoff = buttons.length;
609
388
  for (let i = 0; i < buttons.length; i++) {
@@ -626,10 +405,6 @@ function updateOverflow(el) {
626
405
  buttons.forEach((b) => b.classList.toggle('overflow-hidden', hidden.has(b)));
627
406
  renderOverflowMenu(el, container, buttons.filter((b) => hidden.has(b)), tabs);
628
407
  }
629
- /**
630
- * Render the tabs container with buttons and panel viewport.
631
- * Smart rendering: checks if structure exists and only updates when needed.
632
- */
633
408
  function render(el) {
634
409
  const shadowRoot = el.shadowRoot;
635
410
  if (!shadowRoot)
@@ -638,16 +413,6 @@ function render(el) {
638
413
  const tabs = getChildTabs(el);
639
414
  const activeId = getActiveTabId(el, tabs);
640
415
  const activeIndex = activeId ? (findTabIndex(tabs, activeId) ?? 0) : 0;
641
- // Each <ty-tab> IS its tabpanel (no separate wrapper — there's just one
642
- // catch-all <slot> in the carousel viewport below). role/aria-labelledby/
643
- // tabindex go directly on the light-DOM host; its `id` attribute is ALSO
644
- // its tabId (getTabId reads it directly), so it's never touched here.
645
- // No aria-controls on the tab BUTTON pointing at this: it lives in the
646
- // shadow root while the panel is in light DOM, and ARIA id-references
647
- // don't resolve across a shadow-tree boundary — axe's aria-valid-attr-value
648
- // correctly flags that as invalid even though getElementById can find the
649
- // target. role=tab + aria-selected + aria-labelledby (this direction) is
650
- // what's actually load-bearing; caught by axe once ty-tabs got coverage.
651
416
  tabs.forEach((tab) => {
652
417
  const tabId = getTabId(tab);
653
418
  if (!tabId)
@@ -656,63 +421,48 @@ function render(el) {
656
421
  tab.setAttribute('aria-labelledby', `tab-${tabId}`);
657
422
  tab.setAttribute('tabindex', '0');
658
423
  });
659
- // Check if structure already exists
660
424
  const existingContainer = shadowRoot.querySelector('.tabs-container');
661
425
  const existingButtons = shadowRoot.querySelector('.tab-buttons');
662
426
  const existingViewport = shadowRoot.querySelector('.panels-viewport');
663
- // Ensure styles are loaded
664
427
  ensureStyles(shadowRoot, { css: tabsStyles, id: 'ty-tabs' });
665
- // Dev warning for too many tabs
666
428
  if (tabs.length > 7) {
667
429
  console.warn(`[ty-tabs] More than 7 tabs detected (${tabs.length} tabs). ` +
668
430
  'This may cause overflow and poor UX. ' +
669
431
  'Consider using sidebar navigation, accordion menu, or other patterns.');
670
432
  }
671
- // Set CSS variables for dimensions
672
433
  el.style.setProperty('--tabs-width', width.includes('%') ? '100%' : width);
673
434
  el.style.setProperty('--tabs-height', height);
674
435
  el.style.setProperty('--active-index', String(activeIndex));
675
436
  if (existingContainer && existingButtons && existingViewport) {
676
- // === SMART UPDATE: Structure exists, only update what changed ===
677
- // Update placement if changed
678
437
  existingContainer.setAttribute('data-placement', placement);
679
- // SMART UPDATE: Preserve marker wrapper, only update buttons
680
438
  existingButtons.querySelector('.tab-overflow-trigger')?.remove();
681
439
  const allButtons = existingButtons.querySelectorAll('.tab-button');
682
440
  allButtons.forEach(button => button.remove());
683
- // Generate and append new buttons after the marker wrapper
684
441
  const buttonsHtml = renderButtonsOnly(el, tabs, activeId);
685
442
  const tempDiv = document.createElement('div');
686
443
  tempDiv.innerHTML = buttonsHtml;
687
444
  Array.from(tempDiv.children).forEach(button => {
688
445
  existingButtons.appendChild(button);
689
446
  });
690
- // Re-setup event listeners (buttons were recreated)
691
447
  setupEventListeners(el, shadowRoot, tabs);
692
- // Update ARIA and data-active attributes on initial render
693
448
  updateAriaAttributes(el, shadowRoot, activeId || '');
694
- // Measure button height and update marker after update
695
449
  requestAnimationFrame(() => {
696
450
  const buttons = shadowRoot.querySelector('.tab-buttons');
697
451
  if (buttons) {
698
452
  const buttonsHeight = buttons.offsetHeight;
699
453
  el.style.setProperty('--buttons-height', `${buttonsHeight}px`);
700
454
  }
701
- // Update transform with current active index
702
455
  updateTransform(el, activeIndex);
703
- // Update marker position to match active tab
704
456
  if (activeId) {
705
457
  updateMarker(el, activeId);
706
458
  }
707
459
  updateOverflow(el);
708
460
  });
709
- // Update panel interaction states
710
461
  if (activeId) {
711
462
  updatePanelInteraction(el, activeId);
712
463
  }
713
464
  }
714
465
  else {
715
- // === FULL RENDER: First time or structure missing ===
716
466
  shadowRoot.innerHTML = `
717
467
  <div class="tabs-container" data-placement="${placement}">
718
468
  ${renderTabButtons(el, tabs, activeId)}
@@ -723,32 +473,24 @@ function render(el) {
723
473
  </div>
724
474
  </div>
725
475
  `;
726
- // Measure button height, update transform and marker after render
727
476
  requestAnimationFrame(() => {
728
477
  const buttons = shadowRoot.querySelector('.tab-buttons');
729
478
  if (buttons) {
730
479
  const buttonsHeight = buttons.offsetHeight;
731
480
  el.style.setProperty('--buttons-height', `${buttonsHeight}px`);
732
481
  }
733
- // Update transform with measured width
734
482
  updateTransform(el, activeIndex);
735
- // Update marker position to match active tab
736
483
  if (activeId) {
737
484
  updateMarker(el, activeId);
738
485
  }
739
486
  updateOverflow(el);
740
487
  });
741
- // Setup event listeners
742
488
  setupEventListeners(el, shadowRoot, tabs);
743
- // Update ARIA and data-active attributes on initial render
744
489
  updateAriaAttributes(el, shadowRoot, activeId || '');
745
- // Setup ResizeObserver for responsive width
746
490
  setupResizeObserver(el);
747
- // Update tab panel states
748
491
  if (activeId) {
749
492
  updatePanelInteraction(el, activeId);
750
493
  }
751
- // Listen for marker slot changes to hide default marker
752
494
  const markerSlot = shadowRoot.querySelector('slot[name="marker"]');
753
495
  if (markerSlot) {
754
496
  const updateDefaultMarkerVisibility = () => {
@@ -759,60 +501,55 @@ function render(el) {
759
501
  }
760
502
  };
761
503
  markerSlot.addEventListener('slotchange', updateDefaultMarkerVisibility);
762
- // Check immediately in case content is already slotted
763
504
  updateDefaultMarkerVisibility();
764
505
  }
765
506
  }
766
507
  }
767
- /**
768
- * Cleanup when tabs component is disconnected
769
- */
770
508
  function cleanup(el) {
771
509
  cleanupEventListeners(el);
772
510
  cleanupResizeObserver(el);
773
511
  }
774
- // ============================================================================
775
- // Component Definition
776
- // ============================================================================
777
- /**
778
- * TyTabs Web Component
779
- */
780
512
  export class TyTabs extends HTMLElement {
781
- /** Observed attributes */
782
513
  static get observedAttributes() {
783
514
  return ['width', 'height', 'active', 'placement'];
784
515
  }
785
516
  constructor() {
786
517
  super();
518
+ this._childObserver = null;
519
+ this._childRenderQueued = false;
787
520
  this.attachShadow({ mode: 'open' });
788
521
  }
789
522
  connectedCallback() {
790
523
  render(this);
524
+ this._childObserver = new MutationObserver(() => {
525
+ if (this._childRenderQueued)
526
+ return;
527
+ this._childRenderQueued = true;
528
+ queueMicrotask(() => {
529
+ this._childRenderQueued = false;
530
+ if (this.isConnected)
531
+ render(this);
532
+ });
533
+ });
534
+ this._childObserver.observe(this, { childList: true });
791
535
  }
792
536
  disconnectedCallback() {
537
+ this._childObserver?.disconnect();
538
+ this._childObserver = null;
793
539
  cleanup(this);
794
540
  }
795
541
  attributeChangedCallback(name, oldValue, newValue) {
796
- // Smart rendering: only full render when structural attributes change
797
542
  if (name === 'active') {
798
- // Active tab changed - update state, then do smart render
799
543
  if (newValue) {
800
- // Pass `oldValue` explicitly so the change-detection inside
801
- // `updateActiveTabState` compares against the previous value
802
- // rather than re-reading the (already updated) attribute.
803
544
  updateActiveTabState(this, newValue, oldValue);
804
545
  }
805
- // Always call render after active change to update button states
806
546
  render(this);
807
547
  }
808
548
  else {
809
- // Other attributes changed (width, height, placement) - full render
810
549
  render(this);
811
550
  }
812
551
  }
813
552
  }
814
- // Register the custom element
815
553
  if (!customElements.get('ty-tabs')) {
816
554
  customElements.define('ty-tabs', TyTabs);
817
555
  }
818
- //# sourceMappingURL=tabs.js.map