eidos-ui 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/README.md +10 -0
  2. package/dist/{Avatar.types-BY-C09Ke.d.cts → Avatar.types-DkRznn-7.d.cts} +6 -1
  3. package/dist/{Avatar.types-BY-C09Ke.d.ts → Avatar.types-DkRznn-7.d.ts} +6 -1
  4. package/dist/{Breadcrumb.types-DaykU-C8.d.cts → Breadcrumb.types-C3uADbKU.d.cts} +4 -1
  5. package/dist/{Breadcrumb.types-DaykU-C8.d.ts → Breadcrumb.types-C3uADbKU.d.ts} +4 -1
  6. package/dist/Button.types-BCIK8Qlk.d.ts +59 -0
  7. package/dist/Button.types-DkgWjFsk.d.cts +59 -0
  8. package/dist/{Chip.types-COQfx2Ru.d.ts → Chip.types-BM-UZOCv.d.ts} +10 -1
  9. package/dist/{Chip.types-B6y3Wifa.d.cts → Chip.types-_1bhyKz4.d.cts} +10 -1
  10. package/dist/{Header.types-Ce5Cj301.d.cts → Header.types-a6p0-ZHx.d.cts} +24 -8
  11. package/dist/{Header.types-0XovUp0T.d.ts → Header.types-zbhURbg7.d.ts} +24 -8
  12. package/dist/{Input.types-D7-sWq-q.d.cts → Input.types--K0t_3L2.d.cts} +1 -1
  13. package/dist/{Input.types-CSDMaZNe.d.ts → Input.types--UjucBDd.d.ts} +1 -1
  14. package/dist/{Menu.types-CTnUijv5.d.cts → Menu.types-D6VoK9T9.d.cts} +1 -1
  15. package/dist/{Menu.types-BN4g1-3A.d.ts → Menu.types-tUEhXn52.d.ts} +1 -1
  16. package/dist/{Navigation.types-By5HfPTi.d.ts → Navigation.types-CJRzo9Zo.d.ts} +1 -1
  17. package/dist/{Navigation.types-_MrYqqUI.d.cts → Navigation.types-D95oHZEJ.d.cts} +1 -1
  18. package/dist/{SegmentedControl.types-cjLwwBAm.d.ts → SegmentedControl.types-CHtjABQM.d.ts} +1 -1
  19. package/dist/{SegmentedControl.types-BofrEYyd.d.cts → SegmentedControl.types-D_gHYL9X.d.cts} +1 -1
  20. package/dist/{Select.types-ok-03rLo.d.cts → Select.types-8CGZBgT-.d.cts} +2 -2
  21. package/dist/{Select.types-C2tGy1vj.d.ts → Select.types-CAVh9Gvd.d.ts} +2 -2
  22. package/dist/{Table.types-D8pmgxKA.d.ts → Table.types-BFDE-hhF.d.ts} +2 -2
  23. package/dist/{Table.types-D-9tC0kJ.d.cts → Table.types-Tai7_MTz.d.cts} +2 -2
  24. package/dist/{Toolbar.types-CKnQC_TW.d.ts → Toolbar.types-BqBrPVww.d.ts} +8 -6
  25. package/dist/{Toolbar.types-DUz2mxct.d.cts → Toolbar.types-GCUgzuKW.d.cts} +8 -6
  26. package/dist/alert/index.cjs +8 -5
  27. package/dist/alert/index.cjs.map +1 -1
  28. package/dist/alert/index.d.cts +1 -1
  29. package/dist/alert/index.d.ts +1 -1
  30. package/dist/alert/index.js +7 -4
  31. package/dist/avatar/index.d.cts +2 -2
  32. package/dist/avatar/index.d.ts +2 -2
  33. package/dist/breadcrumb/index.cjs +4 -2
  34. package/dist/breadcrumb/index.cjs.map +1 -1
  35. package/dist/breadcrumb/index.d.cts +2 -2
  36. package/dist/breadcrumb/index.d.ts +2 -2
  37. package/dist/breadcrumb/index.js +3 -1
  38. package/dist/button/index.cjs +7 -4
  39. package/dist/button/index.cjs.map +1 -1
  40. package/dist/button/index.d.cts +5 -5
  41. package/dist/button/index.d.ts +5 -5
  42. package/dist/button/index.js +6 -3
  43. package/dist/button-group/index.d.cts +2 -2
  44. package/dist/button-group/index.d.ts +2 -2
  45. package/dist/chip/index.cjs +7 -4
  46. package/dist/chip/index.cjs.map +1 -1
  47. package/dist/chip/index.d.cts +3 -3
  48. package/dist/chip/index.d.ts +3 -3
  49. package/dist/chip/index.js +6 -3
  50. package/dist/{chunk-DRXPTACG.js → chunk-26PRBZA6.js} +6 -2
  51. package/dist/chunk-26PRBZA6.js.map +1 -0
  52. package/dist/{chunk-L7I5NEGI.js → chunk-2IXCEKLI.js} +2 -2
  53. package/dist/{chunk-GWHZFX2H.js → chunk-2TIMJD4K.js} +29 -6
  54. package/dist/chunk-2TIMJD4K.js.map +1 -0
  55. package/dist/{chunk-RHFD2Z5V.js → chunk-3TF2GZSG.js} +7 -7
  56. package/dist/{chunk-TC5IC6OP.cjs → chunk-3VXUOAIJ.cjs} +3 -3
  57. package/dist/{chunk-TC5IC6OP.cjs.map → chunk-3VXUOAIJ.cjs.map} +1 -1
  58. package/dist/{chunk-JHX6JK3M.js → chunk-3X6QVHTD.js} +2 -2
  59. package/dist/{chunk-7VJCGKVP.cjs → chunk-4HZIXRAJ.cjs} +11 -11
  60. package/dist/{chunk-7VJCGKVP.cjs.map → chunk-4HZIXRAJ.cjs.map} +1 -1
  61. package/dist/{chunk-MQAOXZD4.js → chunk-4S57PE43.js} +4 -4
  62. package/dist/{chunk-JT4KVXJK.js → chunk-4V2XYUQL.js} +2 -2
  63. package/dist/{chunk-PMSENLOA.cjs → chunk-544RAXWO.cjs} +19 -19
  64. package/dist/{chunk-PMSENLOA.cjs.map → chunk-544RAXWO.cjs.map} +1 -1
  65. package/dist/{chunk-4JSTBA4F.cjs → chunk-6CW5QSYV.cjs} +7 -7
  66. package/dist/{chunk-4JSTBA4F.cjs.map → chunk-6CW5QSYV.cjs.map} +1 -1
  67. package/dist/{chunk-AR2ZOO6L.js → chunk-6FXFKMJ5.js} +3 -3
  68. package/dist/{chunk-KREHT4AQ.cjs → chunk-6IB27I5K.cjs} +8 -8
  69. package/dist/{chunk-KREHT4AQ.cjs.map → chunk-6IB27I5K.cjs.map} +1 -1
  70. package/dist/{chunk-LGDODZHI.js → chunk-6LTZV6HN.js} +4 -4
  71. package/dist/{chunk-YP4HUBKN.js → chunk-6NAOAKII.js} +2 -2
  72. package/dist/chunk-75Q5OS7L.cjs +10 -0
  73. package/dist/chunk-75Q5OS7L.cjs.map +1 -0
  74. package/dist/{chunk-LQXFXXMJ.cjs → chunk-7FV2X62W.cjs} +7 -7
  75. package/dist/{chunk-LQXFXXMJ.cjs.map → chunk-7FV2X62W.cjs.map} +1 -1
  76. package/dist/{chunk-2T7ZMMH5.cjs → chunk-7NUVLVSL.cjs} +5 -5
  77. package/dist/{chunk-2T7ZMMH5.cjs.map → chunk-7NUVLVSL.cjs.map} +1 -1
  78. package/dist/chunk-ACH7SYQ2.js +47 -0
  79. package/dist/chunk-ACH7SYQ2.js.map +1 -0
  80. package/dist/{chunk-HNGFRRVZ.cjs → chunk-AUNDBUHM.cjs} +3 -3
  81. package/dist/{chunk-HNGFRRVZ.cjs.map → chunk-AUNDBUHM.cjs.map} +1 -1
  82. package/dist/{chunk-XZLSHEHE.cjs → chunk-B2OHBIAL.cjs} +10 -10
  83. package/dist/{chunk-XZLSHEHE.cjs.map → chunk-B2OHBIAL.cjs.map} +1 -1
  84. package/dist/chunk-BDH2MXJW.js +46 -0
  85. package/dist/chunk-BDH2MXJW.js.map +1 -0
  86. package/dist/{chunk-D7SQ7LHJ.js → chunk-BO3KK3ZQ.js} +2 -2
  87. package/dist/{chunk-NOQF7UQ5.cjs → chunk-CFVUUMQA.cjs} +6 -6
  88. package/dist/{chunk-NOQF7UQ5.cjs.map → chunk-CFVUUMQA.cjs.map} +1 -1
  89. package/dist/chunk-CJT7L764.cjs +21 -0
  90. package/dist/chunk-CJT7L764.cjs.map +1 -0
  91. package/dist/{chunk-5XH2PHT2.js → chunk-CNTG7OOO.js} +14 -14
  92. package/dist/{chunk-I6M6CVI2.cjs → chunk-DG3O5NV4.cjs} +3 -3
  93. package/dist/{chunk-I6M6CVI2.cjs.map → chunk-DG3O5NV4.cjs.map} +1 -1
  94. package/dist/{chunk-WNDDS367.js → chunk-DPTTDHZ5.js} +3 -3
  95. package/dist/{chunk-35LGEPLP.cjs → chunk-ENMDOQQO.cjs} +29 -14
  96. package/dist/chunk-ENMDOQQO.cjs.map +1 -0
  97. package/dist/{chunk-V4BZWBZT.cjs → chunk-F5IRMMZ2.cjs} +7 -7
  98. package/dist/{chunk-V4BZWBZT.cjs.map → chunk-F5IRMMZ2.cjs.map} +1 -1
  99. package/dist/{chunk-WE2SBWZ6.js → chunk-FE7S4RSK.js} +10 -10
  100. package/dist/chunk-FE7S4RSK.js.map +1 -0
  101. package/dist/{chunk-BFKPJ627.js → chunk-FHOD3FUN.js} +4 -4
  102. package/dist/{chunk-SNRRF64M.js → chunk-GDRLVX2A.js} +31 -10
  103. package/dist/chunk-GDRLVX2A.js.map +1 -0
  104. package/dist/{chunk-5O73AALP.cjs → chunk-GP6EEZH5.cjs} +7 -3
  105. package/dist/chunk-GP6EEZH5.cjs.map +1 -0
  106. package/dist/{chunk-SDUGYJGW.js → chunk-H3C6YECZ.js} +6 -6
  107. package/dist/{chunk-RJVHR45I.cjs → chunk-H46ZLPNJ.cjs} +7 -7
  108. package/dist/{chunk-RJVHR45I.cjs.map → chunk-H46ZLPNJ.cjs.map} +1 -1
  109. package/dist/{chunk-66UQTF57.cjs → chunk-HLI4SOH3.cjs} +45 -45
  110. package/dist/{chunk-66UQTF57.cjs.map → chunk-HLI4SOH3.cjs.map} +1 -1
  111. package/dist/{chunk-ZMRINZIO.cjs → chunk-HR5MWA3B.cjs} +5 -5
  112. package/dist/{chunk-ZMRINZIO.cjs.map → chunk-HR5MWA3B.cjs.map} +1 -1
  113. package/dist/{chunk-MPIVBAMY.js → chunk-HYRLK462.js} +2 -2
  114. package/dist/{chunk-FIVMMCGM.js → chunk-IHPSRTVW.js} +4 -4
  115. package/dist/{chunk-THCTUBX6.js → chunk-IKXXXVUO.js} +3 -3
  116. package/dist/{chunk-JPYJQ4TF.cjs → chunk-JFV56ISS.cjs} +6 -6
  117. package/dist/{chunk-JPYJQ4TF.cjs.map → chunk-JFV56ISS.cjs.map} +1 -1
  118. package/dist/{chunk-75GFIQRU.js → chunk-JY77VJAZ.js} +30 -15
  119. package/dist/chunk-JY77VJAZ.js.map +1 -0
  120. package/dist/chunk-KXDGW6LJ.cjs +46 -0
  121. package/dist/chunk-KXDGW6LJ.cjs.map +1 -0
  122. package/dist/{chunk-24HKKSKX.js → chunk-L6ERHCQ2.js} +3 -3
  123. package/dist/{chunk-4LNTZPSQ.js → chunk-L6YJ6QTZ.js} +2 -2
  124. package/dist/{chunk-KBQD4JLK.cjs → chunk-LUDK44NZ.cjs} +3 -3
  125. package/dist/{chunk-KBQD4JLK.cjs.map → chunk-LUDK44NZ.cjs.map} +1 -1
  126. package/dist/{chunk-PCTHJZRO.cjs → chunk-LWCFT5SP.cjs} +3 -3
  127. package/dist/{chunk-PCTHJZRO.cjs.map → chunk-LWCFT5SP.cjs.map} +1 -1
  128. package/dist/{chunk-JNY7TUDS.js → chunk-MCPGI6EI.js} +2 -2
  129. package/dist/{chunk-DU5JKTHK.cjs → chunk-MKC6V3SY.cjs} +6 -6
  130. package/dist/{chunk-DU5JKTHK.cjs.map → chunk-MKC6V3SY.cjs.map} +1 -1
  131. package/dist/{chunk-J76JHN7J.cjs → chunk-N5NXKVFJ.cjs} +10 -10
  132. package/dist/{chunk-J76JHN7J.cjs.map → chunk-N5NXKVFJ.cjs.map} +1 -1
  133. package/dist/{chunk-XERBOFYZ.cjs → chunk-NERASTO2.cjs} +3 -3
  134. package/dist/{chunk-XERBOFYZ.cjs.map → chunk-NERASTO2.cjs.map} +1 -1
  135. package/dist/{chunk-3KBBJZHK.cjs → chunk-NFD7MPX4.cjs} +16 -14
  136. package/dist/chunk-NFD7MPX4.cjs.map +1 -0
  137. package/dist/{chunk-3ZRFOKDM.cjs → chunk-NNAEQR3Y.cjs} +30 -30
  138. package/dist/{chunk-3ZRFOKDM.cjs.map → chunk-NNAEQR3Y.cjs.map} +1 -1
  139. package/dist/{chunk-36JFXFM2.cjs → chunk-OGGZIFTI.cjs} +38 -38
  140. package/dist/chunk-OGGZIFTI.cjs.map +1 -0
  141. package/dist/{chunk-2PCCGQOH.js → chunk-P6OJQ2RN.js} +6 -6
  142. package/dist/{chunk-3RWZC6OW.cjs → chunk-PE2SLXUS.cjs} +5 -5
  143. package/dist/{chunk-3RWZC6OW.cjs.map → chunk-PE2SLXUS.cjs.map} +1 -1
  144. package/dist/chunk-QD32IMMH.js +13 -0
  145. package/dist/chunk-QD32IMMH.js.map +1 -0
  146. package/dist/{chunk-HEXAY6DE.cjs → chunk-QUSO4GFA.cjs} +7 -7
  147. package/dist/{chunk-HEXAY6DE.cjs.map → chunk-QUSO4GFA.cjs.map} +1 -1
  148. package/dist/{chunk-OOCCSMWM.js → chunk-RHWZX7PB.js} +9 -7
  149. package/dist/chunk-RHWZX7PB.js.map +1 -0
  150. package/dist/{chunk-YXRKBKFB.js → chunk-S5CMZFX6.js} +3 -3
  151. package/dist/{chunk-4AO5RKV5.cjs → chunk-SLHIVEFW.cjs} +7 -7
  152. package/dist/{chunk-4AO5RKV5.cjs.map → chunk-SLHIVEFW.cjs.map} +1 -1
  153. package/dist/{chunk-HEX4IMLO.js → chunk-TJQNGJHP.js} +4 -4
  154. package/dist/{chunk-JCKGAAPZ.cjs → chunk-TNQ2ZZ7W.cjs} +9 -9
  155. package/dist/{chunk-JCKGAAPZ.cjs.map → chunk-TNQ2ZZ7W.cjs.map} +1 -1
  156. package/dist/chunk-U32K2H3N.js +10 -0
  157. package/dist/chunk-U32K2H3N.js.map +1 -0
  158. package/dist/{chunk-RHHZRTWM.js → chunk-U34INRFO.js} +28 -13
  159. package/dist/chunk-U34INRFO.js.map +1 -0
  160. package/dist/{chunk-SFFKQQ5Z.js → chunk-U4SVBE2M.js} +5 -5
  161. package/dist/{chunk-O3QM3BFE.js → chunk-UC4NYF6T.js} +2 -2
  162. package/dist/{chunk-6JHVXWDV.cjs → chunk-ULKWUKD2.cjs} +22 -22
  163. package/dist/{chunk-6JHVXWDV.cjs.map → chunk-ULKWUKD2.cjs.map} +1 -1
  164. package/dist/{chunk-BEOECPVJ.cjs → chunk-UTYY35QX.cjs} +3 -3
  165. package/dist/{chunk-BEOECPVJ.cjs.map → chunk-UTYY35QX.cjs.map} +1 -1
  166. package/dist/{chunk-7BFODYNL.cjs → chunk-UUT2VEN5.cjs} +7 -7
  167. package/dist/{chunk-7BFODYNL.cjs.map → chunk-UUT2VEN5.cjs.map} +1 -1
  168. package/dist/chunk-V3T7RO4F.js +21 -0
  169. package/dist/chunk-V3T7RO4F.js.map +1 -0
  170. package/dist/{chunk-GIEDIAPD.js → chunk-V5CM5TH3.js} +3 -3
  171. package/dist/{chunk-L65MHFMD.js → chunk-V64DICJK.js} +3 -3
  172. package/dist/{chunk-KIZVY57H.cjs → chunk-VXC7Z7XB.cjs} +5 -5
  173. package/dist/{chunk-KIZVY57H.cjs.map → chunk-VXC7Z7XB.cjs.map} +1 -1
  174. package/dist/chunk-VYF7WV4K.cjs +47 -0
  175. package/dist/chunk-VYF7WV4K.cjs.map +1 -0
  176. package/dist/{chunk-P4XZH3OR.js → chunk-WHQGJHMD.js} +4 -4
  177. package/dist/{chunk-TTLFY76J.cjs → chunk-WR77IZUU.cjs} +32 -9
  178. package/dist/chunk-WR77IZUU.cjs.map +1 -0
  179. package/dist/{chunk-O2BECFXJ.cjs → chunk-WTFEMOV5.cjs} +43 -22
  180. package/dist/chunk-WTFEMOV5.cjs.map +1 -0
  181. package/dist/{chunk-YAKTVIVK.js → chunk-XIUZT4TO.js} +3 -3
  182. package/dist/chunk-XPG56DLQ.cjs +103 -0
  183. package/dist/chunk-XPG56DLQ.cjs.map +1 -0
  184. package/dist/{chunk-GPVGGBIR.js → chunk-XWMXEJVZ.js} +2 -2
  185. package/dist/chunk-XZILHRKG.cjs +13 -0
  186. package/dist/chunk-XZILHRKG.cjs.map +1 -0
  187. package/dist/{chunk-VQGLVBPL.cjs → chunk-YIOHKQ6Y.cjs} +11 -4
  188. package/dist/chunk-YIOHKQ6Y.cjs.map +1 -0
  189. package/dist/{chunk-A3LMUPG3.js → chunk-Z3IJWYUD.js} +2 -2
  190. package/dist/{chunk-4J25XZDI.js → chunk-ZJWQPZ7E.js} +9 -2
  191. package/dist/chunk-ZJWQPZ7E.js.map +1 -0
  192. package/dist/color-picker/index.cjs +5 -4
  193. package/dist/color-picker/index.cjs.map +1 -1
  194. package/dist/color-picker/index.js +4 -3
  195. package/dist/combobox/index.cjs +7 -6
  196. package/dist/combobox/index.cjs.map +1 -1
  197. package/dist/combobox/index.js +6 -5
  198. package/dist/command-palette/index.cjs +4 -3
  199. package/dist/command-palette/index.cjs.map +1 -1
  200. package/dist/command-palette/index.js +3 -2
  201. package/dist/context-menu/index.cjs +7 -6
  202. package/dist/context-menu/index.cjs.map +1 -1
  203. package/dist/context-menu/index.d.cts +2 -2
  204. package/dist/context-menu/index.d.ts +2 -2
  205. package/dist/context-menu/index.js +6 -5
  206. package/dist/conversation/index.cjs +11 -8
  207. package/dist/conversation/index.cjs.map +1 -1
  208. package/dist/conversation/index.d.cts +3 -3
  209. package/dist/conversation/index.d.ts +3 -3
  210. package/dist/conversation/index.js +10 -7
  211. package/dist/data-grid/index.cjs +18 -15
  212. package/dist/data-grid/index.cjs.map +1 -1
  213. package/dist/data-grid/index.d.cts +8 -8
  214. package/dist/data-grid/index.d.ts +8 -8
  215. package/dist/data-grid/index.js +17 -14
  216. package/dist/date-picker/index.cjs +11 -8
  217. package/dist/date-picker/index.cjs.map +1 -1
  218. package/dist/date-picker/index.d.cts +2 -2
  219. package/dist/date-picker/index.d.ts +2 -2
  220. package/dist/date-picker/index.js +10 -7
  221. package/dist/drawer/index.cjs +8 -5
  222. package/dist/drawer/index.cjs.map +1 -1
  223. package/dist/drawer/index.d.cts +5 -4
  224. package/dist/drawer/index.d.ts +5 -4
  225. package/dist/drawer/index.js +7 -4
  226. package/dist/dropdown/index.cjs +4 -3
  227. package/dist/dropdown/index.cjs.map +1 -1
  228. package/dist/dropdown/index.js +3 -2
  229. package/dist/header/index.cjs +9 -6
  230. package/dist/header/index.cjs.map +1 -1
  231. package/dist/header/index.d.cts +10 -10
  232. package/dist/header/index.d.ts +10 -10
  233. package/dist/header/index.js +8 -5
  234. package/dist/index.cjs +56 -43
  235. package/dist/index.cjs.map +1 -1
  236. package/dist/index.css +48 -14
  237. package/dist/index.d.cts +14 -13
  238. package/dist/index.d.ts +14 -13
  239. package/dist/index.js +60 -47
  240. package/dist/inline-edit/index.cjs +6 -5
  241. package/dist/inline-edit/index.cjs.map +1 -1
  242. package/dist/inline-edit/index.js +5 -4
  243. package/dist/input/index.cjs +5 -4
  244. package/dist/input/index.cjs.map +1 -1
  245. package/dist/input/index.d.cts +3 -3
  246. package/dist/input/index.d.ts +3 -3
  247. package/dist/input/index.js +4 -3
  248. package/dist/link-provider/index.cjs +12 -0
  249. package/dist/link-provider/index.cjs.map +1 -0
  250. package/dist/link-provider/index.d.cts +43 -0
  251. package/dist/link-provider/index.d.ts +43 -0
  252. package/dist/link-provider/index.js +12 -0
  253. package/dist/link-provider/index.js.map +1 -0
  254. package/dist/lucide-icons/index.cjs +9 -0
  255. package/dist/lucide-icons/index.cjs.map +1 -0
  256. package/dist/lucide-icons/index.d.cts +2 -0
  257. package/dist/lucide-icons/index.d.ts +2 -0
  258. package/dist/lucide-icons/index.js +9 -0
  259. package/dist/lucide-icons/index.js.map +1 -0
  260. package/dist/menu/index.cjs +6 -5
  261. package/dist/menu/index.cjs.map +1 -1
  262. package/dist/menu/index.d.cts +3 -3
  263. package/dist/menu/index.d.ts +3 -3
  264. package/dist/menu/index.js +5 -4
  265. package/dist/modal/index.cjs +8 -5
  266. package/dist/modal/index.cjs.map +1 -1
  267. package/dist/modal/index.d.cts +2 -2
  268. package/dist/modal/index.d.ts +2 -2
  269. package/dist/modal/index.js +7 -4
  270. package/dist/navigation/index.cjs +8 -5
  271. package/dist/navigation/index.cjs.map +1 -1
  272. package/dist/navigation/index.d.cts +3 -3
  273. package/dist/navigation/index.d.ts +3 -3
  274. package/dist/navigation/index.js +7 -4
  275. package/dist/number-input/index.cjs +4 -3
  276. package/dist/number-input/index.cjs.map +1 -1
  277. package/dist/number-input/index.js +3 -2
  278. package/dist/page-layout/index.cjs +16 -13
  279. package/dist/page-layout/index.cjs.map +1 -1
  280. package/dist/page-layout/index.d.cts +8 -8
  281. package/dist/page-layout/index.d.ts +8 -8
  282. package/dist/page-layout/index.js +15 -12
  283. package/dist/pagination/index.cjs +11 -8
  284. package/dist/pagination/index.cjs.map +1 -1
  285. package/dist/pagination/index.js +10 -7
  286. package/dist/popover/index.cjs +4 -3
  287. package/dist/popover/index.cjs.map +1 -1
  288. package/dist/popover/index.d.cts +1 -1
  289. package/dist/popover/index.d.ts +1 -1
  290. package/dist/popover/index.js +3 -2
  291. package/dist/renderIcon-BW3PUtg8.d.cts +50 -0
  292. package/dist/renderIcon-BW3PUtg8.d.ts +50 -0
  293. package/dist/segmented-control/index.cjs +5 -4
  294. package/dist/segmented-control/index.cjs.map +1 -1
  295. package/dist/segmented-control/index.d.cts +3 -3
  296. package/dist/segmented-control/index.d.ts +3 -3
  297. package/dist/segmented-control/index.js +4 -3
  298. package/dist/select/index.cjs +7 -6
  299. package/dist/select/index.cjs.map +1 -1
  300. package/dist/select/index.d.cts +4 -4
  301. package/dist/select/index.d.ts +4 -4
  302. package/dist/select/index.js +6 -5
  303. package/dist/snackbar/index.cjs +8 -5
  304. package/dist/snackbar/index.cjs.map +1 -1
  305. package/dist/snackbar/index.js +7 -4
  306. package/dist/split-button/index.cjs +7 -6
  307. package/dist/split-button/index.cjs.map +1 -1
  308. package/dist/split-button/index.d.cts +2 -2
  309. package/dist/split-button/index.d.ts +2 -2
  310. package/dist/split-button/index.js +6 -5
  311. package/dist/split-chip/index.cjs +8 -5
  312. package/dist/split-chip/index.cjs.map +1 -1
  313. package/dist/split-chip/index.d.cts +2 -2
  314. package/dist/split-chip/index.d.ts +2 -2
  315. package/dist/split-chip/index.js +7 -4
  316. package/dist/table/index.cjs +16 -13
  317. package/dist/table/index.cjs.map +1 -1
  318. package/dist/table/index.d.cts +4 -4
  319. package/dist/table/index.d.ts +4 -4
  320. package/dist/table/index.js +15 -12
  321. package/dist/tabs/index.cjs +4 -3
  322. package/dist/tabs/index.cjs.map +1 -1
  323. package/dist/tabs/index.d.cts +1 -1
  324. package/dist/tabs/index.d.ts +1 -1
  325. package/dist/tabs/index.js +3 -2
  326. package/dist/tag-input/index.cjs +9 -6
  327. package/dist/tag-input/index.cjs.map +1 -1
  328. package/dist/tag-input/index.js +8 -5
  329. package/dist/textarea/index.cjs +5 -4
  330. package/dist/textarea/index.cjs.map +1 -1
  331. package/dist/textarea/index.d.cts +1 -1
  332. package/dist/textarea/index.d.ts +1 -1
  333. package/dist/textarea/index.js +4 -3
  334. package/dist/theme-editor/index.cjs +14 -11
  335. package/dist/theme-editor/index.cjs.map +1 -1
  336. package/dist/theme-editor/index.js +13 -10
  337. package/dist/theme-provider/index.cjs +4 -3
  338. package/dist/theme-provider/index.cjs.map +1 -1
  339. package/dist/theme-provider/index.js +3 -2
  340. package/dist/timeline/index.cjs +2 -2
  341. package/dist/timeline/index.d.cts +18 -3
  342. package/dist/timeline/index.d.ts +18 -3
  343. package/dist/timeline/index.js +1 -1
  344. package/dist/toolbar/index.cjs +12 -9
  345. package/dist/toolbar/index.cjs.map +1 -1
  346. package/dist/toolbar/index.d.cts +7 -7
  347. package/dist/toolbar/index.d.ts +7 -7
  348. package/dist/toolbar/index.js +11 -8
  349. package/dist/tooltip/index.cjs +4 -3
  350. package/dist/tooltip/index.cjs.map +1 -1
  351. package/dist/tooltip/index.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/Button.types-BH0CPyPx.d.ts +0 -33
  354. package/dist/Button.types-DZRCVqyN.d.cts +0 -33
  355. package/dist/chunk-35LGEPLP.cjs.map +0 -1
  356. package/dist/chunk-36JFXFM2.cjs.map +0 -1
  357. package/dist/chunk-3KBBJZHK.cjs.map +0 -1
  358. package/dist/chunk-4J25XZDI.js.map +0 -1
  359. package/dist/chunk-5O73AALP.cjs.map +0 -1
  360. package/dist/chunk-75GFIQRU.js.map +0 -1
  361. package/dist/chunk-DA375AI6.cjs +0 -88
  362. package/dist/chunk-DA375AI6.cjs.map +0 -1
  363. package/dist/chunk-DRXPTACG.js.map +0 -1
  364. package/dist/chunk-GWHZFX2H.js.map +0 -1
  365. package/dist/chunk-NEQXEVHM.js +0 -34
  366. package/dist/chunk-NEQXEVHM.js.map +0 -1
  367. package/dist/chunk-O2BECFXJ.cjs.map +0 -1
  368. package/dist/chunk-OOCCSMWM.js.map +0 -1
  369. package/dist/chunk-RHHZRTWM.js.map +0 -1
  370. package/dist/chunk-SNRRF64M.js.map +0 -1
  371. package/dist/chunk-TTLFY76J.cjs.map +0 -1
  372. package/dist/chunk-VQGLVBPL.cjs.map +0 -1
  373. package/dist/chunk-WE2SBWZ6.js.map +0 -1
  374. package/dist/chunk-ZMDZGL5A.cjs +0 -34
  375. package/dist/chunk-ZMDZGL5A.cjs.map +0 -1
  376. package/dist/renderIcon-C2lEMMzb.d.cts +0 -30
  377. package/dist/renderIcon-C2lEMMzb.d.ts +0 -30
  378. /package/dist/{chunk-L7I5NEGI.js.map → chunk-2IXCEKLI.js.map} +0 -0
  379. /package/dist/{chunk-RHFD2Z5V.js.map → chunk-3TF2GZSG.js.map} +0 -0
  380. /package/dist/{chunk-JHX6JK3M.js.map → chunk-3X6QVHTD.js.map} +0 -0
  381. /package/dist/{chunk-MQAOXZD4.js.map → chunk-4S57PE43.js.map} +0 -0
  382. /package/dist/{chunk-JT4KVXJK.js.map → chunk-4V2XYUQL.js.map} +0 -0
  383. /package/dist/{chunk-AR2ZOO6L.js.map → chunk-6FXFKMJ5.js.map} +0 -0
  384. /package/dist/{chunk-LGDODZHI.js.map → chunk-6LTZV6HN.js.map} +0 -0
  385. /package/dist/{chunk-YP4HUBKN.js.map → chunk-6NAOAKII.js.map} +0 -0
  386. /package/dist/{chunk-D7SQ7LHJ.js.map → chunk-BO3KK3ZQ.js.map} +0 -0
  387. /package/dist/{chunk-5XH2PHT2.js.map → chunk-CNTG7OOO.js.map} +0 -0
  388. /package/dist/{chunk-WNDDS367.js.map → chunk-DPTTDHZ5.js.map} +0 -0
  389. /package/dist/{chunk-BFKPJ627.js.map → chunk-FHOD3FUN.js.map} +0 -0
  390. /package/dist/{chunk-SDUGYJGW.js.map → chunk-H3C6YECZ.js.map} +0 -0
  391. /package/dist/{chunk-MPIVBAMY.js.map → chunk-HYRLK462.js.map} +0 -0
  392. /package/dist/{chunk-FIVMMCGM.js.map → chunk-IHPSRTVW.js.map} +0 -0
  393. /package/dist/{chunk-THCTUBX6.js.map → chunk-IKXXXVUO.js.map} +0 -0
  394. /package/dist/{chunk-24HKKSKX.js.map → chunk-L6ERHCQ2.js.map} +0 -0
  395. /package/dist/{chunk-4LNTZPSQ.js.map → chunk-L6YJ6QTZ.js.map} +0 -0
  396. /package/dist/{chunk-JNY7TUDS.js.map → chunk-MCPGI6EI.js.map} +0 -0
  397. /package/dist/{chunk-2PCCGQOH.js.map → chunk-P6OJQ2RN.js.map} +0 -0
  398. /package/dist/{chunk-YXRKBKFB.js.map → chunk-S5CMZFX6.js.map} +0 -0
  399. /package/dist/{chunk-HEX4IMLO.js.map → chunk-TJQNGJHP.js.map} +0 -0
  400. /package/dist/{chunk-SFFKQQ5Z.js.map → chunk-U4SVBE2M.js.map} +0 -0
  401. /package/dist/{chunk-O3QM3BFE.js.map → chunk-UC4NYF6T.js.map} +0 -0
  402. /package/dist/{chunk-GIEDIAPD.js.map → chunk-V5CM5TH3.js.map} +0 -0
  403. /package/dist/{chunk-L65MHFMD.js.map → chunk-V64DICJK.js.map} +0 -0
  404. /package/dist/{chunk-P4XZH3OR.js.map → chunk-WHQGJHMD.js.map} +0 -0
  405. /package/dist/{chunk-YAKTVIVK.js.map → chunk-XIUZT4TO.js.map} +0 -0
  406. /package/dist/{chunk-GPVGGBIR.js.map → chunk-XWMXEJVZ.js.map} +0 -0
  407. /package/dist/{chunk-A3LMUPG3.js.map → chunk-Z3IJWYUD.js.map} +0 -0
@@ -1,88 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
-
3
- var _chunkBEOECPVJcjs = require('./chunk-BEOECPVJ.cjs');
4
-
5
-
6
-
7
- var _chunk35LGEPLPcjs = require('./chunk-35LGEPLP.cjs');
8
-
9
- // src/components/Button/Button.component.tsx
10
- var _lucidereact = require('lucide-react');
11
- var _jsxruntime = require('react/jsx-runtime');
12
- var Button = ({
13
- variant = "filled",
14
- color = "primary",
15
- size = "md",
16
- disabled = false,
17
- loading = false,
18
- loadingText = "Loading...",
19
- tooltip,
20
- className = "",
21
- preIcon,
22
- posIcon,
23
- icon,
24
- children,
25
- ...buttonProps
26
- }) => {
27
- const isIconOnly = !!icon;
28
- if (isIconOnly) {
29
- if (children) {
30
- throw new Error("Icon-only buttons cannot have children");
31
- }
32
- if (preIcon) {
33
- throw new Error("Icon-only buttons cannot have preIcon");
34
- }
35
- if (posIcon) {
36
- throw new Error("Icon-only buttons cannot have posIcon");
37
- }
38
- }
39
- const suppliedLabel = _nullishCoalesce(buttonProps["aria-label"], () => ( buttonProps["aria-labelledby"]));
40
- const iconOnlyLabel = isIconOnly && !suppliedLabel ? tooltip : void 0;
41
- if (isIconOnly && !suppliedLabel && !tooltip) {
42
- _chunk35LGEPLPcjs.devWarn.call(void 0,
43
- "button-icon-only-name",
44
- 'Button: an icon-only button needs an accessible name. Pass `tooltip` (which now also names it) or `aria-label`, otherwise it is announced as just "button".'
45
- );
46
- }
47
- const buttonClasses = [
48
- "eidos-button",
49
- `eidos-button--${variant}`,
50
- `eidos-button--${color}`,
51
- `eidos-button--${size}`,
52
- isIconOnly && "eidos-button--icon-only",
53
- disabled && "eidos-button--disabled",
54
- loading && "eidos-button--loading",
55
- className
56
- ].filter(Boolean).join(" ");
57
- const buttonContent = /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
58
- "button",
59
- {
60
- type: "button",
61
- ...buttonProps,
62
- "aria-label": _nullishCoalesce(iconOnlyLabel, () => ( buttonProps["aria-label"])),
63
- className: buttonClasses,
64
- disabled: disabled || loading,
65
- children: loading ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
66
- !isIconOnly && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
67
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "eidos-button--loading-spinner", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.LoaderCircle, { className: "eidos-button--spinner-icon" }) }),
68
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "eidos-button--copy", children: loadingText })
69
- ] }),
70
- isIconOnly && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "eidos-button--loading-spinner", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.LoaderCircle, { className: "eidos-button--spinner-icon" }) })
71
- ] }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
72
- preIcon && _chunk35LGEPLPcjs.renderIcon.call(void 0, preIcon, "eidos-button--pre-icon"),
73
- isIconOnly && icon ? _chunk35LGEPLPcjs.renderIcon.call(void 0, icon, "eidos-button--icon") : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "eidos-button--copy", children }),
74
- posIcon && _chunk35LGEPLPcjs.renderIcon.call(void 0, posIcon, "eidos-button--pos-icon")
75
- ] })
76
- }
77
- );
78
- return tooltip ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkBEOECPVJcjs.Tooltip, { message: tooltip, children: buttonContent }) : buttonContent;
79
- };
80
- var IconButton = (props) => {
81
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { ...props });
82
- };
83
-
84
-
85
-
86
-
87
- exports.Button = Button; exports.IconButton = IconButton;
88
- //# sourceMappingURL=chunk-DA375AI6.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-DA375AI6.cjs","../src/components/Button/Button.component.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;ACPA,2CAA6B;AAkFjB,+CAAA;AA7EL,IAAM,OAAA,EAAgC,CAAC;AAAA,EAC5C,QAAA,EAAU,QAAA;AAAA,EACV,MAAA,EAAQ,SAAA;AAAA,EACR,KAAA,EAAO,IAAA;AAAA,EACP,SAAA,EAAW,KAAA;AAAA,EACX,QAAA,EAAU,KAAA;AAAA,EACV,YAAA,EAAc,YAAA;AAAA,EACd,OAAA;AAAA,EACA,UAAA,EAAY,EAAA;AAAA,EACZ,OAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,WAAA,EAAa,CAAC,CAAC,IAAA;AAErB,EAAA,GAAA,CAAI,UAAA,EAAY;AACd,IAAA,GAAA,CAAI,QAAA,EAAU;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,wCAAwC,CAAA;AAAA,IAC1D;AACA,IAAA,GAAA,CAAI,OAAA,EAAS;AACX,MAAA,MAAM,IAAI,KAAA,CAAM,uCAAuC,CAAA;AAAA,IACzD;AACA,IAAA,GAAA,CAAI,OAAA,EAAS;AACX,MAAA,MAAM,IAAI,KAAA,CAAM,uCAAuC,CAAA;AAAA,IACzD;AAAA,EACF;AAgBA,EAAA,MAAM,cAAA,mBAAgB,WAAA,CAAY,YAAY,CAAA,UAAK,WAAA,CAAY,iBAAiB,GAAA;AAChF,EAAA,MAAM,cAAA,EAAgB,WAAA,GAAc,CAAC,cAAA,EAAgB,QAAA,EAAU,KAAA,CAAA;AAE/D,EAAA,GAAA,CAAI,WAAA,GAAc,CAAC,cAAA,GAAiB,CAAC,OAAA,EAAS;AAC5C,IAAA,uCAAA;AAAA,MACE,uBAAA;AAAA,MACA;AAAA,IACF,CAAA;AAAA,EACF;AAEA,EAAA,MAAM,cAAA,EAAgB;AAAA,IACpB,cAAA;AAAA,IACA,CAAA,cAAA,EAAiB,OAAO,CAAA,CAAA;AACF,IAAA;AACD,IAAA;AACP,IAAA;AACF,IAAA;AACD,IAAA;AACX,IAAA;AAGS,EAAA;AAGT,EAAA;AAAC,IAAA;AAAA,IAAA;AACM,MAAA;AACD,MAAA;AACQ,MAAA;AACD,MAAA;AACW,MAAA;AAGpB,MAAA;AAEI,QAAA;AACE,0BAAA;AAGA,0BAAA;AACF,QAAA;AAGA,QAAA;AAMJ,MAAA;AACc,QAAA;AAGV,QAAA;AAKU,QAAA;AACd,MAAA;AAAA,IAAA;AAEJ,EAAA;AAGe,EAAA;AACnB;AAagE;AACtD,EAAA;AACV;AD9C6B;AACA;AACA;AACA;AACA","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-DA375AI6.cjs","sourcesContent":[null,"import React from 'react';\nimport { LoaderCircle } from 'lucide-react';\nimport { Tooltip } from '../Tooltip';\nimport { ButtonProps, IconButtonProps } from './Button.types';\nimport { renderIcon, devWarn } from '../../utils';\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'filled',\n color = 'primary',\n size = 'md',\n disabled = false,\n loading = false,\n loadingText = 'Loading...',\n tooltip,\n className = '',\n preIcon,\n posIcon,\n icon,\n children,\n ...buttonProps\n}) => {\n const isIconOnly = !!icon;\n\n if (isIconOnly) {\n if (children) {\n throw new Error('Icon-only buttons cannot have children');\n }\n if (preIcon) {\n throw new Error('Icon-only buttons cannot have preIcon');\n }\n if (posIcon) {\n throw new Error('Icon-only buttons cannot have posIcon');\n }\n }\n\n // ── Accessible name for icon-only buttons ──────────────────────────────\n //\n // An icon-only button has no text, so without `aria-label` it is announced\n // as just \"button\". `tooltip` alone did NOT fix this: `Tooltip` renders a\n // floating panel and adds no naming attributes to its child, so the\n // library's own documented example - `<IconButton icon={Plus}\n // tooltip=\"Add item\" />` - shipped a nameless control.\n //\n // Falling back to `tooltip` makes that documented pattern correct without\n // asking anyone to change their code: if you already described the button\n // for sighted users, that description now names it for everyone.\n //\n // Only ever a fallback - an explicit `aria-label` or `aria-labelledby`\n // always wins, since the caller may want a longer name than the tooltip.\n const suppliedLabel = buttonProps['aria-label'] ?? buttonProps['aria-labelledby'];\n const iconOnlyLabel = isIconOnly && !suppliedLabel ? tooltip : undefined;\n\n if (isIconOnly && !suppliedLabel && !tooltip) {\n devWarn(\n 'button-icon-only-name',\n 'Button: an icon-only button needs an accessible name. Pass `tooltip` (which now also names it) or `aria-label`, otherwise it is announced as just \"button\".',\n );\n }\n\n const buttonClasses = [\n 'eidos-button',\n `eidos-button--${variant}`,\n `eidos-button--${color}`,\n `eidos-button--${size}`,\n isIconOnly && 'eidos-button--icon-only',\n disabled && 'eidos-button--disabled',\n loading && 'eidos-button--loading',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const buttonContent = (\n <button\n type=\"button\"\n {...buttonProps}\n aria-label={iconOnlyLabel ?? buttonProps['aria-label']}\n className={buttonClasses}\n disabled={disabled || loading}\n >\n {loading ? (\n <>\n {!isIconOnly && (\n <>\n <span className=\"eidos-button--loading-spinner\">\n <LoaderCircle className=\"eidos-button--spinner-icon\" />\n </span>\n <span className=\"eidos-button--copy\">{loadingText}</span>\n </>\n )}\n {isIconOnly && (\n <span className=\"eidos-button--loading-spinner\">\n <LoaderCircle className=\"eidos-button--spinner-icon\" />\n </span>\n )}\n </>\n ) : (\n <>\n {preIcon && renderIcon(preIcon, 'eidos-button--pre-icon')}\n\n {isIconOnly && icon ? (\n renderIcon(icon, 'eidos-button--icon')\n ) : (\n <span className=\"eidos-button--copy\">{children}</span>\n )}\n\n {posIcon && renderIcon(posIcon, 'eidos-button--pos-icon')}\n </>\n )}\n </button>\n );\n\n return tooltip ? <Tooltip message={tooltip}>{buttonContent}</Tooltip> : buttonContent;\n};\n\n/**\n * IconButton - Convenience wrapper for icon-only buttons\n *\n * @example\n * ```tsx\n * import { IconButton } from 'eidos-ui';\n * import { Plus } from 'lucide-react';\n *\n * <IconButton icon={Plus} tooltip=\"Add item\" />\n * ```\n */\nexport const IconButton: React.FC<IconButtonProps> = (props) => {\n return <Button {...props} />;\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Breadcrumb/Breadcrumb.component.tsx"],"sourcesContent":["import React from 'react';\nimport type { BreadcrumbProps } from './Breadcrumb.types';\n\nexport const Breadcrumb: React.FC<BreadcrumbProps> = ({\n items,\n separator = '/',\n className = '',\n}) => (\n <nav\n aria-label=\"Breadcrumb\"\n className={['eidos-breadcrumb-nav', className].filter(Boolean).join(' ')}\n >\n <ol className=\"eidos-breadcrumb\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n\n return (\n <li\n key={index}\n className={['eidos-breadcrumb-item', isLast && 'eidos-breadcrumb-item--current']\n .filter(Boolean)\n .join(' ')}\n >\n {isLast ? (\n <span aria-current=\"page\" className=\"eidos-breadcrumb-current\">\n {item.icon && <span className=\"eidos-breadcrumb-icon\">{item.icon}</span>}\n {item.label}\n </span>\n ) : item.href ? (\n <a href={item.href} onClick={item.onClick} className=\"eidos-breadcrumb-link\">\n {item.icon && <span className=\"eidos-breadcrumb-icon\">{item.icon}</span>}\n {item.label}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={item.onClick}\n className=\"eidos-breadcrumb-link eidos-breadcrumb-link--button\"\n >\n {item.icon && <span className=\"eidos-breadcrumb-icon\">{item.icon}</span>}\n {item.label}\n </button>\n )}\n\n {!isLast && (\n <span aria-hidden=\"true\" className=\"eidos-breadcrumb-separator\">\n {separator}\n </span>\n )}\n </li>\n );\n })}\n </ol>\n </nav>\n);\n\nBreadcrumb.displayName = 'Breadcrumb';\n"],"mappings":";AAwBc,SACgB,KADhB;AArBP,IAAM,aAAwC,CAAC;AAAA,EACpD;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AACd,MACE;AAAA,EAAC;AAAA;AAAA,IACC,cAAW;AAAA,IACX,WAAW,CAAC,wBAAwB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,IAEvE,8BAAC,QAAG,WAAU,oBACX,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,YAAM,SAAS,UAAU,MAAM,SAAS;AAExC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAW,CAAC,yBAAyB,UAAU,gCAAgC,EAC5E,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,UAEV;AAAA,qBACC,qBAAC,UAAK,gBAAa,QAAO,WAAU,4BACjC;AAAA,mBAAK,QAAQ,oBAAC,UAAK,WAAU,yBAAyB,eAAK,MAAK;AAAA,cAChE,KAAK;AAAA,eACR,IACE,KAAK,OACP,qBAAC,OAAE,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,WAAU,yBAClD;AAAA,mBAAK,QAAQ,oBAAC,UAAK,WAAU,yBAAyB,eAAK,MAAK;AAAA,cAChE,KAAK;AAAA,eACR,IAEA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,KAAK;AAAA,gBACd,WAAU;AAAA,gBAET;AAAA,uBAAK,QAAQ,oBAAC,UAAK,WAAU,yBAAyB,eAAK,MAAK;AAAA,kBAChE,KAAK;AAAA;AAAA;AAAA,YACR;AAAA,YAGD,CAAC,UACA,oBAAC,UAAK,eAAY,QAAO,WAAU,8BAChC,qBACH;AAAA;AAAA;AAAA,QA7BG;AAAA,MA+BP;AAAA,IAEJ,CAAC,GACH;AAAA;AACF;AAGF,WAAW,cAAc;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Chip/Chip.component.tsx"],"sourcesContent":["import React from 'react';\nimport { X } from 'lucide-react';\nimport type { ChipProps } from './Chip.types';\nimport { Tooltip } from '../Tooltip/Tooltip.component';\nimport { renderIcon } from '../../utils';\n\nexport const Chip: React.FC<ChipProps> = ({\n variant = 'filled',\n color = 'primary',\n size = 'md',\n disabled = false,\n fullWidth = false,\n tooltip,\n className = '',\n preIcon,\n posIcon,\n children,\n onClick,\n onRemove,\n ...chipProps\n}) => {\n const isClickable = !!onClick;\n const isRemovable = !!onRemove;\n\n // Build CSS classes\n const chipClasses = [\n 'eidos-chip',\n `eidos-chip--${variant}`,\n `eidos-chip--${color}`,\n `eidos-chip--${size}`,\n disabled && 'eidos-chip--disabled',\n isClickable && 'eidos-chip--clickable',\n isRemovable && 'eidos-chip--removable',\n fullWidth && 'eidos-chip--full-width',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleClick = () => {\n if (disabled || !onClick) return;\n onClick();\n };\n\n const handleRemove = (e: React.MouseEvent) => {\n e.stopPropagation(); // Prevent chip click when removing\n if (disabled || !onRemove) return;\n onRemove();\n };\n\n // A chip that is both clickable and removable holds *two* controls, so it\n // has to render them as siblings.\n //\n // The previous shape switched the outer element to a `<div>` when\n // removable - the comment said \"to avoid nested buttons\" - and then gave\n // that div `role=\"button\"` and `tabIndex={0}`. That is the same defect\n // wearing a different hat: a button containing a button, which is invalid\n // (`nested-interactive`) and leaves a screen reader announcing a control\n // inside a control. It is the same shape already fixed on `Dropdown` and\n // `DataGrid`'s rows.\n //\n // Now the outer element is a plain container whenever there is a remove\n // button, and the chip's own action moves to an inner `<button>` wrapping\n // just the content. Each control is separately reachable and separately\n // named.\n const hasBothControls = isClickable && isRemovable;\n const ChipElement = isClickable && !isRemovable ? 'button' : 'div';\n\n const body = (\n <>\n {preIcon && renderIcon(preIcon, 'eidos-chip--pre-icon')}\n\n <span className=\"eidos-chip--copy\">{children}</span>\n\n {posIcon && renderIcon(posIcon, 'eidos-chip--pos-icon')}\n </>\n );\n\n const chipContent = (\n <ChipElement\n className={chipClasses}\n disabled={ChipElement === 'button' ? disabled : undefined}\n onClick={isClickable && !hasBothControls ? handleClick : undefined}\n {...chipProps}\n >\n {hasBothControls ? (\n <button\n type=\"button\"\n className=\"eidos-chip--action\"\n onClick={handleClick}\n disabled={disabled}\n >\n {body}\n </button>\n ) : (\n body\n )}\n\n {isRemovable && (\n <button\n className=\"eidos-chip--remove-button\"\n onClick={handleRemove}\n disabled={disabled}\n aria-label=\"Remove\"\n type=\"button\"\n >\n <X className=\"eidos-chip--remove-icon\" />\n </button>\n )}\n </ChipElement>\n );\n\n return tooltip ? <Tooltip message={tooltip}>{chipContent}</Tooltip> : chipContent;\n};\n"],"mappings":";;;;;;;;AACA,SAAS,SAAS;AAoEd,mBAGE,KAHF;AA/DG,IAAM,OAA4B,CAAC;AAAA,EACxC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,cAAc,CAAC,CAAC;AACtB,QAAM,cAAc,CAAC,CAAC;AAGtB,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,eAAe,OAAO;AAAA,IACtB,eAAe,KAAK;AAAA,IACpB,eAAe,IAAI;AAAA,IACnB,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb;AAAA,EACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,QAAM,cAAc,MAAM;AACxB,QAAI,YAAY,CAAC,QAAS;AAC1B,YAAQ;AAAA,EACV;AAEA,QAAM,eAAe,CAAC,MAAwB;AAC5C,MAAE,gBAAgB;AAClB,QAAI,YAAY,CAAC,SAAU;AAC3B,aAAS;AAAA,EACX;AAiBA,QAAM,kBAAkB,eAAe;AACvC,QAAM,cAAc,eAAe,CAAC,cAAc,WAAW;AAE7D,QAAM,OACJ,iCACG;AAAA,eAAW,WAAW,SAAS,sBAAsB;AAAA,IAEtD,oBAAC,UAAK,WAAU,oBAAoB,UAAS;AAAA,IAE5C,WAAW,WAAW,SAAS,sBAAsB;AAAA,KACxD;AAGF,QAAM,cACJ;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,UAAU,gBAAgB,WAAW,WAAW;AAAA,MAChD,SAAS,eAAe,CAAC,kBAAkB,cAAc;AAAA,MACxD,GAAG;AAAA,MAEH;AAAA,0BACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS;AAAA,YACT;AAAA,YAEC;AAAA;AAAA,QACH,IAEA;AAAA,QAGD,eACC;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS;AAAA,YACT;AAAA,YACA,cAAW;AAAA,YACX,MAAK;AAAA,YAEL,8BAAC,KAAE,WAAU,2BAA0B;AAAA;AAAA,QACzC;AAAA;AAAA;AAAA,EAEJ;AAGF,SAAO,UAAU,oBAAC,WAAQ,SAAS,SAAU,uBAAY,IAAa;AACxE;","names":[]}
@@ -1,34 +0,0 @@
1
- // src/components/Timeline/Timeline.component.tsx
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- var Timeline = ({ items, className = "" }) => /* @__PURE__ */ jsx("ol", { className: ["eidos-timeline", className].filter(Boolean).join(" "), "aria-label": "Timeline", children: items.map((item, index) => {
4
- const isLast = index === items.length - 1;
5
- const color = item.color ?? "default";
6
- return /* @__PURE__ */ jsxs(
7
- "li",
8
- {
9
- className: [
10
- "eidos-timeline-item",
11
- `eidos-timeline-item--${color}`,
12
- !isLast && "eidos-timeline-item--has-connector"
13
- ].filter(Boolean).join(" "),
14
- children: [
15
- /* @__PURE__ */ jsxs("div", { className: "eidos-timeline-track", "aria-hidden": "true", children: [
16
- /* @__PURE__ */ jsx("div", { className: "eidos-timeline-dot", children: item.icon && /* @__PURE__ */ jsx("span", { className: "eidos-timeline-dot-icon", children: item.icon }) }),
17
- !isLast && /* @__PURE__ */ jsx("div", { className: "eidos-timeline-connector" })
18
- ] }),
19
- /* @__PURE__ */ jsxs("div", { className: "eidos-timeline-content", children: [
20
- item.timestamp && /* @__PURE__ */ jsx("span", { className: "eidos-timeline-timestamp", children: item.timestamp }),
21
- /* @__PURE__ */ jsx("span", { className: "eidos-timeline-title", children: item.title }),
22
- item.description && /* @__PURE__ */ jsx("p", { className: "eidos-timeline-description", children: item.description })
23
- ] })
24
- ]
25
- },
26
- item.id
27
- );
28
- }) });
29
- Timeline.displayName = "Timeline";
30
-
31
- export {
32
- Timeline
33
- };
34
- //# sourceMappingURL=chunk-NEQXEVHM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Timeline/Timeline.component.tsx"],"sourcesContent":["import React from 'react';\nimport type { TimelineProps } from './Timeline.types';\n\nexport const Timeline: React.FC<TimelineProps> = ({ items, className = '' }) => (\n <ol className={['eidos-timeline', className].filter(Boolean).join(' ')} aria-label=\"Timeline\">\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const color = item.color ?? 'default';\n\n return (\n <li\n key={item.id}\n className={[\n 'eidos-timeline-item',\n `eidos-timeline-item--${color}`,\n !isLast && 'eidos-timeline-item--has-connector',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {/* Left: dot + connector */}\n <div className=\"eidos-timeline-track\" aria-hidden=\"true\">\n <div className=\"eidos-timeline-dot\">\n {item.icon && <span className=\"eidos-timeline-dot-icon\">{item.icon}</span>}\n </div>\n {!isLast && <div className=\"eidos-timeline-connector\" />}\n </div>\n\n {/* Right: content */}\n <div className=\"eidos-timeline-content\">\n {item.timestamp && <span className=\"eidos-timeline-timestamp\">{item.timestamp}</span>}\n <span className=\"eidos-timeline-title\">{item.title}</span>\n {item.description && <p className=\"eidos-timeline-description\">{item.description}</p>}\n </div>\n </li>\n );\n })}\n </ol>\n);\n\nTimeline.displayName = 'Timeline';\n"],"mappings":";AAqBU,SAEkB,KAFlB;AAlBH,IAAM,WAAoC,CAAC,EAAE,OAAO,YAAY,GAAG,MACxE,oBAAC,QAAG,WAAW,CAAC,kBAAkB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAAG,cAAW,YAChF,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,QAAM,SAAS,UAAU,MAAM,SAAS;AACxC,QAAM,QAAQ,KAAK,SAAS;AAE5B,SACE;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW;AAAA,QACT;AAAA,QACA,wBAAwB,KAAK;AAAA,QAC7B,CAAC,UAAU;AAAA,MACb,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MAGX;AAAA,6BAAC,SAAI,WAAU,wBAAuB,eAAY,QAChD;AAAA,8BAAC,SAAI,WAAU,sBACZ,eAAK,QAAQ,oBAAC,UAAK,WAAU,2BAA2B,eAAK,MAAK,GACrE;AAAA,UACC,CAAC,UAAU,oBAAC,SAAI,WAAU,4BAA2B;AAAA,WACxD;AAAA,QAGA,qBAAC,SAAI,WAAU,0BACZ;AAAA,eAAK,aAAa,oBAAC,UAAK,WAAU,4BAA4B,eAAK,WAAU;AAAA,UAC9E,oBAAC,UAAK,WAAU,wBAAwB,eAAK,OAAM;AAAA,UAClD,KAAK,eAAe,oBAAC,OAAE,WAAU,8BAA8B,eAAK,aAAY;AAAA,WACnF;AAAA;AAAA;AAAA,IAtBK,KAAK;AAAA,EAuBZ;AAEJ,CAAC,GACH;AAGF,SAAS,cAAc;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-O2BECFXJ.cjs","../src/components/Conversation/Chat.component.tsx","../src/components/Conversation/MessageComposer.component.tsx","../src/components/Conversation/Conversation.hooks.ts","../src/components/Conversation/Conversation.utils.ts","../src/components/Conversation/MessageItem.component.tsx","../src/components/Conversation/CommentThread.component.tsx"],"names":["useRef","useCallback","Fragment","Paperclip","jsx","jsxs"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-O2BECFXJ.cjs","sourcesContent":[null,"import React, { useCallback, useMemo } from 'react';\nimport { ArrowDown, MessagesSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { ChatProps, ConversationMessage } from './Conversation.types';\nimport {\n useLoadMoreOnScrollTop,\n useNewMessageIds,\n useScrollAnchor,\n useStickToBottom,\n} from './Conversation.hooks';\nimport { classNames, continuesRun, formatDateSeparator, isSameDay } from './Conversation.utils';\n\nconst ChatSkeleton: React.FC = () => (\n <div className=\"eidos-chat-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2, 3].map((row) => (\n <div\n key={row}\n className={classNames(\n 'eidos-chat-skeleton-row',\n row % 2 === 1 && 'eidos-chat-skeleton-row--own',\n )}\n >\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"rounded\" height={48} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A bottom-anchored message thread with a composer.\n *\n * Controlled and transport-agnostic: it renders the `messages` it is given and\n * reports intent through `onSend` / `onLoadMore`. It never fetches, so the\n * same array can be persisted and replayed verbatim.\n */\nexport const Chat: React.FC<ChatProps> = ({\n messages,\n currentUserId,\n onSend,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n showDateSeparators = true,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n typing,\n unreadFromId,\n placeholder = 'Write a message...',\n sendOnEnter = true,\n ariaLabel = 'Conversation',\n className = '',\n}) => {\n const { scrollRef, atBottom, scrollToBottom, handleScroll } = useStickToBottom(messages);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n useScrollAnchor(scrollRef, messages);\n\n const loadMoreOnScroll = useLoadMoreOnScrollTop(scrollRef, { hasMore, loadingMore, onLoadMore });\n\n const onScroll = useCallback(() => {\n handleScroll();\n loadMoreOnScroll();\n }, [handleScroll, loadMoreOnScroll]);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n // An id that isn't in the current page must not draw a divider above\n // nothing - it would float at the top of whatever happens to be loaded.\n const unreadIndex = useMemo(() => {\n if (!unreadFromId) return -1;\n return messages.findIndex((message) => message.id === unreadFromId);\n }, [messages, unreadFromId]);\n\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n\n const renderMessage = (message: ConversationMessage, index: number) => {\n const previous = messages[index - 1];\n const actions = readOnly ? undefined : messageActions?.(message);\n const quoted = message.parentId ? byId.get(message.parentId) : undefined;\n\n const nodes: React.ReactNode[] = [];\n\n if (showDateSeparators && (!previous || !isSameDay(previous.sentAt, message.sentAt))) {\n nodes.push(\n <li key={`date-${message.id}`} className=\"eidos-conversation-separator\">\n <span>{formatDateSeparator(message.sentAt)}</span>\n </li>,\n );\n }\n\n if (index === unreadIndex) {\n nodes.push(\n <li\n key={`unread-${message.id}`}\n className=\"eidos-conversation-separator eidos-conversation-separator--unread\"\n >\n <span>Unread messages</span>\n </li>,\n );\n }\n\n // A separator breaks a run: the message under it needs its own header.\n const grouped =\n groupConsecutive &&\n index !== unreadIndex &&\n nodes.length === 0 &&\n continuesRun(previous, message, groupWindowMinutes);\n\n nodes.push(\n <MessageItem\n key={message.id}\n message={message}\n layout=\"chat\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={grouped}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n quoted={quoted}\n formatTimestamp={formatTimestamp}\n />,\n );\n\n return nodes;\n };\n\n return (\n <div className={classNames('eidos-chat', className)}>\n <div\n ref={scrollRef}\n className=\"eidos-chat-scroll\"\n onScroll={onScroll}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n aria-busy={loading}\n aria-label={ariaLabel}\n tabIndex={0}\n >\n {hasMore && (\n <div className=\"eidos-chat-load-more\">\n {loadingMore ? (\n <span className=\"eidos-chat-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading earlier messages…\n </span>\n ) : (\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load earlier messages\n </Button>\n )}\n </div>\n )}\n\n {loading && <ChatSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessagesSquare />}\n title=\"No messages yet\"\n description={\n showComposer ? 'Send the first message to start the conversation.' : undefined\n }\n size=\"sm\"\n />\n ))}\n\n {!loading && messages.length > 0 && (\n <ol className=\"eidos-conversation-list\">{messages.map(renderMessage)}</ol>\n )}\n\n {typing && typing.length > 0 && (\n <div className=\"eidos-chat-typing\" aria-live=\"polite\">\n <span className=\"eidos-chat-typing-dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n {typing.length === 1\n ? `${typing[0].name} is typing…`\n : `${typing.length} people are typing…`}\n </div>\n )}\n </div>\n\n {!atBottom && messages.length > 0 && (\n <div className=\"eidos-chat-jump\">\n <Button\n size=\"sm\"\n variant=\"filled\"\n color=\"secondary\"\n preIcon={ArrowDown}\n onClick={() => scrollToBottom('smooth')}\n >\n Latest\n </Button>\n </div>\n )}\n\n {showComposer && (\n <div className=\"eidos-chat-composer\">\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n />\n </div>\n )}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </div>\n );\n};\n\nChat.displayName = 'Chat';\n","import React, { useCallback, useId, useRef, useState } from 'react';\nimport { Paperclip, Send, X } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Chip } from '../Chip';\nimport type { MessageComposerProps } from './Conversation.types';\nimport { useAutoGrow } from './Conversation.hooks';\nimport { classNames, formatFileSize } from './Conversation.utils';\n\n/**\n * The authoring half of a conversation - a growing text field, optional\n * attachments, and a submit control.\n *\n * Exported on its own so a consumer building a bespoke list can still reuse\n * the input, and used internally by both `Chat` and `CommentThread`.\n */\nexport const MessageComposer: React.FC<MessageComposerProps> = ({\n value,\n onChange,\n onSubmit,\n onCancel,\n placeholder = 'Write a message...',\n submitLabel = 'Send',\n sendOnEnter = false,\n parentId,\n replyingTo,\n allowAttachments = false,\n attachmentAccept,\n author,\n disabled = false,\n requireBody = true,\n autoFocus = false,\n maxLength,\n className = '',\n}) => {\n const [internalBody, setInternalBody] = useState('');\n const [attachments, setAttachments] = useState<File[]>([]);\n const [pending, setPending] = useState(false);\n\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileInputId = useId();\n\n const isControlled = value !== undefined;\n const body = isControlled ? value : internalBody;\n\n useAutoGrow(textareaRef, body);\n\n const setBody = useCallback(\n (next: string) => {\n if (!isControlled) setInternalBody(next);\n onChange?.(next);\n },\n [isControlled, onChange],\n );\n\n const canSubmit = !disabled && !pending && (!requireBody || body.trim().length > 0);\n\n const submit = useCallback(async () => {\n if (!canSubmit) return;\n\n const draft = { body: body.trim(), attachments, parentId };\n const result = onSubmit(draft);\n\n // A synchronous handler clears immediately; an async one keeps the draft\n // until it resolves, so a rejected send never loses what was typed.\n if (!(result instanceof Promise)) {\n setBody('');\n setAttachments([]);\n return;\n }\n\n setPending(true);\n try {\n await result;\n setBody('');\n setAttachments([]);\n } catch {\n // Intentionally swallowed: the draft stays put and the consumer owns\n // error reporting (a Snackbar, an Alert, ...). Rethrowing here would\n // surface as an unhandled rejection from a keystroke.\n } finally {\n setPending(false);\n }\n }, [canSubmit, body, attachments, parentId, onSubmit, setBody]);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!sendOnEnter) return;\n // Shift+Enter always inserts a newline; the modifier check also leaves\n // IME composition (which fires Enter to commit) alone.\n if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;\n event.preventDefault();\n void submit();\n };\n\n const addFiles = (list: FileList | null) => {\n if (!list?.length) return;\n setAttachments((current) => [...current, ...Array.from(list)]);\n // Reset so selecting the same file twice in a row still fires `change`.\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const removeAttachment = (index: number) =>\n setAttachments((current) => current.filter((_, i) => i !== index));\n\n return (\n <form\n className={classNames('eidos-message-composer', className)}\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {replyingTo && (\n <div className=\"eidos-composer-reply-hint\">\n <span>\n Replying to <strong>{replyingTo}</strong>\n </span>\n {onCancel && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={X}\n onClick={onCancel}\n tooltip=\"Cancel reply\"\n aria-label=\"Cancel reply\"\n />\n )}\n </div>\n )}\n\n <div className=\"eidos-composer-row\">\n {author && (\n <div className=\"eidos-composer-avatar\">\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n </div>\n )}\n\n <div className=\"eidos-composer-field\">\n <textarea\n ref={textareaRef}\n className=\"eidos-composer-input\"\n value={body}\n onChange={(event) => setBody(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled || pending}\n rows={1}\n maxLength={maxLength}\n // Opt-in and off by default. Only the reply composer sets it, where\n // the field appears in direct response to the user pressing Reply -\n // moving focus there is the expected outcome, not a hijack.\n autoFocus={autoFocus}\n aria-label={placeholder}\n />\n\n {attachments.length > 0 && (\n <ul className=\"eidos-composer-attachments\">\n {attachments.map((file, index) => (\n <li key={`${file.name}-${file.size}-${index}`}>\n <Chip\n size=\"sm\"\n variant=\"outlined\"\n color=\"secondary\"\n preIcon={Paperclip}\n onRemove={() => removeAttachment(index)}\n >\n {file.name}\n {file.size > 0 && ` (${formatFileSize(file.size)})`}\n </Chip>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n\n <div className=\"eidos-composer-actions\">\n <div className=\"eidos-composer-actions-start\">\n {allowAttachments && (\n <>\n <input\n ref={fileInputRef}\n id={fileInputId}\n type=\"file\"\n multiple\n accept={attachmentAccept}\n // Hidden from both the tab order and the accessibility\n // tree. It is a proxy: the visible button above opens the\n // file dialog via `.click()`, so this input is an\n // implementation detail, not a control.\n //\n // `visually-hidden` keeps an element *focusable* - that is\n // the point of it, for skip links and screen-reader-only\n // text - so a keyboard user landed on an invisible control\n // and met the same \"Attach files\" action twice. `FileUpload`\n // already gets this right with `display: none` +\n // `tabIndex={-1}`; these two had drifted from it.\n //\n // `aria-hidden` is safe here only *because* of the\n // `tabIndex={-1}` beside it - hiding a focusable element is\n // its own violation.\n className=\"eidos-composer-file-input\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => addFiles(event.target.files)}\n disabled={disabled || pending}\n />\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={Paperclip}\n tooltip=\"Attach files\"\n aria-label=\"Attach files\"\n disabled={disabled || pending}\n onClick={() => fileInputRef.current?.click()}\n />\n </>\n )}\n {sendOnEnter && (\n <span className=\"eidos-composer-hint\">\n <kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line\n </span>\n )}\n </div>\n\n <div className=\"eidos-composer-actions-end\">\n {onCancel && !replyingTo && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n onClick={onCancel}\n disabled={pending}\n >\n Cancel\n </Button>\n )}\n <Button\n type=\"submit\"\n size=\"sm\"\n color=\"primary\"\n preIcon={Send}\n loading={pending}\n disabled={!canSubmit}\n >\n {submitLabel}\n </Button>\n </div>\n </div>\n </form>\n );\n};\n\nMessageComposer.displayName = 'MessageComposer';\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { ConversationMessage } from './Conversation.types';\n\n/**\n * Ids that arrived *after* the first render.\n *\n * Seeded from the first commit so an initial page of history is never\n * animated - otherwise opening a thread fades forty messages in at once,\n * which reads as a glitch rather than as feedback. Only genuinely new ids get\n * the entrance class, and each one only once.\n */\nexport const useNewMessageIds = (\n messages: ConversationMessage[],\n enabled: boolean,\n): Set<string> => {\n const seen = useRef<Set<string> | null>(null);\n const [fresh, setFresh] = useState<Set<string>>(() => new Set());\n\n useEffect(() => {\n // First commit: everything already on screen counts as pre-existing.\n if (seen.current === null) {\n seen.current = new Set(messages.map((message) => message.id));\n return;\n }\n\n if (!enabled) {\n for (const message of messages) seen.current.add(message.id);\n return;\n }\n\n const added = messages.filter((message) => !seen.current?.has(message.id));\n if (!added.length) return;\n\n for (const message of added) seen.current.add(message.id);\n setFresh(new Set(added.map((message) => message.id)));\n }, [messages, enabled]);\n\n return fresh;\n};\n\ninterface StickToBottom {\n scrollRef: React.RefObject<HTMLDivElement | null>;\n /** The viewport is at (or near) the newest message. */\n atBottom: boolean;\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n handleScroll: () => void;\n}\n\n/** Distance from the bottom still counted as \"reading the newest message\". */\nconst BOTTOM_THRESHOLD_PX = 48;\n\n/**\n * Keeps a bottom-anchored list pinned to the newest message - but only while\n * the reader is already there.\n *\n * Scrolling someone back down while they are reading scrollback is the single\n * most common defect in chat UIs, so arrival of a new message only forces a\n * scroll when `atBottom` was already true. Otherwise the caller surfaces a\n * jump-to-latest affordance and leaves the viewport alone.\n */\nexport const useStickToBottom = (messages: ConversationMessage[]): StickToBottom => {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [atBottom, setAtBottom] = useState(true);\n // Read during a layout effect, so it must not be state - it has to be\n // correct for the commit that is happening right now, not the next render.\n const atBottomRef = useRef(true);\n const lastCount = useRef(messages.length);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {\n const node = scrollRef.current;\n if (!node) return;\n node.scrollTo({ top: node.scrollHeight, behavior });\n }, []);\n\n const handleScroll = useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n const next = distance <= BOTTOM_THRESHOLD_PX;\n atBottomRef.current = next;\n setAtBottom((current) => (current === next ? current : next));\n }, []);\n\n // Jump (not animate) to the bottom on first paint so a thread opens on its\n // newest message instead of visibly scrolling there.\n useLayoutEffect(() => {\n scrollToBottom('auto');\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount only\n }, []);\n\n useLayoutEffect(() => {\n if (messages.length > lastCount.current && atBottomRef.current) {\n scrollToBottom('smooth');\n }\n lastCount.current = messages.length;\n }, [messages.length, scrollToBottom]);\n\n return { scrollRef, atBottom, scrollToBottom, handleScroll };\n};\n\n/**\n * Preserves the reader's position when older messages are prepended.\n *\n * Without this, inserting a page above the viewport pushes the content the\n * user was reading down by the height of everything added, which reads as the\n * list jumping to a random place. Capturing `scrollHeight` before the DOM\n * mutation and restoring the delta in a *layout* effect corrects it before the\n * browser paints, so there is no visible shift.\n */\nexport const useScrollAnchor = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n messages: ConversationMessage[],\n): void => {\n const previous = useRef({ height: 0, firstId: '', count: 0 });\n\n useLayoutEffect(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n const firstId = messages[0]?.id ?? '';\n const before = previous.current;\n // A prepend is the only case where the first id changes while the list\n // grows - an append leaves it untouched, and a replacement shrinks it.\n const prepended =\n before.firstId !== '' && before.firstId !== firstId && messages.length > before.count;\n\n if (prepended) {\n node.scrollTop += node.scrollHeight - before.height;\n }\n\n previous.current = { height: node.scrollHeight, firstId, count: messages.length };\n }, [messages, scrollRef]);\n};\n\n/**\n * Fires `onLoadMore` once the reader approaches the top of the history.\n *\n * Latched on the way in and released only after they scroll back out of the\n * trigger zone, so a single pause near the top can't queue a dozen fetches.\n */\nexport const useLoadMoreOnScrollTop = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n {\n hasMore,\n loadingMore,\n onLoadMore,\n }: {\n hasMore?: boolean;\n loadingMore?: boolean;\n onLoadMore?: () => void;\n },\n thresholdPx = 80,\n): (() => void) => {\n const armed = useRef(true);\n\n return useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n if (node.scrollTop > thresholdPx) {\n armed.current = true;\n return;\n }\n\n if (!armed.current || !hasMore || loadingMore || !onLoadMore) return;\n armed.current = false;\n onLoadMore();\n }, [scrollRef, hasMore, loadingMore, onLoadMore, thresholdPx]);\n};\n\n/**\n * Grows a textarea to fit its content, up to `maxHeight`.\n *\n * Height is written to `style.height` directly rather than through a React\n * inline style: it is measured from the live DOM after every keystroke, so it\n * cannot be expressed as a class, and routing it through state would add a\n * render per character. Assigning the property imperatively also keeps it out\n * of the server-rendered `style` attribute that CSP's `style-src-attr` gates.\n */\nexport const useAutoGrow = (\n ref: React.RefObject<HTMLTextAreaElement | null>,\n value: string,\n maxHeight = 200,\n): void => {\n useLayoutEffect(() => {\n const node = ref.current;\n if (!node) return;\n\n node.style.height = 'auto';\n const next = Math.min(node.scrollHeight, maxHeight);\n node.style.height = `${next}px`;\n node.style.overflowY = node.scrollHeight > maxHeight ? 'auto' : 'hidden';\n }, [ref, value, maxHeight]);\n};\n","import type { ConversationMessage } from './Conversation.types';\n\n/**\n * Timestamp formatting goes through `Intl` rather than a date library on\n * purpose: `dayjs` is currently bundled only into the `DatePicker` chunk, and\n * pulling it in here would add it to every consumer importing a conversation.\n * `Intl` is built in and already locale-aware.\n */\n\nconst timeFormat = new Intl.DateTimeFormat(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n});\n\nconst dateFormat = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n});\n\nconst dateFormatSameYear = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n});\n\n/** Parses an ISO string, returning null rather than an `Invalid Date`. */\nexport const parseDate = (iso: string): Date | null => {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n};\n\nexport const formatMessageTime = (iso: string): string => {\n const date = parseDate(iso);\n return date ? timeFormat.format(date) : iso;\n};\n\nconst startOfDay = (date: Date): number =>\n new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n\nexport const isSameDay = (a: string, b: string): boolean => {\n const first = parseDate(a);\n const second = parseDate(b);\n if (!first || !second) return false;\n return startOfDay(first) === startOfDay(second);\n};\n\n/** `Today` / `Yesterday` / a full date, for the divider between days. */\nexport const formatDateSeparator = (iso: string): string => {\n const date = parseDate(iso);\n if (!date) return iso;\n\n const now = new Date();\n const dayDelta = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);\n\n if (dayDelta === 0) return 'Today';\n if (dayDelta === 1) return 'Yesterday';\n return date.getFullYear() === now.getFullYear()\n ? dateFormatSameYear.format(date)\n : dateFormat.format(date);\n};\n\nconst FILE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];\n\nexport const formatFileSize = (bytes: number): string => {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < FILE_UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n // One decimal below 10 (9.4 MB), none above it (24 MB) - the extra digit\n // stops carrying information once the integer part is two digits.\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${FILE_UNITS[unit]}`;\n};\n\n/**\n * Whether `message` continues a run started by `previous` - same author, same\n * day, and within the grouping window. A grouped message hides its avatar and\n * author line.\n */\nexport const continuesRun = (\n previous: ConversationMessage | undefined,\n message: ConversationMessage,\n windowMinutes: number,\n): boolean => {\n if (!previous) return false;\n if (previous.author.id !== message.author.id) return false;\n if (!isSameDay(previous.sentAt, message.sentAt)) return false;\n\n const before = parseDate(previous.sentAt);\n const after = parseDate(message.sentAt);\n if (!before || !after) return false;\n\n return Math.abs(after.getTime() - before.getTime()) <= windowMinutes * 60_000;\n};\n\nexport const sortBySentAt = (messages: ConversationMessage[]): ConversationMessage[] =>\n [...messages].sort((a, b) => {\n const first = parseDate(a.sentAt)?.getTime() ?? 0;\n const second = parseDate(b.sentAt)?.getTime() ?? 0;\n return first - second;\n });\n\nexport interface ThreadNode {\n message: ConversationMessage;\n replies: ConversationMessage[];\n}\n\n/**\n * Collapses a flat list into one level of nesting.\n *\n * Deeper chains are flattened onto the nearest **top-level** ancestor rather\n * than being dropped or nested further: indentation past one level is\n * unreadable on a phone, and silently discarding a reply-to-a-reply would lose\n * data the consumer stored. `replyingToName` on the rendered reply is what\n * preserves who was actually answered.\n *\n * A `parentId` pointing at a message that isn't present (paged out, deleted)\n * is treated as top-level, so nothing ever disappears from the list.\n */\nexport const buildThread = (messages: ConversationMessage[]): ThreadNode[] => {\n const byId = new Map(messages.map((message) => [message.id, message]));\n\n /** Walks up to the outermost ancestor, guarding against a parentId cycle. */\n const rootIdOf = (message: ConversationMessage): string => {\n const seen = new Set<string>([message.id]);\n let current = message;\n\n while (current.parentId) {\n const parent = byId.get(current.parentId);\n if (!parent || seen.has(parent.id)) break;\n seen.add(parent.id);\n current = parent;\n }\n\n return current.id;\n };\n\n const nodes = new Map<string, ThreadNode>();\n const roots: ThreadNode[] = [];\n\n for (const message of messages) {\n if (message.parentId && byId.has(message.parentId)) continue;\n const node: ThreadNode = { message, replies: [] };\n nodes.set(message.id, node);\n roots.push(node);\n }\n\n for (const message of messages) {\n if (!message.parentId || !byId.has(message.parentId)) continue;\n nodes.get(rootIdOf(message))?.replies.push(message);\n }\n\n for (const node of nodes.values()) {\n node.replies = sortBySentAt(node.replies);\n }\n\n return roots;\n};\n\n/** Name of the author a reply was addressed to, when it isn't its own parent. */\nexport const replyTargetName = (\n message: ConversationMessage,\n byId: Map<string, ConversationMessage>,\n rootId: string,\n): string | undefined => {\n if (!message.parentId || message.parentId === rootId) return undefined;\n return byId.get(message.parentId)?.author.name;\n};\n\n/** Trims a quoted excerpt so a long parent message can't dominate the reply. */\nexport const excerpt = (body: string, maxLength = 120): string => {\n const flat = body.replace(/\\s+/g, ' ').trim();\n return flat.length > maxLength ? `${flat.slice(0, maxLength - 1)}…` : flat;\n};\n\nexport const classNames = (...values: Array<string | false | null | undefined>): string =>\n values.filter(Boolean).join(' ');\n","import React from 'react';\nimport { AlertCircle, MoreHorizontal, Paperclip, RotateCw } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Menu } from '../Menu';\nimport { Pill } from '../Pill';\nimport { Spinner } from '../Spinner';\nimport type { ConversationMessage, MessageAttachment } from './Conversation.types';\nimport type { MenuItemType } from '../Menu';\nimport { classNames, excerpt, formatFileSize, formatMessageTime } from './Conversation.utils';\n\nexport interface MessageItemProps {\n message: ConversationMessage;\n /** 'chat' right-aligns own messages into bubbles; 'comment' renders a flat card. */\n layout: 'chat' | 'comment';\n isOwn: boolean;\n /** Continues a run from the same author - hides the avatar and author line. */\n grouped: boolean;\n /** Plays the entrance animation. */\n isNew: boolean;\n actions?: MenuItemType[];\n footer?: React.ReactNode;\n body?: React.ReactNode;\n onRetry?: () => void;\n /** Author of the message being answered, for a flattened deep reply. */\n replyingToName?: string;\n /** Quoted parent, rendered above the body in chat layout. */\n quoted?: ConversationMessage;\n /** Reply affordance; omitted entirely when the viewer may not reply. */\n onReply?: () => void;\n formatTimestamp?: (iso: string) => string;\n}\n\nconst AttachmentList: React.FC<{ attachments: MessageAttachment[] }> = ({ attachments }) => (\n <ul className=\"eidos-message-attachments\">\n {attachments.map((attachment) => {\n const label = (\n <>\n <Paperclip aria-hidden=\"true\" />\n <span className=\"eidos-message-attachment-name\">{attachment.name}</span>\n {attachment.size !== undefined && (\n <span className=\"eidos-message-attachment-size\">{formatFileSize(attachment.size)}</span>\n )}\n </>\n );\n\n return (\n <li key={attachment.id} className=\"eidos-message-attachment\">\n {attachment.url ? (\n <a\n href={attachment.url}\n className=\"eidos-message-attachment-link\"\n target=\"_blank\"\n rel=\"noreferrer\"\n download={attachment.name}\n >\n {label}\n </a>\n ) : (\n <span className=\"eidos-message-attachment-link\">{label}</span>\n )}\n </li>\n );\n })}\n </ul>\n);\n\nexport const MessageItem: React.FC<MessageItemProps> = ({\n message,\n layout,\n isOwn,\n grouped,\n isNew,\n actions,\n footer,\n body,\n onRetry,\n replyingToName,\n quoted,\n onReply,\n formatTimestamp,\n}) => {\n const { author, status = 'sent' } = message;\n const timestamp = formatTimestamp\n ? formatTimestamp(message.sentAt)\n : formatMessageTime(message.sentAt);\n // An empty action list must not leave a dead trigger behind - see the\n // permissions note in Conversation.types.ts.\n const hasActions = Boolean(actions && actions.length > 0);\n\n // One placement per layout, rather than one per message.\n //\n // `chat` puts the time at the foot of the bubble, where a grouped message\n // already had it; it used to appear in the header for the first message of a\n // group and inside the bubble for the rest, so a single thread showed the\n // same fact in two places. `comment` keeps it in the header, where the row is\n // full width and has space for it.\n const timeInBubble = layout === 'chat';\n\n return (\n <li\n className={classNames(\n 'eidos-message',\n `eidos-message--${layout}`,\n isOwn && 'eidos-message--own',\n grouped && 'eidos-message--grouped',\n status !== 'sent' && `eidos-message--${status}`,\n isNew && 'eidos-message--enter',\n )}\n data-message-id={message.id}\n >\n <div className=\"eidos-message-avatar\">\n {!grouped && (\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n )}\n </div>\n\n <div className=\"eidos-message-main\">\n {!grouped && (\n <div className=\"eidos-message-header\">\n <span className=\"eidos-message-author\">{author.name}</span>\n {author.role && (\n <Pill size=\"sm\" variant=\"outlined\" color=\"secondary\">\n {author.role}\n </Pill>\n )}\n {!timeInBubble && (\n <time className=\"eidos-message-time\" dateTime={message.sentAt}>\n {timestamp}\n </time>\n )}\n {message.edited && <span className=\"eidos-message-edited\">edited</span>}\n </div>\n )}\n\n <div className=\"eidos-message-bubble\">\n {quoted && (\n <blockquote className=\"eidos-message-quote\">\n <span className=\"eidos-message-quote-author\">{quoted.author.name}</span>\n <span className=\"eidos-message-quote-body\">{excerpt(quoted.body)}</span>\n </blockquote>\n )}\n\n {replyingToName && (\n <span className=\"eidos-message-reply-target\">Replying to {replyingToName}</span>\n )}\n\n <div className=\"eidos-message-body\">{body ?? message.body}</div>\n\n {message.attachments && message.attachments.length > 0 && (\n <AttachmentList attachments={message.attachments} />\n )}\n\n {(timeInBubble || grouped) && (\n <time\n className=\"eidos-message-time eidos-message-time--inline\"\n dateTime={message.sentAt}\n >\n {timestamp}\n </time>\n )}\n </div>\n\n {status === 'sending' && (\n <span className=\"eidos-message-status\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Sending…\n </span>\n )}\n\n {status === 'failed' && (\n <span className=\"eidos-message-status eidos-message-status--failed\">\n <AlertCircle aria-hidden=\"true\" />\n Not sent\n {onRetry && (\n <Button variant=\"text\" color=\"danger\" size=\"sm\" preIcon={RotateCw} onClick={onRetry}>\n Retry\n </Button>\n )}\n </span>\n )}\n\n {footer && <div className=\"eidos-message-footer\">{footer}</div>}\n\n {onReply && (\n <div className=\"eidos-message-reply\">\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onReply}>\n Reply\n </Button>\n </div>\n )}\n </div>\n\n {hasActions && (\n <div className=\"eidos-message-actions\">\n <Menu\n items={actions ?? []}\n trigger={\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={MoreHorizontal}\n aria-label={`Actions for ${author.name}'s message`}\n />\n }\n />\n </div>\n )}\n </li>\n );\n};\n\nMessageItem.displayName = 'MessageItem';\n","import React, { useCallback, useMemo, useState } from 'react';\nimport { MessageSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { CommentThreadProps, ConversationMessage, MessageDraft } from './Conversation.types';\nimport { useNewMessageIds } from './Conversation.hooks';\nimport {\n buildThread,\n classNames,\n continuesRun,\n replyTargetName,\n sortBySentAt,\n} from './Conversation.utils';\n\nconst CommentSkeleton: React.FC = () => (\n <div className=\"eidos-comment-thread-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2].map((row) => (\n <div key={row} className=\"eidos-comment-thread-skeleton-row\">\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"text\" lines={2} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A comment section: document flow, no internal scroll container, one level of\n * nesting.\n *\n * Shares its data model and message rendering with `Chat`, so history captured\n * in one can be displayed in the other. Unlike `Chat` it has no scroll region\n * of its own - it grows with the page and pages *downward* via `onLoadMore`.\n */\nexport const CommentThread: React.FC<CommentThreadProps> = ({\n messages,\n currentUserId,\n onSend,\n onReply,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n order = 'oldest-first',\n composerPosition = 'bottom',\n placeholder = 'Add a comment...',\n sendOnEnter = false,\n title,\n ariaLabel = 'Comments',\n className = '',\n}) => {\n const [replyingToId, setReplyingToId] = useState<string | null>(null);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n const roots = useMemo(() => {\n const thread = buildThread(messages);\n const sorted = sortBySentAt(thread.map((node) => node.message));\n const ordered = order === 'newest-first' ? [...sorted].reverse() : sorted;\n const nodeById = new Map(thread.map((node) => [node.message.id, node]));\n // Replies always read oldest-first, even when roots are reversed - a\n // conversation under a single comment is chronological by nature.\n return ordered.map((message) => nodeById.get(message.id)!);\n }, [messages, order]);\n\n const canReply = !readOnly && Boolean(onReply);\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n const total = messages.length;\n\n const handleReply = useCallback(\n async (draft: MessageDraft) => {\n await onReply?.(draft);\n setReplyingToId(null);\n },\n [onReply],\n );\n\n const renderComment = (\n message: ConversationMessage,\n options: { rootId: string; previous?: ConversationMessage; isReply: boolean },\n ) => {\n const actions = readOnly ? undefined : messageActions?.(message);\n\n return (\n <MessageItem\n key={message.id}\n message={message}\n layout=\"comment\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={groupConsecutive && continuesRun(options.previous, message, groupWindowMinutes)}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n replyingToName={\n options.isReply ? replyTargetName(message, byId, options.rootId) : undefined\n }\n onReply={canReply ? () => setReplyingToId(message.id) : undefined}\n formatTimestamp={formatTimestamp}\n />\n );\n };\n\n const composer = showComposer && (\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n submitLabel=\"Comment\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n className=\"eidos-comment-thread-composer\"\n />\n );\n\n return (\n <section className={classNames('eidos-comment-thread', className)} aria-label={ariaLabel}>\n {(title || total > 0) && (\n <header className=\"eidos-comment-thread-header\">\n {title && <h3 className=\"eidos-comment-thread-title\">{title}</h3>}\n {total > 0 && (\n <span className=\"eidos-comment-thread-count\">\n {total} {total === 1 ? 'comment' : 'comments'}\n </span>\n )}\n </header>\n )}\n\n {composerPosition === 'top' && composer}\n\n {loading && <CommentSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessageSquare />}\n title=\"No comments yet\"\n description={showComposer ? 'Start the discussion by leaving a comment.' : undefined}\n size=\"sm\"\n />\n ))}\n\n {!loading && roots.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-list\" aria-busy={loadingMore}>\n {roots.map((node) => (\n <li key={node.message.id} className=\"eidos-comment-thread-node\">\n <ol className=\"eidos-conversation-list\">\n {renderComment(node.message, { rootId: node.message.id, isReply: false })}\n </ol>\n\n {node.replies.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-replies\">\n {node.replies.map((reply, index) =>\n renderComment(reply, {\n rootId: node.message.id,\n previous: node.replies[index - 1],\n isReply: true,\n }),\n )}\n </ol>\n )}\n\n {replyingToId &&\n (replyingToId === node.message.id ||\n node.replies.some((reply) => reply.id === replyingToId)) && (\n <div className=\"eidos-comment-thread-reply-composer\">\n <MessageComposer\n onSubmit={handleReply}\n onCancel={() => setReplyingToId(null)}\n parentId={replyingToId}\n replyingTo={byId.get(replyingToId)?.author.name}\n placeholder=\"Write a reply...\"\n submitLabel=\"Reply\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n autoFocus\n />\n </div>\n )}\n </li>\n ))}\n </ol>\n )}\n\n {hasMore && (\n <div className=\"eidos-comment-thread-load-more\">\n {loadingMore ? (\n <span className=\"eidos-comment-thread-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading more comments…\n </span>\n ) : (\n <Button variant=\"outlined\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load more comments\n </Button>\n )}\n </div>\n )}\n\n {composerPosition === 'bottom' && composer}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </section>\n );\n};\n\nCommentThread.displayName = 'CommentThread';\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Header/Header.component.tsx","../src/components/Header/IdentityHeader.component.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { HeaderActionProps, HeaderProps } from './Header.types';\nimport './Header.scss';\nimport { Button } from '../Button';\nimport { Popover } from '../Popover';\nimport { renderIcon } from '../../utils';\n\nexport const Header: React.FC<HeaderProps> = ({\n title,\n subtitle,\n media,\n meta,\n variant = 'default',\n actions,\n collapseActionsBelow = 640,\n actionsVisibleWhenCollapsed = 1,\n className = '',\n}) => {\n const classes = ['eidos-header', `eidos-header--${variant}`, className].filter(Boolean).join(' ');\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isNarrow, setIsNarrow] = useState(false);\n const [overflowOpen, setOverflowOpen] = useState(false);\n\n // Measures the header's own box rather than the viewport, so the collapse\n // point matches the container queries in Header.scss - a viewport query\n // would disagree with them by the width of PageLayout's navigation rail,\n // which is itself collapsible. The 1px hero border makes this border-box\n // reading differ from the container query's content-box one by 2px; that is\n // far below any threshold worth caring about, and using one measurement for\n // both the initial pass and the observer keeps it to a single code path.\n const measure = useCallback(() => {\n const element = rootRef.current;\n if (!element) return;\n setIsNarrow(element.getBoundingClientRect().width <= collapseActionsBelow);\n }, [collapseActionsBelow]);\n\n // `useLayoutEffect` for the first measurement specifically: ResizeObserver\n // delivers its initial callback asynchronously, so relying on it alone\n // would paint the full action row for a frame before collapsing it. Reading\n // the box synchronously here collapses before paint instead. Nothing runs\n // during SSR, so the server renders the uncollapsed row and hydration\n // matches - the same client-only posture Navigation's `collapseBelow` has.\n useLayoutEffect(() => {\n if (!collapseActionsBelow) {\n setIsNarrow(false);\n return;\n }\n\n measure();\n\n const element = rootRef.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [collapseActionsBelow, measure]);\n\n const allActions = actions ?? [];\n const keptCount = Math.max(0, actionsVisibleWhenCollapsed);\n const isCollapsed = isNarrow && !!collapseActionsBelow && allActions.length > keptCount;\n\n // The *last* actions stay inline: a primary call to action is conventionally\n // rightmost, which is also how Header's own stories order them.\n const overflowActions = isCollapsed ? allActions.slice(0, allActions.length - keptCount) : [];\n const inlineActions = isCollapsed ? allActions.slice(allActions.length - keptCount) : allActions;\n\n // A portalled popover would otherwise stay on screen after the header grows\n // back past the threshold and its trigger stops being rendered.\n useEffect(() => {\n if (!isCollapsed && overflowOpen) setOverflowOpen(false);\n }, [isCollapsed, overflowOpen]);\n\n const renderOverflowAction = (action: HeaderActionProps, index: number) => {\n const handleClick: HeaderActionProps['onClick'] = (event) => {\n action.onClick?.(event);\n setOverflowOpen(false);\n };\n\n // An icon-only action has no visible label, which is fine in a horizontal\n // row of small buttons and useless in a vertical list. Its tooltip is\n // already the label, so promote it rather than showing a bare glyph.\n if (action.icon && !action.children && action.tooltip) {\n const { icon, tooltip, ...rest } = action;\n return (\n <Button key={index} {...rest} size=\"md\" preIcon={icon} onClick={handleClick}>\n {tooltip}\n </Button>\n );\n }\n\n return <Button key={index} {...action} size=\"md\" onClick={handleClick} />;\n };\n\n const titles = (\n <div className=\"eidos-header__titles\">\n <h1 className=\"eidos-header__title\">{title}</h1>\n {subtitle && <span className=\"eidos-header__subtitle\">{subtitle}</span>}\n {!!meta?.length && (\n <div className=\"eidos-header__meta\">\n {meta.map((item, index) => (\n <span key={index} className=\"eidos-header__meta-item\">\n {item.icon && renderIcon(item.icon, 'eidos-header__meta-icon')}\n {item.label && <span className=\"eidos-header__meta-label\">{item.label}</span>}\n <span className=\"eidos-header__meta-value\">{item.value}</span>\n </span>\n ))}\n </div>\n )}\n </div>\n );\n\n return (\n <div className={classes} ref={rootRef}>\n <div className=\"eidos-header__content\">\n {/* The `__lead` wrapper only exists when there is media to group with\n the titles. Media and titles need a tighter gap than titles and\n actions do, and a flex container has a single gap - but rendering\n the wrapper unconditionally would change the markup for every\n existing consumer, and the `eidos-*` class names are documented as\n targetable. */}\n {media ? (\n <div className=\"eidos-header__lead\">\n <div className=\"eidos-header__media\">{media}</div>\n {titles}\n </div>\n ) : (\n titles\n )}\n <div className=\"eidos-header__actions\">\n {overflowActions.length > 0 && (\n <Popover\n isOpen={overflowOpen}\n onOpenChange={setOverflowOpen}\n placement=\"bottom\"\n maxWidth={260}\n // Popover puts `className` on its trigger wrapper, which is the\n // element that actually becomes a flex item of `__actions`.\n className=\"eidos-header__overflow-trigger\"\n trigger={\n // No `aria-haspopup`/`aria-expanded` here: Popover already\n // sets both on the wrapper it renders around this button.\n <Button\n icon=\"ellipsis\"\n size=\"md\"\n variant=\"outlined\"\n color=\"secondary\"\n aria-label={`${overflowActions.length} more actions`}\n />\n }\n >\n <div className=\"eidos-header__overflow-actions\">\n {overflowActions.map(renderOverflowAction)}\n </div>\n </Popover>\n )}\n {inlineActions.map((action, index) => (\n <Button key={index} size=\"md\" {...action} />\n ))}\n </div>\n </div>\n </div>\n );\n};\n","import React from 'react';\nimport type { IdentityHeaderProps } from './IdentityHeader.types';\nimport { Header } from './Header.component';\nimport { Avatar } from '../Avatar';\nimport { devWarn } from '../../utils';\n\n/**\n * `Header` preconfigured for the \"this page is about one person or entity\"\n * shape - an avatar beside the name, supporting facts below it, actions on\n * the end - which account and profile pages share. Settings-style pages want\n * a plain `Header` instead; they have no identity to lead with.\n *\n * Deliberately thin: it pins `variant=\"hero\"`, types the avatar so consumers\n * don't have to import `Avatar` to fill the slot, and gives the shape a name\n * worth discovering. Everything else is `Header`.\n */\nexport const IdentityHeader: React.FC<IdentityHeaderProps> = ({\n avatar,\n media,\n variant = 'hero',\n className = '',\n ...rest\n}) => {\n if (avatar && media) {\n devWarn(\n 'identity-header-avatar-and-media',\n 'IdentityHeader received both `avatar` and `media`. `media` takes precedence - drop `avatar` to silence this.',\n );\n }\n\n const resolvedMedia = media ?? (avatar ? <Avatar size=\"lg\" {...avatar} /> : undefined);\n\n return (\n <Header\n {...rest}\n variant={variant}\n media={resolvedMedia}\n className={['eidos-identity-header', className].filter(Boolean).join(' ')}\n />\n );\n};\n"],"mappings":"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","names":["jsx"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/renderIcon.tsx","../src/utils/devWarn.ts","../src/utils/dualModifier.ts","../src/utils/useDialogFocus.ts","../src/utils/useIsClient.ts"],"sourcesContent":["import React from 'react';\nimport { icons } from 'lucide-react';\n\nexport type IconType = React.ComponentType<{ className?: string }> | string;\n\n/**\n * Helper function to render icons dynamically\n * Supports both React components (Lucide, MUI, etc.) and string-based icon names\n *\n * @param icon - Icon component or string name (e.g., \"heart\" or \"arrow-right\")\n * @param className - Optional CSS class name to apply to the icon\n * @returns React node representing the icon, or null if no icon provided\n *\n * @example\n * // Component-based icon\n * import { Heart } from 'lucide-react';\n * renderIcon(Heart, 'my-icon-class');\n *\n * @example\n * // String-based Lucide icon (kebab-case converted to PascalCase)\n * renderIcon('heart', 'my-icon-class');\n * renderIcon('arrow-right', 'my-icon-class');\n *\n * @example\n * // String-based CSS class (for Font Awesome, Remixicon, etc.)\n * renderIcon('fas fa-heart', 'my-icon-class');\n */\nexport const renderIcon = (icon: IconType | undefined, className?: string): React.ReactNode => {\n if (!icon) return null;\n\n if (typeof icon === 'string') {\n // Convert kebab-case to PascalCase for Lucide icons\n const iconName = icon\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n\n // Try to find the icon in lucide-react\n const LucideIcon = icons[iconName as keyof typeof icons] as\n React.ComponentType<{ className?: string }> | undefined;\n\n if (LucideIcon) {\n return <LucideIcon className={className} aria-hidden=\"true\" />;\n }\n\n // Fallback: render as a CSS class (for Font Awesome, Remixicon, etc.)\n return <i className={`${icon} ${className || ''}`} aria-hidden=\"true\" />;\n }\n\n // It's a React component\n const IconComponent = icon;\n return <IconComponent className={className} aria-hidden=\"true\" />;\n};\n","/**\n * Logs a misconfiguration warning, once per `key`, and never in production.\n *\n * Deliberately reads `process.env.NODE_ENV` off `globalThis` behind a cast\n * rather than referencing `process` directly: this is a browser library, and\n * a bare `process` reference fails the `tsup` dts build (its worker resolves\n * without node types, so `npm run typecheck` passing is not enough) as well\n * as throwing at runtime anywhere a bundler hasn't substituted it. The\n * optional chaining covers every case - no `process`, no `env`, no value.\n *\n * Because the check is dynamic rather than a statically replaceable\n * `process.env.NODE_ENV` expression, the message text stays in production\n * bundles; only the logging is skipped. That is the trade for not shipping a\n * `process` reference at all, and these strings are small.\n */\nconst warned = new Set<string>();\n\nexport function devWarn(key: string, message: string): void {\n const nodeEnv = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV;\n if (nodeEnv === 'production') return;\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[eidos-ui] ${message}`);\n}\n","/**\n * Emits a BEM modifier in both its canonical kebab-case spelling and a legacy\n * camelCase one.\n *\n * The library shipped two conventions for the same idea: eight components\n * used `--fullWidth` while four used `--full-width`, and `Tabs` had\n * `--hideScrollbar`. Kebab-case is the canonical form - every block name\n * (`eidos-date-picker-trigger`) and every design token (`--font-size-sm`) is\n * already kebab, so a camelCase modifier was inconsistent with the very\n * selector it hung off.\n *\n * Renaming outright would be a **breaking** change: `README.md` presents the\n * `eidos-*` class names as things consumers may target, so a stylesheet out\n * there is matching the old spelling. Emitting both keeps every existing\n * selector working while new code has one spelling to learn.\n *\n * Deliberately a helper rather than a duplicated string literal at each call\n * site: two near-identical classes in a `className` read as a copy-paste slip\n * and invite exactly the \"tidy up\" that would silently break those consumer\n * stylesheets. It also makes dropping the aliases at the next major a change\n * to this one file rather than an edit in every component.\n *\n * Internal only - deliberately not re-exported from `src/index.ts`.\n *\n * @example\n * dualModifier('eidos-input', 'full-width', 'fullWidth');\n * // 'eidos-input--full-width eidos-input--fullWidth'\n */\nexport const dualModifier = (block: string, canonical: string, legacy: string): string =>\n `${block}--${canonical} ${block}--${legacy}`;\n\n/** `dualModifier` preset for the library's most widespread split modifier. */\nexport const fullWidthModifier = (block: string): string =>\n dualModifier(block, 'full-width', 'fullWidth');\n","import { useEffect, type RefObject } from 'react';\n\n/**\n * Focus management for a dialog: move focus in on open, optionally trap Tab\n * inside it, and restore focus to the opener on close.\n *\n * Named for the job rather than the mechanism because the trap is optional.\n * `Popover` declares `aria-modal=\"false\"` - an honest non-modal dialog - so\n * trapping Tab in it would be wrong, but it still needs the other two: its\n * panel is portaled to `document.body`, so without moving focus in, Tab from\n * the trigger goes to whatever follows it in the page and the panel's own\n * controls are unreachable by keyboard entirely.\n *\n * ## Why this exists\n *\n * `Modal`, `Drawer` and `CommandPalette` all rendered\n * `role=\"dialog\" aria-modal=\"true\"` with no focus management whatsoever. That\n * attribute is a **promise to assistive technology that the rest of the page\n * is inert**, and it was false: measured in a browser, focus never entered the\n * dialog, and Tab moved to the page behind the scrim on the first press\n * (Drawer on the second). Drawer additionally dropped focus onto `<body>` when\n * it closed, so a keyboard user was returned to the top of the document.\n *\n * That is WCAG 2.4.3 Focus Order (Level A), and it is invisible to axe - a\n * static snapshot of the DOM looks perfectly correct.\n *\n * ## Nesting\n *\n * Traps register on a module-level stack and only the innermost one acts, so a\n * dialog opened from a dialog behaves correctly. Without this, both listeners\n * fire for the same Tab and fight over where focus lands.\n *\n * ## What this does *not* do\n *\n * It does not mark the background `inert`. A trap plus `aria-modal` is the\n * widely-supported approach and is what screen readers act on; `inert` on\n * every sibling of the portal root would be stronger, but it needs the\n * component to know about DOM it does not own. Worth revisiting if a\n * consumer reports a screen reader escaping the dialog in browse mode.\n */\n\n/**\n * Deliberately not `[tabindex]` in general - that would match `tabindex=\"-1\"`,\n * which is programmatically focusable but explicitly removed from the Tab\n * sequence, so including it would make Tab stop somewhere the user cannot\n * reach by tabbing in the first place.\n */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'details > summary:first-of-type',\n 'iframe',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]:not([tabindex^=\"-\"])',\n].join(',');\n\n/**\n * `offsetParent === null` is the usual visibility check and is wrong here: it\n * is also null for `position: fixed` elements, which is exactly what a modal\n * is. `getClientRects()` is empty only when the element genuinely does not\n * render.\n */\nconst isVisible = (element: HTMLElement): boolean =>\n element.getClientRects().length > 0 && getComputedStyle(element).visibility !== 'hidden';\n\nconst getFocusable = (container: HTMLElement): HTMLElement[] =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (element) => isVisible(element) && element.closest('[aria-hidden=\"true\"]') === null,\n );\n\n/** Innermost trap last. Only the last entry responds to Tab. */\nconst trapStack: HTMLElement[] = [];\n\nexport interface UseDialogFocusOptions {\n /** Focused when the trap activates. Defaults to the first focusable child. */\n initialFocus?: RefObject<HTMLElement | null>;\n /**\n * Whether to return focus to whatever had it when the trap activated.\n * Defaults to `true`; pass `false` when the caller restores focus itself.\n */\n restoreFocus?: boolean;\n /**\n * Whether Tab is confined to the container. Defaults to `true`.\n *\n * Set `false` for a non-modal dialog (`aria-modal=\"false\"`), where the rest\n * of the page stays available and trapping would strand the user.\n */\n trapTab?: boolean;\n}\n\nexport const useDialogFocus = (\n active: boolean,\n containerRef: RefObject<HTMLElement | null>,\n { initialFocus, restoreFocus = true, trapTab = true }: UseDialogFocusOptions = {},\n): void => {\n useEffect(() => {\n const container = containerRef.current;\n if (!active || !container) return;\n\n // Captured before focus moves. The caller must only flip `active` to true\n // once the container is in the DOM - for the animated overlays that means\n // `isOpen && isMounted`, since `isMounted` is set in an effect one render\n // later and the container does not exist before it.\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n trapStack.push(container);\n\n // Moving focus in is retried across a few frames rather than attempted\n // once.\n //\n // These overlays animate open, and `.focus()` on an element that is still\n // `visibility: hidden` is a silent no-op - it does not throw, it just\n // leaves focus where it was. `Modal` sets its `--is-open` class two\n // animation frames after mounting, and the computed style has not caught\n // up at the moment the effect runs, so a single attempt focused nothing\n // and the trap looked broken.\n //\n // Retrying is better than keying off any particular animation: the hook\n // stays ignorant of each component's transition duration, and simply\n // stops as soon as focus has actually landed inside.\n const MAX_FRAMES = 10;\n let frame = 0;\n let attempts = 0;\n\n const tryFocus = () => {\n const target = initialFocus?.current ?? getFocusable(container)[0];\n if (target) {\n target.focus();\n } else {\n // A dialog with nothing focusable in it still must not leave focus on\n // the page behind, so the container takes it. `tabindex=\"-1\"` keeps\n // it out of the Tab sequence while allowing `.focus()`.\n if (!container.hasAttribute('tabindex')) container.setAttribute('tabindex', '-1');\n container.focus();\n }\n\n if (!container.contains(document.activeElement) && attempts < MAX_FRAMES) {\n attempts += 1;\n frame = requestAnimationFrame(tryFocus);\n }\n };\n\n tryFocus();\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n // Only the innermost trap acts.\n if (trapStack[trapStack.length - 1] !== container) return;\n\n // Re-queried on every Tab rather than cached: dialog contents change\n // (a disabled Confirm button becoming enabled, a list filtering down),\n // and a stale list sends focus to an element that is no longer there.\n const focusable = getFocusable(container);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeElement = document.activeElement as HTMLElement | null;\n\n // Focus outside the container - e.g. a click landed on the backdrop.\n // Pull it back in rather than letting Tab continue through the page.\n if (!activeElement || !container.contains(activeElement)) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n return;\n }\n\n if (event.shiftKey && activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n };\n\n if (trapTab) document.addEventListener('keydown', onKeyDown, true);\n\n return () => {\n cancelAnimationFrame(frame);\n document.removeEventListener('keydown', onKeyDown, true);\n\n const index = trapStack.lastIndexOf(container);\n if (index !== -1) trapStack.splice(index, 1);\n\n // Restore only if focus still belongs to the closing dialog.\n //\n // Two cases count as \"belongs\":\n //\n // 1. Focus is still inside the container.\n // 2. Focus has been lost to `<body>`. When the overlay hides, the\n // browser blurs whatever was focused inside it, so by the time this\n // cleanup runs `activeElement` is often already `<body>` - checking\n // only case 1 meant `CommandPalette` never restored at all.\n //\n // Anything else means something took focus deliberately - a confirm\n // action moving to a new region, say - and stealing it back would be\n // worse than doing nothing.\n const active = document.activeElement;\n const focusIsOurs = container.contains(active) || active === document.body || active === null;\n\n if (restoreFocus && focusIsOurs && previouslyFocused?.isConnected) {\n previouslyFocused.focus();\n }\n };\n }, [active, containerRef, initialFocus, restoreFocus, trapTab]);\n};\n","import { useEffect, useState } from 'react';\n\n/**\n * `false` on the server and on the very first client render, `true` from the\n * first effect onwards.\n *\n * ## Why this exists\n *\n * Every overlay in this library renders through `createPortal(…,\n * document.body)`. A portal is a client-only construct: there is no\n * `document` during `renderToString`, so any overlay that is *already open on\n * its first render* does not merely render oddly - it throws, and takes the\n * whole server render down with it. Measured against the built package:\n *\n * ```\n * renderToString(<Modal isOpen>) ReferenceError: document is not defined\n * renderToString(<Dropdown defaultOpen>) ReferenceError: DOMRect is not defined\n * renderToString(<Select autoOpen>) ReferenceError: DOMRect is not defined\n * ```\n *\n * `Modal`, `Drawer` and `CommandPalette` each keep an `isMounted` flag, which\n * looks like it would prevent this and does not: it is initialised to\n * `useState(isOpen)`, so it is already `true` on the first render. It exists\n * to sequence the open/close *animation*, not to defer the portal.\n *\n * With this hook the server renders nothing for the panel, and the portal\n * appears on the client after mount.\n *\n * ## Why a state flag rather than `typeof document !== 'undefined'`\n *\n * That check is `true` during hydration, so the client's first render would\n * produce the portal while the server's produced nothing - a hydration\n * mismatch, which React resolves by discarding and re-rendering the subtree.\n * A state flag set in an effect makes the first client render agree with the\n * server by construction, and the portal arrive one render later.\n *\n * ## Scope\n *\n * Only for components that can be open on their first render - `Dropdown`\n * (`defaultOpen`), `Popover`, `Modal`, `Drawer`, `CommandPalette`. `Tooltip`,\n * `ContextMenu` and `Snackbar` can only open in response to an event, which\n * cannot happen during a server render, so they need nothing. `check-ssr.js`\n * asserts all of them either way.\n */\nexport const useIsClient = (): boolean => {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n return isClient;\n};\n"],"mappings":"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","names":["active","useEffect"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/Conversation/Chat.component.tsx","../src/components/Conversation/MessageComposer.component.tsx","../src/components/Conversation/Conversation.hooks.ts","../src/components/Conversation/Conversation.utils.ts","../src/components/Conversation/MessageItem.component.tsx","../src/components/Conversation/CommentThread.component.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from 'react';\nimport { ArrowDown, MessagesSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { ChatProps, ConversationMessage } from './Conversation.types';\nimport {\n useLoadMoreOnScrollTop,\n useNewMessageIds,\n useScrollAnchor,\n useStickToBottom,\n} from './Conversation.hooks';\nimport { classNames, continuesRun, formatDateSeparator, isSameDay } from './Conversation.utils';\n\nconst ChatSkeleton: React.FC = () => (\n <div className=\"eidos-chat-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2, 3].map((row) => (\n <div\n key={row}\n className={classNames(\n 'eidos-chat-skeleton-row',\n row % 2 === 1 && 'eidos-chat-skeleton-row--own',\n )}\n >\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"rounded\" height={48} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A bottom-anchored message thread with a composer.\n *\n * Controlled and transport-agnostic: it renders the `messages` it is given and\n * reports intent through `onSend` / `onLoadMore`. It never fetches, so the\n * same array can be persisted and replayed verbatim.\n */\nexport const Chat: React.FC<ChatProps> = ({\n messages,\n currentUserId,\n onSend,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n showDateSeparators = true,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n typing,\n unreadFromId,\n placeholder = 'Write a message...',\n sendOnEnter = true,\n ariaLabel = 'Conversation',\n className = '',\n}) => {\n const { scrollRef, atBottom, scrollToBottom, handleScroll } = useStickToBottom(messages);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n useScrollAnchor(scrollRef, messages);\n\n const loadMoreOnScroll = useLoadMoreOnScrollTop(scrollRef, { hasMore, loadingMore, onLoadMore });\n\n const onScroll = useCallback(() => {\n handleScroll();\n loadMoreOnScroll();\n }, [handleScroll, loadMoreOnScroll]);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n // An id that isn't in the current page must not draw a divider above\n // nothing - it would float at the top of whatever happens to be loaded.\n const unreadIndex = useMemo(() => {\n if (!unreadFromId) return -1;\n return messages.findIndex((message) => message.id === unreadFromId);\n }, [messages, unreadFromId]);\n\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n\n const renderMessage = (message: ConversationMessage, index: number) => {\n const previous = messages[index - 1];\n const actions = readOnly ? undefined : messageActions?.(message);\n const quoted = message.parentId ? byId.get(message.parentId) : undefined;\n\n const nodes: React.ReactNode[] = [];\n\n if (showDateSeparators && (!previous || !isSameDay(previous.sentAt, message.sentAt))) {\n nodes.push(\n <li key={`date-${message.id}`} className=\"eidos-conversation-separator\">\n <span>{formatDateSeparator(message.sentAt)}</span>\n </li>,\n );\n }\n\n if (index === unreadIndex) {\n nodes.push(\n <li\n key={`unread-${message.id}`}\n className=\"eidos-conversation-separator eidos-conversation-separator--unread\"\n >\n <span>Unread messages</span>\n </li>,\n );\n }\n\n // A separator breaks a run: the message under it needs its own header.\n const grouped =\n groupConsecutive &&\n index !== unreadIndex &&\n nodes.length === 0 &&\n continuesRun(previous, message, groupWindowMinutes);\n\n nodes.push(\n <MessageItem\n key={message.id}\n message={message}\n layout=\"chat\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={grouped}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n quoted={quoted}\n formatTimestamp={formatTimestamp}\n />,\n );\n\n return nodes;\n };\n\n return (\n <div className={classNames('eidos-chat', className)}>\n <div\n ref={scrollRef}\n className=\"eidos-chat-scroll\"\n onScroll={onScroll}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n aria-busy={loading}\n aria-label={ariaLabel}\n tabIndex={0}\n >\n {hasMore && (\n <div className=\"eidos-chat-load-more\">\n {loadingMore ? (\n <span className=\"eidos-chat-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading earlier messages…\n </span>\n ) : (\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load earlier messages\n </Button>\n )}\n </div>\n )}\n\n {loading && <ChatSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessagesSquare />}\n title=\"No messages yet\"\n description={\n showComposer ? 'Send the first message to start the conversation.' : undefined\n }\n size=\"sm\"\n />\n ))}\n\n {!loading && messages.length > 0 && (\n <ol className=\"eidos-conversation-list\">{messages.map(renderMessage)}</ol>\n )}\n\n {typing && typing.length > 0 && (\n <div className=\"eidos-chat-typing\" aria-live=\"polite\">\n <span className=\"eidos-chat-typing-dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n {typing.length === 1\n ? `${typing[0].name} is typing…`\n : `${typing.length} people are typing…`}\n </div>\n )}\n </div>\n\n {!atBottom && messages.length > 0 && (\n <div className=\"eidos-chat-jump\">\n <Button\n size=\"sm\"\n variant=\"filled\"\n color=\"secondary\"\n preIcon={ArrowDown}\n onClick={() => scrollToBottom('smooth')}\n >\n Latest\n </Button>\n </div>\n )}\n\n {showComposer && (\n <div className=\"eidos-chat-composer\">\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n />\n </div>\n )}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </div>\n );\n};\n\nChat.displayName = 'Chat';\n","import React, { useCallback, useId, useRef, useState } from 'react';\nimport { Paperclip, Send, X } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Chip } from '../Chip';\nimport type { MessageComposerProps } from './Conversation.types';\nimport { useAutoGrow } from './Conversation.hooks';\nimport { classNames, formatFileSize } from './Conversation.utils';\n\n/**\n * The authoring half of a conversation - a growing text field, optional\n * attachments, and a submit control.\n *\n * Exported on its own so a consumer building a bespoke list can still reuse\n * the input, and used internally by both `Chat` and `CommentThread`.\n */\nexport const MessageComposer: React.FC<MessageComposerProps> = ({\n value,\n onChange,\n onSubmit,\n onCancel,\n placeholder = 'Write a message...',\n submitLabel = 'Send',\n sendOnEnter = false,\n parentId,\n replyingTo,\n allowAttachments = false,\n attachmentAccept,\n author,\n disabled = false,\n requireBody = true,\n autoFocus = false,\n maxLength,\n className = '',\n}) => {\n const [internalBody, setInternalBody] = useState('');\n const [attachments, setAttachments] = useState<File[]>([]);\n const [pending, setPending] = useState(false);\n\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileInputId = useId();\n\n const isControlled = value !== undefined;\n const body = isControlled ? value : internalBody;\n\n useAutoGrow(textareaRef, body);\n\n const setBody = useCallback(\n (next: string) => {\n if (!isControlled) setInternalBody(next);\n onChange?.(next);\n },\n [isControlled, onChange],\n );\n\n const canSubmit = !disabled && !pending && (!requireBody || body.trim().length > 0);\n\n const submit = useCallback(async () => {\n if (!canSubmit) return;\n\n const draft = { body: body.trim(), attachments, parentId };\n const result = onSubmit(draft);\n\n // A synchronous handler clears immediately; an async one keeps the draft\n // until it resolves, so a rejected send never loses what was typed.\n if (!(result instanceof Promise)) {\n setBody('');\n setAttachments([]);\n return;\n }\n\n setPending(true);\n try {\n await result;\n setBody('');\n setAttachments([]);\n } catch {\n // Intentionally swallowed: the draft stays put and the consumer owns\n // error reporting (a Snackbar, an Alert, ...). Rethrowing here would\n // surface as an unhandled rejection from a keystroke.\n } finally {\n setPending(false);\n }\n }, [canSubmit, body, attachments, parentId, onSubmit, setBody]);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!sendOnEnter) return;\n // Shift+Enter always inserts a newline; the modifier check also leaves\n // IME composition (which fires Enter to commit) alone.\n if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;\n event.preventDefault();\n void submit();\n };\n\n const addFiles = (list: FileList | null) => {\n if (!list?.length) return;\n setAttachments((current) => [...current, ...Array.from(list)]);\n // Reset so selecting the same file twice in a row still fires `change`.\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const removeAttachment = (index: number) =>\n setAttachments((current) => current.filter((_, i) => i !== index));\n\n return (\n <form\n className={classNames('eidos-message-composer', className)}\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {replyingTo && (\n <div className=\"eidos-composer-reply-hint\">\n <span>\n Replying to <strong>{replyingTo}</strong>\n </span>\n {onCancel && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={X}\n onClick={onCancel}\n tooltip=\"Cancel reply\"\n aria-label=\"Cancel reply\"\n />\n )}\n </div>\n )}\n\n <div className=\"eidos-composer-row\">\n {author && (\n <div className=\"eidos-composer-avatar\">\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n </div>\n )}\n\n <div className=\"eidos-composer-field\">\n <textarea\n ref={textareaRef}\n className=\"eidos-composer-input\"\n value={body}\n onChange={(event) => setBody(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled || pending}\n rows={1}\n maxLength={maxLength}\n // Opt-in and off by default. Only the reply composer sets it, where\n // the field appears in direct response to the user pressing Reply -\n // moving focus there is the expected outcome, not a hijack.\n autoFocus={autoFocus}\n aria-label={placeholder}\n />\n\n {attachments.length > 0 && (\n <ul className=\"eidos-composer-attachments\">\n {attachments.map((file, index) => (\n <li key={`${file.name}-${file.size}-${index}`}>\n <Chip\n size=\"sm\"\n variant=\"outlined\"\n color=\"secondary\"\n preIcon={Paperclip}\n onRemove={() => removeAttachment(index)}\n >\n {file.name}\n {file.size > 0 && ` (${formatFileSize(file.size)})`}\n </Chip>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n\n <div className=\"eidos-composer-actions\">\n <div className=\"eidos-composer-actions-start\">\n {allowAttachments && (\n <>\n <input\n ref={fileInputRef}\n id={fileInputId}\n type=\"file\"\n multiple\n accept={attachmentAccept}\n // Hidden from both the tab order and the accessibility\n // tree. It is a proxy: the visible button above opens the\n // file dialog via `.click()`, so this input is an\n // implementation detail, not a control.\n //\n // `visually-hidden` keeps an element *focusable* - that is\n // the point of it, for skip links and screen-reader-only\n // text - so a keyboard user landed on an invisible control\n // and met the same \"Attach files\" action twice. `FileUpload`\n // already gets this right with `display: none` +\n // `tabIndex={-1}`; these two had drifted from it.\n //\n // `aria-hidden` is safe here only *because* of the\n // `tabIndex={-1}` beside it - hiding a focusable element is\n // its own violation.\n className=\"eidos-composer-file-input\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => addFiles(event.target.files)}\n disabled={disabled || pending}\n />\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={Paperclip}\n tooltip=\"Attach files\"\n aria-label=\"Attach files\"\n disabled={disabled || pending}\n onClick={() => fileInputRef.current?.click()}\n />\n </>\n )}\n {sendOnEnter && (\n <span className=\"eidos-composer-hint\">\n <kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line\n </span>\n )}\n </div>\n\n <div className=\"eidos-composer-actions-end\">\n {onCancel && !replyingTo && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n onClick={onCancel}\n disabled={pending}\n >\n Cancel\n </Button>\n )}\n <Button\n type=\"submit\"\n size=\"sm\"\n color=\"primary\"\n preIcon={Send}\n loading={pending}\n disabled={!canSubmit}\n >\n {submitLabel}\n </Button>\n </div>\n </div>\n </form>\n );\n};\n\nMessageComposer.displayName = 'MessageComposer';\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { ConversationMessage } from './Conversation.types';\n\n/**\n * Ids that arrived *after* the first render.\n *\n * Seeded from the first commit so an initial page of history is never\n * animated - otherwise opening a thread fades forty messages in at once,\n * which reads as a glitch rather than as feedback. Only genuinely new ids get\n * the entrance class, and each one only once.\n */\nexport const useNewMessageIds = (\n messages: ConversationMessage[],\n enabled: boolean,\n): Set<string> => {\n const seen = useRef<Set<string> | null>(null);\n const [fresh, setFresh] = useState<Set<string>>(() => new Set());\n\n useEffect(() => {\n // First commit: everything already on screen counts as pre-existing.\n if (seen.current === null) {\n seen.current = new Set(messages.map((message) => message.id));\n return;\n }\n\n if (!enabled) {\n for (const message of messages) seen.current.add(message.id);\n return;\n }\n\n const added = messages.filter((message) => !seen.current?.has(message.id));\n if (!added.length) return;\n\n for (const message of added) seen.current.add(message.id);\n setFresh(new Set(added.map((message) => message.id)));\n }, [messages, enabled]);\n\n return fresh;\n};\n\ninterface StickToBottom {\n scrollRef: React.RefObject<HTMLDivElement | null>;\n /** The viewport is at (or near) the newest message. */\n atBottom: boolean;\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n handleScroll: () => void;\n}\n\n/** Distance from the bottom still counted as \"reading the newest message\". */\nconst BOTTOM_THRESHOLD_PX = 48;\n\n/**\n * Keeps a bottom-anchored list pinned to the newest message - but only while\n * the reader is already there.\n *\n * Scrolling someone back down while they are reading scrollback is the single\n * most common defect in chat UIs, so arrival of a new message only forces a\n * scroll when `atBottom` was already true. Otherwise the caller surfaces a\n * jump-to-latest affordance and leaves the viewport alone.\n */\nexport const useStickToBottom = (messages: ConversationMessage[]): StickToBottom => {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [atBottom, setAtBottom] = useState(true);\n // Read during a layout effect, so it must not be state - it has to be\n // correct for the commit that is happening right now, not the next render.\n const atBottomRef = useRef(true);\n const lastCount = useRef(messages.length);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {\n const node = scrollRef.current;\n if (!node) return;\n node.scrollTo({ top: node.scrollHeight, behavior });\n }, []);\n\n const handleScroll = useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n const next = distance <= BOTTOM_THRESHOLD_PX;\n atBottomRef.current = next;\n setAtBottom((current) => (current === next ? current : next));\n }, []);\n\n // Jump (not animate) to the bottom on first paint so a thread opens on its\n // newest message instead of visibly scrolling there.\n useLayoutEffect(() => {\n scrollToBottom('auto');\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount only\n }, []);\n\n useLayoutEffect(() => {\n if (messages.length > lastCount.current && atBottomRef.current) {\n scrollToBottom('smooth');\n }\n lastCount.current = messages.length;\n }, [messages.length, scrollToBottom]);\n\n return { scrollRef, atBottom, scrollToBottom, handleScroll };\n};\n\n/**\n * Preserves the reader's position when older messages are prepended.\n *\n * Without this, inserting a page above the viewport pushes the content the\n * user was reading down by the height of everything added, which reads as the\n * list jumping to a random place. Capturing `scrollHeight` before the DOM\n * mutation and restoring the delta in a *layout* effect corrects it before the\n * browser paints, so there is no visible shift.\n */\nexport const useScrollAnchor = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n messages: ConversationMessage[],\n): void => {\n const previous = useRef({ height: 0, firstId: '', count: 0 });\n\n useLayoutEffect(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n const firstId = messages[0]?.id ?? '';\n const before = previous.current;\n // A prepend is the only case where the first id changes while the list\n // grows - an append leaves it untouched, and a replacement shrinks it.\n const prepended =\n before.firstId !== '' && before.firstId !== firstId && messages.length > before.count;\n\n if (prepended) {\n node.scrollTop += node.scrollHeight - before.height;\n }\n\n previous.current = { height: node.scrollHeight, firstId, count: messages.length };\n }, [messages, scrollRef]);\n};\n\n/**\n * Fires `onLoadMore` once the reader approaches the top of the history.\n *\n * Latched on the way in and released only after they scroll back out of the\n * trigger zone, so a single pause near the top can't queue a dozen fetches.\n */\nexport const useLoadMoreOnScrollTop = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n {\n hasMore,\n loadingMore,\n onLoadMore,\n }: {\n hasMore?: boolean;\n loadingMore?: boolean;\n onLoadMore?: () => void;\n },\n thresholdPx = 80,\n): (() => void) => {\n const armed = useRef(true);\n\n return useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n if (node.scrollTop > thresholdPx) {\n armed.current = true;\n return;\n }\n\n if (!armed.current || !hasMore || loadingMore || !onLoadMore) return;\n armed.current = false;\n onLoadMore();\n }, [scrollRef, hasMore, loadingMore, onLoadMore, thresholdPx]);\n};\n\n/**\n * Grows a textarea to fit its content, up to `maxHeight`.\n *\n * Height is written to `style.height` directly rather than through a React\n * inline style: it is measured from the live DOM after every keystroke, so it\n * cannot be expressed as a class, and routing it through state would add a\n * render per character. Assigning the property imperatively also keeps it out\n * of the server-rendered `style` attribute that CSP's `style-src-attr` gates.\n */\nexport const useAutoGrow = (\n ref: React.RefObject<HTMLTextAreaElement | null>,\n value: string,\n maxHeight = 200,\n): void => {\n useLayoutEffect(() => {\n const node = ref.current;\n if (!node) return;\n\n node.style.height = 'auto';\n const next = Math.min(node.scrollHeight, maxHeight);\n node.style.height = `${next}px`;\n node.style.overflowY = node.scrollHeight > maxHeight ? 'auto' : 'hidden';\n }, [ref, value, maxHeight]);\n};\n","import type { ConversationMessage } from './Conversation.types';\n\n/**\n * Timestamp formatting goes through `Intl` rather than a date library on\n * purpose: `dayjs` is currently bundled only into the `DatePicker` chunk, and\n * pulling it in here would add it to every consumer importing a conversation.\n * `Intl` is built in and already locale-aware.\n */\n\nconst timeFormat = new Intl.DateTimeFormat(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n});\n\nconst dateFormat = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n});\n\nconst dateFormatSameYear = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n});\n\n/** Parses an ISO string, returning null rather than an `Invalid Date`. */\nexport const parseDate = (iso: string): Date | null => {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n};\n\nexport const formatMessageTime = (iso: string): string => {\n const date = parseDate(iso);\n return date ? timeFormat.format(date) : iso;\n};\n\nconst startOfDay = (date: Date): number =>\n new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n\nexport const isSameDay = (a: string, b: string): boolean => {\n const first = parseDate(a);\n const second = parseDate(b);\n if (!first || !second) return false;\n return startOfDay(first) === startOfDay(second);\n};\n\n/** `Today` / `Yesterday` / a full date, for the divider between days. */\nexport const formatDateSeparator = (iso: string): string => {\n const date = parseDate(iso);\n if (!date) return iso;\n\n const now = new Date();\n const dayDelta = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);\n\n if (dayDelta === 0) return 'Today';\n if (dayDelta === 1) return 'Yesterday';\n return date.getFullYear() === now.getFullYear()\n ? dateFormatSameYear.format(date)\n : dateFormat.format(date);\n};\n\nconst FILE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];\n\nexport const formatFileSize = (bytes: number): string => {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < FILE_UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n // One decimal below 10 (9.4 MB), none above it (24 MB) - the extra digit\n // stops carrying information once the integer part is two digits.\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${FILE_UNITS[unit]}`;\n};\n\n/**\n * Whether `message` continues a run started by `previous` - same author, same\n * day, and within the grouping window. A grouped message hides its avatar and\n * author line.\n */\nexport const continuesRun = (\n previous: ConversationMessage | undefined,\n message: ConversationMessage,\n windowMinutes: number,\n): boolean => {\n if (!previous) return false;\n if (previous.author.id !== message.author.id) return false;\n if (!isSameDay(previous.sentAt, message.sentAt)) return false;\n\n const before = parseDate(previous.sentAt);\n const after = parseDate(message.sentAt);\n if (!before || !after) return false;\n\n return Math.abs(after.getTime() - before.getTime()) <= windowMinutes * 60_000;\n};\n\nexport const sortBySentAt = (messages: ConversationMessage[]): ConversationMessage[] =>\n [...messages].sort((a, b) => {\n const first = parseDate(a.sentAt)?.getTime() ?? 0;\n const second = parseDate(b.sentAt)?.getTime() ?? 0;\n return first - second;\n });\n\nexport interface ThreadNode {\n message: ConversationMessage;\n replies: ConversationMessage[];\n}\n\n/**\n * Collapses a flat list into one level of nesting.\n *\n * Deeper chains are flattened onto the nearest **top-level** ancestor rather\n * than being dropped or nested further: indentation past one level is\n * unreadable on a phone, and silently discarding a reply-to-a-reply would lose\n * data the consumer stored. `replyingToName` on the rendered reply is what\n * preserves who was actually answered.\n *\n * A `parentId` pointing at a message that isn't present (paged out, deleted)\n * is treated as top-level, so nothing ever disappears from the list.\n */\nexport const buildThread = (messages: ConversationMessage[]): ThreadNode[] => {\n const byId = new Map(messages.map((message) => [message.id, message]));\n\n /** Walks up to the outermost ancestor, guarding against a parentId cycle. */\n const rootIdOf = (message: ConversationMessage): string => {\n const seen = new Set<string>([message.id]);\n let current = message;\n\n while (current.parentId) {\n const parent = byId.get(current.parentId);\n if (!parent || seen.has(parent.id)) break;\n seen.add(parent.id);\n current = parent;\n }\n\n return current.id;\n };\n\n const nodes = new Map<string, ThreadNode>();\n const roots: ThreadNode[] = [];\n\n for (const message of messages) {\n if (message.parentId && byId.has(message.parentId)) continue;\n const node: ThreadNode = { message, replies: [] };\n nodes.set(message.id, node);\n roots.push(node);\n }\n\n for (const message of messages) {\n if (!message.parentId || !byId.has(message.parentId)) continue;\n nodes.get(rootIdOf(message))?.replies.push(message);\n }\n\n for (const node of nodes.values()) {\n node.replies = sortBySentAt(node.replies);\n }\n\n return roots;\n};\n\n/** Name of the author a reply was addressed to, when it isn't its own parent. */\nexport const replyTargetName = (\n message: ConversationMessage,\n byId: Map<string, ConversationMessage>,\n rootId: string,\n): string | undefined => {\n if (!message.parentId || message.parentId === rootId) return undefined;\n return byId.get(message.parentId)?.author.name;\n};\n\n/** Trims a quoted excerpt so a long parent message can't dominate the reply. */\nexport const excerpt = (body: string, maxLength = 120): string => {\n const flat = body.replace(/\\s+/g, ' ').trim();\n return flat.length > maxLength ? `${flat.slice(0, maxLength - 1)}…` : flat;\n};\n\nexport const classNames = (...values: Array<string | false | null | undefined>): string =>\n values.filter(Boolean).join(' ');\n","import React from 'react';\nimport { AlertCircle, MoreHorizontal, Paperclip, RotateCw } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Menu } from '../Menu';\nimport { Pill } from '../Pill';\nimport { Spinner } from '../Spinner';\nimport type { ConversationMessage, MessageAttachment } from './Conversation.types';\nimport type { MenuItemType } from '../Menu';\nimport { classNames, excerpt, formatFileSize, formatMessageTime } from './Conversation.utils';\n\nexport interface MessageItemProps {\n message: ConversationMessage;\n /** 'chat' right-aligns own messages into bubbles; 'comment' renders a flat card. */\n layout: 'chat' | 'comment';\n isOwn: boolean;\n /** Continues a run from the same author - hides the avatar and author line. */\n grouped: boolean;\n /** Plays the entrance animation. */\n isNew: boolean;\n actions?: MenuItemType[];\n footer?: React.ReactNode;\n body?: React.ReactNode;\n onRetry?: () => void;\n /** Author of the message being answered, for a flattened deep reply. */\n replyingToName?: string;\n /** Quoted parent, rendered above the body in chat layout. */\n quoted?: ConversationMessage;\n /** Reply affordance; omitted entirely when the viewer may not reply. */\n onReply?: () => void;\n formatTimestamp?: (iso: string) => string;\n}\n\nconst AttachmentList: React.FC<{ attachments: MessageAttachment[] }> = ({ attachments }) => (\n <ul className=\"eidos-message-attachments\">\n {attachments.map((attachment) => {\n const label = (\n <>\n <Paperclip aria-hidden=\"true\" />\n <span className=\"eidos-message-attachment-name\">{attachment.name}</span>\n {attachment.size !== undefined && (\n <span className=\"eidos-message-attachment-size\">{formatFileSize(attachment.size)}</span>\n )}\n </>\n );\n\n return (\n <li key={attachment.id} className=\"eidos-message-attachment\">\n {attachment.url ? (\n <a\n href={attachment.url}\n className=\"eidos-message-attachment-link\"\n target=\"_blank\"\n rel=\"noreferrer\"\n download={attachment.name}\n >\n {label}\n </a>\n ) : (\n <span className=\"eidos-message-attachment-link\">{label}</span>\n )}\n </li>\n );\n })}\n </ul>\n);\n\nexport const MessageItem: React.FC<MessageItemProps> = ({\n message,\n layout,\n isOwn,\n grouped,\n isNew,\n actions,\n footer,\n body,\n onRetry,\n replyingToName,\n quoted,\n onReply,\n formatTimestamp,\n}) => {\n const { author, status = 'sent' } = message;\n const timestamp = formatTimestamp\n ? formatTimestamp(message.sentAt)\n : formatMessageTime(message.sentAt);\n // An empty action list must not leave a dead trigger behind - see the\n // permissions note in Conversation.types.ts.\n const hasActions = Boolean(actions && actions.length > 0);\n\n // One placement per layout, rather than one per message.\n //\n // `chat` puts the time at the foot of the bubble, where a grouped message\n // already had it; it used to appear in the header for the first message of a\n // group and inside the bubble for the rest, so a single thread showed the\n // same fact in two places. `comment` keeps it in the header, where the row is\n // full width and has space for it.\n const timeInBubble = layout === 'chat';\n\n return (\n <li\n className={classNames(\n 'eidos-message',\n `eidos-message--${layout}`,\n isOwn && 'eidos-message--own',\n grouped && 'eidos-message--grouped',\n status !== 'sent' && `eidos-message--${status}`,\n isNew && 'eidos-message--enter',\n )}\n data-message-id={message.id}\n >\n <div className=\"eidos-message-avatar\">\n {!grouped && (\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n )}\n </div>\n\n <div className=\"eidos-message-main\">\n {!grouped && (\n <div className=\"eidos-message-header\">\n <span className=\"eidos-message-author\">{author.name}</span>\n {author.role && (\n <Pill size=\"sm\" variant=\"outlined\" color=\"secondary\">\n {author.role}\n </Pill>\n )}\n {!timeInBubble && (\n <time className=\"eidos-message-time\" dateTime={message.sentAt}>\n {timestamp}\n </time>\n )}\n {message.edited && <span className=\"eidos-message-edited\">edited</span>}\n </div>\n )}\n\n <div className=\"eidos-message-bubble\">\n {quoted && (\n <blockquote className=\"eidos-message-quote\">\n <span className=\"eidos-message-quote-author\">{quoted.author.name}</span>\n <span className=\"eidos-message-quote-body\">{excerpt(quoted.body)}</span>\n </blockquote>\n )}\n\n {replyingToName && (\n <span className=\"eidos-message-reply-target\">Replying to {replyingToName}</span>\n )}\n\n <div className=\"eidos-message-body\">{body ?? message.body}</div>\n\n {message.attachments && message.attachments.length > 0 && (\n <AttachmentList attachments={message.attachments} />\n )}\n\n {(timeInBubble || grouped) && (\n <time\n className=\"eidos-message-time eidos-message-time--inline\"\n dateTime={message.sentAt}\n >\n {timestamp}\n </time>\n )}\n </div>\n\n {status === 'sending' && (\n <span className=\"eidos-message-status\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Sending…\n </span>\n )}\n\n {status === 'failed' && (\n <span className=\"eidos-message-status eidos-message-status--failed\">\n <AlertCircle aria-hidden=\"true\" />\n Not sent\n {onRetry && (\n <Button variant=\"text\" color=\"danger\" size=\"sm\" preIcon={RotateCw} onClick={onRetry}>\n Retry\n </Button>\n )}\n </span>\n )}\n\n {footer && <div className=\"eidos-message-footer\">{footer}</div>}\n\n {onReply && (\n <div className=\"eidos-message-reply\">\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onReply}>\n Reply\n </Button>\n </div>\n )}\n </div>\n\n {hasActions && (\n <div className=\"eidos-message-actions\">\n <Menu\n items={actions ?? []}\n trigger={\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={MoreHorizontal}\n aria-label={`Actions for ${author.name}'s message`}\n />\n }\n />\n </div>\n )}\n </li>\n );\n};\n\nMessageItem.displayName = 'MessageItem';\n","import React, { useCallback, useMemo, useState } from 'react';\nimport { MessageSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { CommentThreadProps, ConversationMessage, MessageDraft } from './Conversation.types';\nimport { useNewMessageIds } from './Conversation.hooks';\nimport {\n buildThread,\n classNames,\n continuesRun,\n replyTargetName,\n sortBySentAt,\n} from './Conversation.utils';\n\nconst CommentSkeleton: React.FC = () => (\n <div className=\"eidos-comment-thread-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2].map((row) => (\n <div key={row} className=\"eidos-comment-thread-skeleton-row\">\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"text\" lines={2} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A comment section: document flow, no internal scroll container, one level of\n * nesting.\n *\n * Shares its data model and message rendering with `Chat`, so history captured\n * in one can be displayed in the other. Unlike `Chat` it has no scroll region\n * of its own - it grows with the page and pages *downward* via `onLoadMore`.\n */\nexport const CommentThread: React.FC<CommentThreadProps> = ({\n messages,\n currentUserId,\n onSend,\n onReply,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n order = 'oldest-first',\n composerPosition = 'bottom',\n placeholder = 'Add a comment...',\n sendOnEnter = false,\n title,\n ariaLabel = 'Comments',\n className = '',\n}) => {\n const [replyingToId, setReplyingToId] = useState<string | null>(null);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n const roots = useMemo(() => {\n const thread = buildThread(messages);\n const sorted = sortBySentAt(thread.map((node) => node.message));\n const ordered = order === 'newest-first' ? [...sorted].reverse() : sorted;\n const nodeById = new Map(thread.map((node) => [node.message.id, node]));\n // Replies always read oldest-first, even when roots are reversed - a\n // conversation under a single comment is chronological by nature.\n return ordered.map((message) => nodeById.get(message.id)!);\n }, [messages, order]);\n\n const canReply = !readOnly && Boolean(onReply);\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n const total = messages.length;\n\n const handleReply = useCallback(\n async (draft: MessageDraft) => {\n await onReply?.(draft);\n setReplyingToId(null);\n },\n [onReply],\n );\n\n const renderComment = (\n message: ConversationMessage,\n options: { rootId: string; previous?: ConversationMessage; isReply: boolean },\n ) => {\n const actions = readOnly ? undefined : messageActions?.(message);\n\n return (\n <MessageItem\n key={message.id}\n message={message}\n layout=\"comment\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={groupConsecutive && continuesRun(options.previous, message, groupWindowMinutes)}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n replyingToName={\n options.isReply ? replyTargetName(message, byId, options.rootId) : undefined\n }\n onReply={canReply ? () => setReplyingToId(message.id) : undefined}\n formatTimestamp={formatTimestamp}\n />\n );\n };\n\n const composer = showComposer && (\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n submitLabel=\"Comment\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n className=\"eidos-comment-thread-composer\"\n />\n );\n\n return (\n <section className={classNames('eidos-comment-thread', className)} aria-label={ariaLabel}>\n {(title || total > 0) && (\n <header className=\"eidos-comment-thread-header\">\n {title && <h3 className=\"eidos-comment-thread-title\">{title}</h3>}\n {total > 0 && (\n <span className=\"eidos-comment-thread-count\">\n {total} {total === 1 ? 'comment' : 'comments'}\n </span>\n )}\n </header>\n )}\n\n {composerPosition === 'top' && composer}\n\n {loading && <CommentSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessageSquare />}\n title=\"No comments yet\"\n description={showComposer ? 'Start the discussion by leaving a comment.' : undefined}\n size=\"sm\"\n />\n ))}\n\n {!loading && roots.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-list\" aria-busy={loadingMore}>\n {roots.map((node) => (\n <li key={node.message.id} className=\"eidos-comment-thread-node\">\n <ol className=\"eidos-conversation-list\">\n {renderComment(node.message, { rootId: node.message.id, isReply: false })}\n </ol>\n\n {node.replies.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-replies\">\n {node.replies.map((reply, index) =>\n renderComment(reply, {\n rootId: node.message.id,\n previous: node.replies[index - 1],\n isReply: true,\n }),\n )}\n </ol>\n )}\n\n {replyingToId &&\n (replyingToId === node.message.id ||\n node.replies.some((reply) => reply.id === replyingToId)) && (\n <div className=\"eidos-comment-thread-reply-composer\">\n <MessageComposer\n onSubmit={handleReply}\n onCancel={() => setReplyingToId(null)}\n parentId={replyingToId}\n replyingTo={byId.get(replyingToId)?.author.name}\n placeholder=\"Write a reply...\"\n submitLabel=\"Reply\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n autoFocus\n />\n </div>\n )}\n </li>\n ))}\n </ol>\n )}\n\n {hasMore && (\n <div className=\"eidos-comment-thread-load-more\">\n {loadingMore ? (\n <span className=\"eidos-comment-thread-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading more comments…\n </span>\n ) : (\n <Button variant=\"outlined\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load more comments\n </Button>\n )}\n </div>\n )}\n\n {composerPosition === 'bottom' && composer}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </section>\n );\n};\n\nCommentThread.displayName = 'CommentThread';\n"],"mappings":"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","names":["useCallback","useCallback","useRef","useState","useState","useRef","useCallback","Paperclip","Fragment","jsx","jsxs","Paperclip","jsx","jsxs","useCallback","useCallback","useMemo","useState","jsx","jsxs","useState","useMemo","useCallback"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-TTLFY76J.cjs","../src/components/Chip/Chip.component.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACNA,2CAAkB;AAoEd,+CAAA;AA/DG,IAAM,KAAA,EAA4B,CAAC;AAAA,EACxC,QAAA,EAAU,QAAA;AAAA,EACV,MAAA,EAAQ,SAAA;AAAA,EACR,KAAA,EAAO,IAAA;AAAA,EACP,SAAA,EAAW,KAAA;AAAA,EACX,UAAA,EAAY,KAAA;AAAA,EACZ,OAAA;AAAA,EACA,UAAA,EAAY,EAAA;AAAA,EACZ,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,YAAA,EAAc,CAAC,CAAC,OAAA;AACtB,EAAA,MAAM,YAAA,EAAc,CAAC,CAAC,QAAA;AAGtB,EAAA,MAAM,YAAA,EAAc;AAAA,IAClB,YAAA;AAAA,IACA,CAAA,YAAA,EAAe,OAAO,CAAA,CAAA;AACF,IAAA;AACD,IAAA;AACP,IAAA;AACG,IAAA;AACA,IAAA;AACF,IAAA;AACb,IAAA;AAGM,EAAA;AAEY,EAAA;AACD,IAAA;AACT,IAAA;AACV,EAAA;AAEsB,EAAA;AACF,IAAA;AACD,IAAA;AACR,IAAA;AACX,EAAA;AAiBwB,EAAA;AACJ,EAAA;AAGlB,EAAA;AACyB,IAAA;AAEtB,oBAAA;AAEsB,IAAA;AACzB,EAAA;AAIA,EAAA;AAAC,IAAA;AAAA,IAAA;AACY,MAAA;AACD,MAAA;AACD,MAAA;AACL,MAAA;AAEH,MAAA;AACC,QAAA;AAAC,UAAA;AAAA,UAAA;AACM,YAAA;AACK,YAAA;AACD,YAAA;AACT,YAAA;AAEC,YAAA;AAAA,UAAA;AAGH,QAAA;AAIA,QAAA;AAAC,UAAA;AAAA,UAAA;AACW,YAAA;AACD,YAAA;AACT,YAAA;AACW,YAAA;AACN,YAAA;AAEL,YAAA;AAAuC,UAAA;AACzC,QAAA;AAAA,MAAA;AAAA,IAAA;AAEJ,EAAA;AAGe,EAAA;AACnB;ADzB2B;AACA;AACA;AACA","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-TTLFY76J.cjs","sourcesContent":[null,"import React from 'react';\nimport { X } from 'lucide-react';\nimport type { ChipProps } from './Chip.types';\nimport { Tooltip } from '../Tooltip/Tooltip.component';\nimport { renderIcon } from '../../utils';\n\nexport const Chip: React.FC<ChipProps> = ({\n variant = 'filled',\n color = 'primary',\n size = 'md',\n disabled = false,\n fullWidth = false,\n tooltip,\n className = '',\n preIcon,\n posIcon,\n children,\n onClick,\n onRemove,\n ...chipProps\n}) => {\n const isClickable = !!onClick;\n const isRemovable = !!onRemove;\n\n // Build CSS classes\n const chipClasses = [\n 'eidos-chip',\n `eidos-chip--${variant}`,\n `eidos-chip--${color}`,\n `eidos-chip--${size}`,\n disabled && 'eidos-chip--disabled',\n isClickable && 'eidos-chip--clickable',\n isRemovable && 'eidos-chip--removable',\n fullWidth && 'eidos-chip--full-width',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleClick = () => {\n if (disabled || !onClick) return;\n onClick();\n };\n\n const handleRemove = (e: React.MouseEvent) => {\n e.stopPropagation(); // Prevent chip click when removing\n if (disabled || !onRemove) return;\n onRemove();\n };\n\n // A chip that is both clickable and removable holds *two* controls, so it\n // has to render them as siblings.\n //\n // The previous shape switched the outer element to a `<div>` when\n // removable - the comment said \"to avoid nested buttons\" - and then gave\n // that div `role=\"button\"` and `tabIndex={0}`. That is the same defect\n // wearing a different hat: a button containing a button, which is invalid\n // (`nested-interactive`) and leaves a screen reader announcing a control\n // inside a control. It is the same shape already fixed on `Dropdown` and\n // `DataGrid`'s rows.\n //\n // Now the outer element is a plain container whenever there is a remove\n // button, and the chip's own action moves to an inner `<button>` wrapping\n // just the content. Each control is separately reachable and separately\n // named.\n const hasBothControls = isClickable && isRemovable;\n const ChipElement = isClickable && !isRemovable ? 'button' : 'div';\n\n const body = (\n <>\n {preIcon && renderIcon(preIcon, 'eidos-chip--pre-icon')}\n\n <span className=\"eidos-chip--copy\">{children}</span>\n\n {posIcon && renderIcon(posIcon, 'eidos-chip--pos-icon')}\n </>\n );\n\n const chipContent = (\n <ChipElement\n className={chipClasses}\n disabled={ChipElement === 'button' ? disabled : undefined}\n onClick={isClickable && !hasBothControls ? handleClick : undefined}\n {...chipProps}\n >\n {hasBothControls ? (\n <button\n type=\"button\"\n className=\"eidos-chip--action\"\n onClick={handleClick}\n disabled={disabled}\n >\n {body}\n </button>\n ) : (\n body\n )}\n\n {isRemovable && (\n <button\n className=\"eidos-chip--remove-button\"\n onClick={handleRemove}\n disabled={disabled}\n aria-label=\"Remove\"\n type=\"button\"\n >\n <X className=\"eidos-chip--remove-icon\" />\n </button>\n )}\n </ChipElement>\n );\n\n return tooltip ? <Tooltip message={tooltip}>{chipContent}</Tooltip> : chipContent;\n};\n"]}