eidos-ui 3.4.0 → 3.5.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 (278) hide show
  1. package/dist/{Menu.types-DNIpjSAK.d.ts → Menu.types-BN4g1-3A.d.ts} +9 -0
  2. package/dist/{Menu.types-DwcuCZM7.d.cts → Menu.types-CTnUijv5.d.cts} +9 -0
  3. package/dist/{Select.types-BqaD7bmB.d.ts → Select.types-C2tGy1vj.d.ts} +10 -0
  4. package/dist/{Select.types-2p-mpaBL.d.cts → Select.types-ok-03rLo.d.cts} +10 -0
  5. package/dist/{Table.types-CPj-udMc.d.cts → Table.types-D-9tC0kJ.d.cts} +2 -3
  6. package/dist/{Table.types-CZZ7QWAI.d.ts → Table.types-D8pmgxKA.d.ts} +2 -3
  7. package/dist/{Toolbar.types-RN0klQzu.d.ts → Toolbar.types-CKnQC_TW.d.ts} +1 -1
  8. package/dist/{Toolbar.types-DSk8z2iy.d.cts → Toolbar.types-DUz2mxct.d.cts} +1 -1
  9. package/dist/alert/index.cjs +5 -5
  10. package/dist/alert/index.js +4 -4
  11. package/dist/button/index.cjs +4 -4
  12. package/dist/button/index.js +3 -3
  13. package/dist/chip/index.cjs +4 -4
  14. package/dist/chip/index.js +3 -3
  15. package/dist/{chunk-WGCGSM5P.js → chunk-24HKKSKX.js} +11 -18
  16. package/dist/chunk-24HKKSKX.js.map +1 -0
  17. package/dist/{chunk-BBU7QG7L.js → chunk-26SO4LZ5.js} +4 -4
  18. package/dist/{chunk-Y7VSWZWM.js → chunk-2JW3WSBF.js} +4 -4
  19. package/dist/{chunk-IXVM2XNS.js → chunk-2PCCGQOH.js} +89 -19
  20. package/dist/chunk-2PCCGQOH.js.map +1 -0
  21. package/dist/{chunk-4LET2PEX.cjs → chunk-2T7ZMMH5.cjs} +64 -11
  22. package/dist/chunk-2T7ZMMH5.cjs.map +1 -0
  23. package/dist/{chunk-VHSTB7GB.cjs → chunk-35LGEPLP.cjs} +13 -2
  24. package/dist/chunk-35LGEPLP.cjs.map +1 -0
  25. package/dist/{chunk-5E4DYTZ3.cjs → chunk-36JFXFM2.cjs} +36 -36
  26. package/dist/{chunk-5E4DYTZ3.cjs.map → chunk-36JFXFM2.cjs.map} +1 -1
  27. package/dist/{chunk-XMXL6NVJ.cjs → chunk-3KBBJZHK.cjs} +11 -11
  28. package/dist/{chunk-XMXL6NVJ.cjs.map → chunk-3KBBJZHK.cjs.map} +1 -1
  29. package/dist/{chunk-BIANFFJL.cjs → chunk-3RWZC6OW.cjs} +5 -5
  30. package/dist/{chunk-BIANFFJL.cjs.map → chunk-3RWZC6OW.cjs.map} +1 -1
  31. package/dist/{chunk-CBT3TTQR.cjs → chunk-3ZRFOKDM.cjs} +30 -30
  32. package/dist/{chunk-CBT3TTQR.cjs.map → chunk-3ZRFOKDM.cjs.map} +1 -1
  33. package/dist/{chunk-5V7DXZWT.cjs → chunk-4AO5RKV5.cjs} +25 -37
  34. package/dist/chunk-4AO5RKV5.cjs.map +1 -0
  35. package/dist/{chunk-UDRAM6JU.js → chunk-4J25XZDI.js} +13 -11
  36. package/dist/chunk-4J25XZDI.js.map +1 -0
  37. package/dist/{chunk-TWXZKUCG.cjs → chunk-4JSTBA4F.cjs} +9 -7
  38. package/dist/chunk-4JSTBA4F.cjs.map +1 -0
  39. package/dist/{chunk-TYGVKSL6.js → chunk-4LNTZPSQ.js} +2 -2
  40. package/dist/{chunk-UGTKZ7HP.js → chunk-5XH2PHT2.js} +13 -13
  41. package/dist/{chunk-UGTKZ7HP.js.map → chunk-5XH2PHT2.js.map} +1 -1
  42. package/dist/{chunk-H5D3ZFBM.cjs → chunk-66UQTF57.cjs} +46 -46
  43. package/dist/{chunk-H5D3ZFBM.cjs.map → chunk-66UQTF57.cjs.map} +1 -1
  44. package/dist/{chunk-JKBBHAIC.cjs → chunk-6JHVXWDV.cjs} +124 -54
  45. package/dist/chunk-6JHVXWDV.cjs.map +1 -0
  46. package/dist/{chunk-44T4HMFU.js → chunk-75GFIQRU.js} +3 -3
  47. package/dist/{chunk-AHVDTWVM.cjs → chunk-7BFODYNL.cjs} +7 -7
  48. package/dist/{chunk-AHVDTWVM.cjs.map → chunk-7BFODYNL.cjs.map} +1 -1
  49. package/dist/{chunk-NCZUQFKL.cjs → chunk-7VJCGKVP.cjs} +11 -11
  50. package/dist/{chunk-NCZUQFKL.cjs.map → chunk-7VJCGKVP.cjs.map} +1 -1
  51. package/dist/{chunk-PG2EII6U.js → chunk-A3LMUPG3.js} +2 -2
  52. package/dist/{chunk-BO4LTIJI.js → chunk-AR2ZOO6L.js} +3 -3
  53. package/dist/{chunk-MLUHM3VG.cjs → chunk-BEOECPVJ.cjs} +5 -7
  54. package/dist/chunk-BEOECPVJ.cjs.map +1 -0
  55. package/dist/{chunk-FPLX5FIK.js → chunk-BFKPJ627.js} +5 -4
  56. package/dist/chunk-BFKPJ627.js.map +1 -0
  57. package/dist/{chunk-MP7LN5CW.js → chunk-D7SQ7LHJ.js} +2 -2
  58. package/dist/{chunk-OWWPHXAF.cjs → chunk-DA375AI6.cjs} +8 -8
  59. package/dist/{chunk-OWWPHXAF.cjs.map → chunk-DA375AI6.cjs.map} +1 -1
  60. package/dist/{chunk-7KVXXHFX.cjs → chunk-DU5JKTHK.cjs} +8 -6
  61. package/dist/chunk-DU5JKTHK.cjs.map +1 -0
  62. package/dist/{chunk-LH5A2SIX.js → chunk-FIVMMCGM.js} +8 -20
  63. package/dist/chunk-FIVMMCGM.js.map +1 -0
  64. package/dist/{chunk-22EKNTWJ.js → chunk-GIEDIAPD.js} +8 -6
  65. package/dist/chunk-GIEDIAPD.js.map +1 -0
  66. package/dist/{chunk-X6UKU3C3.js → chunk-GPVGGBIR.js} +2 -2
  67. package/dist/{chunk-NACA72XI.js → chunk-GWHZFX2H.js} +3 -3
  68. package/dist/{chunk-YIBPOLNN.js → chunk-HEX4IMLO.js} +4 -4
  69. package/dist/{chunk-KWDOOMHR.cjs → chunk-HMEREPDB.cjs} +7 -7
  70. package/dist/{chunk-KWDOOMHR.cjs.map → chunk-HMEREPDB.cjs.map} +1 -1
  71. package/dist/{chunk-TYSNOPMQ.cjs → chunk-HNGFRRVZ.cjs} +3 -3
  72. package/dist/{chunk-TYSNOPMQ.cjs.map → chunk-HNGFRRVZ.cjs.map} +1 -1
  73. package/dist/{chunk-3QQKWXMV.cjs → chunk-I6M6CVI2.cjs} +3 -3
  74. package/dist/{chunk-3QQKWXMV.cjs.map → chunk-I6M6CVI2.cjs.map} +1 -1
  75. package/dist/chunk-J76JHN7J.cjs +225 -0
  76. package/dist/chunk-J76JHN7J.cjs.map +1 -0
  77. package/dist/{chunk-SKLMBE3Z.cjs → chunk-JCKGAAPZ.cjs} +7 -7
  78. package/dist/{chunk-SKLMBE3Z.cjs.map → chunk-JCKGAAPZ.cjs.map} +1 -1
  79. package/dist/{chunk-67EXHO4T.js → chunk-JHX6JK3M.js} +79 -75
  80. package/dist/chunk-JHX6JK3M.js.map +1 -0
  81. package/dist/{chunk-XO7WTE6O.js → chunk-JNY7TUDS.js} +4 -6
  82. package/dist/chunk-JNY7TUDS.js.map +1 -0
  83. package/dist/{chunk-GMDVW76A.cjs → chunk-JPYJQ4TF.cjs} +6 -6
  84. package/dist/{chunk-GMDVW76A.cjs.map → chunk-JPYJQ4TF.cjs.map} +1 -1
  85. package/dist/{chunk-SVCWSW2F.js → chunk-JT4KVXJK.js} +65 -12
  86. package/dist/chunk-JT4KVXJK.js.map +1 -0
  87. package/dist/{chunk-FODRHCRP.cjs → chunk-KBQD4JLK.cjs} +3 -3
  88. package/dist/{chunk-FODRHCRP.cjs.map → chunk-KBQD4JLK.cjs.map} +1 -1
  89. package/dist/{chunk-P7J6WZTO.cjs → chunk-KIZVY57H.cjs} +82 -78
  90. package/dist/chunk-KIZVY57H.cjs.map +1 -0
  91. package/dist/{chunk-N5ZWQJTM.cjs → chunk-KREHT4AQ.cjs} +8 -8
  92. package/dist/{chunk-N5ZWQJTM.cjs.map → chunk-KREHT4AQ.cjs.map} +1 -1
  93. package/dist/{chunk-SCLFJHN2.js → chunk-L65MHFMD.js} +3 -3
  94. package/dist/{chunk-N77VOYCK.cjs → chunk-LQXFXXMJ.cjs} +8 -7
  95. package/dist/chunk-LQXFXXMJ.cjs.map +1 -0
  96. package/dist/{chunk-OIXPYXR6.js → chunk-MPIVBAMY.js} +2 -2
  97. package/dist/{chunk-TLECMA3B.js → chunk-MQAOXZD4.js} +27 -14
  98. package/dist/chunk-MQAOXZD4.js.map +1 -0
  99. package/dist/{chunk-YUH56CJI.cjs → chunk-NOQF7UQ5.cjs} +6 -6
  100. package/dist/{chunk-YUH56CJI.cjs.map → chunk-NOQF7UQ5.cjs.map} +1 -1
  101. package/dist/{chunk-DMOZURHV.js → chunk-O3QM3BFE.js} +2 -2
  102. package/dist/{chunk-SFRILDXY.js → chunk-OOCCSMWM.js} +4 -4
  103. package/dist/chunk-P4XZH3OR.js +225 -0
  104. package/dist/chunk-P4XZH3OR.js.map +1 -0
  105. package/dist/{chunk-O46UJUZM.cjs → chunk-PMSENLOA.cjs} +35 -27
  106. package/dist/chunk-PMSENLOA.cjs.map +1 -0
  107. package/dist/{chunk-EWTMX4DK.js → chunk-RHFD2Z5V.js} +7 -7
  108. package/dist/{chunk-WT4WBHBR.js → chunk-RHHZRTWM.js} +13 -2
  109. package/dist/chunk-RHHZRTWM.js.map +1 -0
  110. package/dist/{chunk-NF55JG2V.cjs → chunk-RJVHR45I.cjs} +7 -7
  111. package/dist/{chunk-NF55JG2V.cjs.map → chunk-RJVHR45I.cjs.map} +1 -1
  112. package/dist/{chunk-FDMYDIGI.js → chunk-SDUGYJGW.js} +22 -14
  113. package/dist/chunk-SDUGYJGW.js.map +1 -0
  114. package/dist/{chunk-VRNCXYEP.js → chunk-SFFKQQ5Z.js} +4 -4
  115. package/dist/{chunk-AXPEJ5IG.cjs → chunk-TC5IC6OP.cjs} +3 -3
  116. package/dist/{chunk-AXPEJ5IG.cjs.map → chunk-TC5IC6OP.cjs.map} +1 -1
  117. package/dist/{chunk-BGBAZMFE.js → chunk-THCTUBX6.js} +8 -6
  118. package/dist/chunk-THCTUBX6.js.map +1 -0
  119. package/dist/{chunk-FXMRDNR6.cjs → chunk-TTLFY76J.cjs} +6 -6
  120. package/dist/{chunk-FXMRDNR6.cjs.map → chunk-TTLFY76J.cjs.map} +1 -1
  121. package/dist/{chunk-OZQKWDFH.cjs → chunk-V4BZWBZT.cjs} +7 -7
  122. package/dist/{chunk-OZQKWDFH.cjs.map → chunk-V4BZWBZT.cjs.map} +1 -1
  123. package/dist/{chunk-27UANGPW.cjs → chunk-VQGLVBPL.cjs} +13 -11
  124. package/dist/chunk-VQGLVBPL.cjs.map +1 -0
  125. package/dist/{chunk-LIKSLFX6.js → chunk-WE2SBWZ6.js} +7 -7
  126. package/dist/{chunk-GPK2THIG.js → chunk-WNDDS367.js} +3 -3
  127. package/dist/{chunk-3S33BIN2.cjs → chunk-X3BVQBXD.cjs} +16 -16
  128. package/dist/{chunk-3S33BIN2.cjs.map → chunk-X3BVQBXD.cjs.map} +1 -1
  129. package/dist/{chunk-SKBKTNEQ.cjs → chunk-XERBOFYZ.cjs} +3 -3
  130. package/dist/{chunk-SKBKTNEQ.cjs.map → chunk-XERBOFYZ.cjs.map} +1 -1
  131. package/dist/{chunk-6LCYO2HG.cjs → chunk-XZLSHEHE.cjs} +40 -27
  132. package/dist/chunk-XZLSHEHE.cjs.map +1 -0
  133. package/dist/{chunk-CEC3GO4R.js → chunk-YAKTVIVK.js} +3 -3
  134. package/dist/{chunk-3WV6RTXX.js → chunk-YP4HUBKN.js} +2 -2
  135. package/dist/{chunk-XK5USBHB.js → chunk-YXRKBKFB.js} +3 -3
  136. package/dist/{chunk-MW4YZVP5.cjs → chunk-ZMRINZIO.cjs} +17 -24
  137. package/dist/chunk-ZMRINZIO.cjs.map +1 -0
  138. package/dist/color-picker/index.cjs +4 -4
  139. package/dist/color-picker/index.js +3 -3
  140. package/dist/combobox/index.cjs +6 -6
  141. package/dist/combobox/index.js +5 -5
  142. package/dist/command-palette/index.cjs +3 -3
  143. package/dist/command-palette/index.js +2 -2
  144. package/dist/context-menu/index.cjs +6 -6
  145. package/dist/context-menu/index.d.cts +1 -1
  146. package/dist/context-menu/index.d.ts +1 -1
  147. package/dist/context-menu/index.js +5 -5
  148. package/dist/conversation/index.cjs +8 -8
  149. package/dist/conversation/index.d.cts +1 -1
  150. package/dist/conversation/index.d.ts +1 -1
  151. package/dist/conversation/index.js +7 -7
  152. package/dist/data-grid/index.cjs +15 -15
  153. package/dist/data-grid/index.d.cts +13 -14
  154. package/dist/data-grid/index.d.ts +13 -14
  155. package/dist/data-grid/index.js +14 -14
  156. package/dist/date-picker/index.cjs +8 -8
  157. package/dist/date-picker/index.d.cts +11 -1
  158. package/dist/date-picker/index.d.ts +11 -1
  159. package/dist/date-picker/index.js +7 -7
  160. package/dist/drawer/index.cjs +5 -5
  161. package/dist/drawer/index.js +4 -4
  162. package/dist/dropdown/index.cjs +3 -3
  163. package/dist/dropdown/index.d.cts +34 -0
  164. package/dist/dropdown/index.d.ts +34 -0
  165. package/dist/dropdown/index.js +2 -2
  166. package/dist/header/index.cjs +6 -6
  167. package/dist/header/index.js +5 -5
  168. package/dist/index.cjs +37 -37
  169. package/dist/index.css +19 -2
  170. package/dist/index.d.cts +4 -4
  171. package/dist/index.d.ts +4 -4
  172. package/dist/index.js +36 -36
  173. package/dist/inline-edit/index.cjs +5 -5
  174. package/dist/inline-edit/index.js +4 -4
  175. package/dist/input/index.cjs +4 -4
  176. package/dist/input/index.js +3 -3
  177. package/dist/menu/index.cjs +5 -5
  178. package/dist/menu/index.d.cts +23 -2
  179. package/dist/menu/index.d.ts +23 -2
  180. package/dist/menu/index.js +4 -4
  181. package/dist/modal/index.cjs +5 -5
  182. package/dist/modal/index.js +4 -4
  183. package/dist/navigation/index.cjs +5 -5
  184. package/dist/navigation/index.js +4 -4
  185. package/dist/number-input/index.cjs +3 -3
  186. package/dist/number-input/index.js +2 -2
  187. package/dist/page-layout/index.cjs +12 -12
  188. package/dist/page-layout/index.d.cts +2 -2
  189. package/dist/page-layout/index.d.ts +2 -2
  190. package/dist/page-layout/index.js +11 -11
  191. package/dist/pagination/index.cjs +8 -8
  192. package/dist/pagination/index.js +7 -7
  193. package/dist/popover/index.cjs +3 -3
  194. package/dist/popover/index.d.cts +3 -1
  195. package/dist/popover/index.d.ts +3 -1
  196. package/dist/popover/index.js +2 -2
  197. package/dist/segmented-control/index.cjs +4 -4
  198. package/dist/segmented-control/index.js +3 -3
  199. package/dist/select/index.cjs +6 -6
  200. package/dist/select/index.d.cts +2 -2
  201. package/dist/select/index.d.ts +2 -2
  202. package/dist/select/index.js +5 -5
  203. package/dist/snackbar/index.cjs +5 -5
  204. package/dist/snackbar/index.js +4 -4
  205. package/dist/split-button/index.cjs +6 -6
  206. package/dist/split-button/index.js +5 -5
  207. package/dist/table/index.cjs +13 -13
  208. package/dist/table/index.d.cts +2 -2
  209. package/dist/table/index.d.ts +2 -2
  210. package/dist/table/index.js +12 -12
  211. package/dist/tabs/index.cjs +3 -3
  212. package/dist/tabs/index.js +2 -2
  213. package/dist/tag-input/index.cjs +6 -6
  214. package/dist/tag-input/index.js +5 -5
  215. package/dist/textarea/index.cjs +4 -4
  216. package/dist/textarea/index.js +3 -3
  217. package/dist/theme-editor/index.cjs +11 -11
  218. package/dist/theme-editor/index.js +10 -10
  219. package/dist/theme-provider/index.cjs +3 -3
  220. package/dist/theme-provider/index.js +2 -2
  221. package/dist/toolbar/index.cjs +8 -8
  222. package/dist/toolbar/index.d.cts +3 -3
  223. package/dist/toolbar/index.d.ts +3 -3
  224. package/dist/toolbar/index.js +7 -7
  225. package/dist/tooltip/index.cjs +3 -3
  226. package/dist/tooltip/index.js +2 -2
  227. package/package.json +1 -1
  228. package/dist/chunk-22EKNTWJ.js.map +0 -1
  229. package/dist/chunk-27UANGPW.cjs.map +0 -1
  230. package/dist/chunk-4LET2PEX.cjs.map +0 -1
  231. package/dist/chunk-5V7DXZWT.cjs.map +0 -1
  232. package/dist/chunk-67EXHO4T.js.map +0 -1
  233. package/dist/chunk-6LCYO2HG.cjs.map +0 -1
  234. package/dist/chunk-7KVXXHFX.cjs.map +0 -1
  235. package/dist/chunk-BGBAZMFE.js.map +0 -1
  236. package/dist/chunk-CP335H4Q.js +0 -125
  237. package/dist/chunk-CP335H4Q.js.map +0 -1
  238. package/dist/chunk-FDMYDIGI.js.map +0 -1
  239. package/dist/chunk-FPLX5FIK.js.map +0 -1
  240. package/dist/chunk-G74DHVHV.cjs +0 -125
  241. package/dist/chunk-G74DHVHV.cjs.map +0 -1
  242. package/dist/chunk-IXVM2XNS.js.map +0 -1
  243. package/dist/chunk-JKBBHAIC.cjs.map +0 -1
  244. package/dist/chunk-LH5A2SIX.js.map +0 -1
  245. package/dist/chunk-MLUHM3VG.cjs.map +0 -1
  246. package/dist/chunk-MW4YZVP5.cjs.map +0 -1
  247. package/dist/chunk-N77VOYCK.cjs.map +0 -1
  248. package/dist/chunk-O46UJUZM.cjs.map +0 -1
  249. package/dist/chunk-P7J6WZTO.cjs.map +0 -1
  250. package/dist/chunk-SVCWSW2F.js.map +0 -1
  251. package/dist/chunk-TLECMA3B.js.map +0 -1
  252. package/dist/chunk-TWXZKUCG.cjs.map +0 -1
  253. package/dist/chunk-UDRAM6JU.js.map +0 -1
  254. package/dist/chunk-VHSTB7GB.cjs.map +0 -1
  255. package/dist/chunk-WGCGSM5P.js.map +0 -1
  256. package/dist/chunk-WT4WBHBR.js.map +0 -1
  257. package/dist/chunk-XO7WTE6O.js.map +0 -1
  258. /package/dist/{chunk-BBU7QG7L.js.map → chunk-26SO4LZ5.js.map} +0 -0
  259. /package/dist/{chunk-Y7VSWZWM.js.map → chunk-2JW3WSBF.js.map} +0 -0
  260. /package/dist/{chunk-TYGVKSL6.js.map → chunk-4LNTZPSQ.js.map} +0 -0
  261. /package/dist/{chunk-44T4HMFU.js.map → chunk-75GFIQRU.js.map} +0 -0
  262. /package/dist/{chunk-PG2EII6U.js.map → chunk-A3LMUPG3.js.map} +0 -0
  263. /package/dist/{chunk-BO4LTIJI.js.map → chunk-AR2ZOO6L.js.map} +0 -0
  264. /package/dist/{chunk-MP7LN5CW.js.map → chunk-D7SQ7LHJ.js.map} +0 -0
  265. /package/dist/{chunk-X6UKU3C3.js.map → chunk-GPVGGBIR.js.map} +0 -0
  266. /package/dist/{chunk-NACA72XI.js.map → chunk-GWHZFX2H.js.map} +0 -0
  267. /package/dist/{chunk-YIBPOLNN.js.map → chunk-HEX4IMLO.js.map} +0 -0
  268. /package/dist/{chunk-SCLFJHN2.js.map → chunk-L65MHFMD.js.map} +0 -0
  269. /package/dist/{chunk-OIXPYXR6.js.map → chunk-MPIVBAMY.js.map} +0 -0
  270. /package/dist/{chunk-DMOZURHV.js.map → chunk-O3QM3BFE.js.map} +0 -0
  271. /package/dist/{chunk-SFRILDXY.js.map → chunk-OOCCSMWM.js.map} +0 -0
  272. /package/dist/{chunk-EWTMX4DK.js.map → chunk-RHFD2Z5V.js.map} +0 -0
  273. /package/dist/{chunk-VRNCXYEP.js.map → chunk-SFFKQQ5Z.js.map} +0 -0
  274. /package/dist/{chunk-LIKSLFX6.js.map → chunk-WE2SBWZ6.js.map} +0 -0
  275. /package/dist/{chunk-GPK2THIG.js.map → chunk-WNDDS367.js.map} +0 -0
  276. /package/dist/{chunk-CEC3GO4R.js.map → chunk-YAKTVIVK.js.map} +0 -0
  277. /package/dist/{chunk-3WV6RTXX.js.map → chunk-YP4HUBKN.js.map} +0 -0
  278. /package/dist/{chunk-XK5USBHB.js.map → chunk-YXRKBKFB.js.map} +0 -0
@@ -12,16 +12,16 @@ var _chunkTROYIUDHcjs = require('./chunk-TROYIUDH.cjs');
12
12
  var _chunkKV3V2CPHcjs = require('./chunk-KV3V2CPH.cjs');
13
13
 
14
14
 
15
- var _chunkFXMRDNR6cjs = require('./chunk-FXMRDNR6.cjs');
15
+ var _chunkTTLFY76Jcjs = require('./chunk-TTLFY76J.cjs');
16
16
 
17
17
 
18
- var _chunkG74DHVHVcjs = require('./chunk-G74DHVHV.cjs');
18
+ var _chunkJ76JHN7Jcjs = require('./chunk-J76JHN7J.cjs');
19
19
 
20
20
 
21
21
  var _chunkQ3GNKF74cjs = require('./chunk-Q3GNKF74.cjs');
22
22
 
23
23
 
24
- var _chunkOWWPHXAFcjs = require('./chunk-OWWPHXAF.cjs');
24
+ var _chunkDA375AI6cjs = require('./chunk-DA375AI6.cjs');
25
25
 
26
26
  // src/components/Conversation/Chat.component.tsx
27
27
  var _react = require('react');
@@ -313,7 +313,7 @@ var MessageComposer = ({
313
313
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: replyingTo })
314
314
  ] }),
315
315
  onCancel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
316
- _chunkOWWPHXAFcjs.Button,
316
+ _chunkDA375AI6cjs.Button,
317
317
  {
318
318
  variant: "text",
319
319
  color: "secondary",
@@ -353,7 +353,7 @@ var MessageComposer = ({
353
353
  }
354
354
  ),
355
355
  attachments.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "eidos-composer-attachments", children: attachments.map((file, index) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
356
- _chunkFXMRDNR6cjs.Chip,
356
+ _chunkTTLFY76Jcjs.Chip,
357
357
  {
358
358
  size: "sm",
359
359
  variant: "outlined",
@@ -387,7 +387,7 @@ var MessageComposer = ({
387
387
  }
388
388
  ),
389
389
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
390
- _chunkOWWPHXAFcjs.Button,
390
+ _chunkDA375AI6cjs.Button,
391
391
  {
392
392
  variant: "text",
393
393
  color: "secondary",
@@ -411,7 +411,7 @@ var MessageComposer = ({
411
411
  ] }),
412
412
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "eidos-composer-actions-end", children: [
413
413
  onCancel && !replyingTo && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
414
- _chunkOWWPHXAFcjs.Button,
414
+ _chunkDA375AI6cjs.Button,
415
415
  {
416
416
  variant: "text",
417
417
  color: "secondary",
@@ -422,7 +422,7 @@ var MessageComposer = ({
422
422
  }
423
423
  ),
424
424
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
425
- _chunkOWWPHXAFcjs.Button,
425
+ _chunkDA375AI6cjs.Button,
426
426
  {
427
427
  type: "submit",
428
428
  size: "sm",
@@ -536,17 +536,17 @@ var MessageItem = ({
536
536
  status === "failed" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "eidos-message-status eidos-message-status--failed", children: [
537
537
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.AlertCircle, { "aria-hidden": "true" }),
538
538
  "Not sent",
539
- onRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkOWWPHXAFcjs.Button, { variant: "text", color: "danger", size: "sm", preIcon: _lucidereact.RotateCw, onClick: onRetry, children: "Retry" })
539
+ onRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDA375AI6cjs.Button, { variant: "text", color: "danger", size: "sm", preIcon: _lucidereact.RotateCw, onClick: onRetry, children: "Retry" })
540
540
  ] }),
541
541
  footer && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-message-footer", children: footer }),
542
- onReply && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-message-reply", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkOWWPHXAFcjs.Button, { variant: "text", color: "secondary", size: "sm", onClick: onReply, children: "Reply" }) })
542
+ onReply && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-message-reply", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDA375AI6cjs.Button, { variant: "text", color: "secondary", size: "sm", onClick: onReply, children: "Reply" }) })
543
543
  ] }),
544
544
  hasActions && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-message-actions", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
545
- _chunkG74DHVHVcjs.Menu,
545
+ _chunkJ76JHN7Jcjs.Menu,
546
546
  {
547
547
  items: _nullishCoalesce(actions, () => ( [])),
548
548
  trigger: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
549
- _chunkOWWPHXAFcjs.Button,
549
+ _chunkDA375AI6cjs.Button,
550
550
  {
551
551
  variant: "text",
552
552
  color: "secondary",
@@ -684,7 +684,7 @@ var Chat = ({
684
684
  hasMore && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-chat-load-more", children: loadingMore ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "eidos-chat-loading-more", children: [
685
685
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkTROYIUDHcjs.Spinner, { size: "sm", color: "secondary" }),
686
686
  "Loading earlier messages\u2026"
687
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkOWWPHXAFcjs.Button, { variant: "text", color: "secondary", size: "sm", onClick: onLoadMore, children: "Load earlier messages" }) }),
687
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDA375AI6cjs.Button, { variant: "text", color: "secondary", size: "sm", onClick: onLoadMore, children: "Load earlier messages" }) }),
688
688
  loading && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatSkeleton, {}),
689
689
  isEmpty && (_nullishCoalesce(emptyContent, () => ( /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
690
690
  _chunkKV3V2CPHcjs.EmptyState,
@@ -708,7 +708,7 @@ var Chat = ({
708
708
  }
709
709
  ),
710
710
  !atBottom && messages.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-chat-jump", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
711
- _chunkOWWPHXAFcjs.Button,
711
+ _chunkDA375AI6cjs.Button,
712
712
  {
713
713
  size: "sm",
714
714
  variant: "filled",
@@ -873,7 +873,7 @@ var CommentThread = ({
873
873
  hasMore && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-comment-thread-load-more", children: loadingMore ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "eidos-comment-thread-loading-more", children: [
874
874
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkTROYIUDHcjs.Spinner, { size: "sm", color: "secondary" }),
875
875
  "Loading more comments\u2026"
876
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkOWWPHXAFcjs.Button, { variant: "outlined", color: "secondary", size: "sm", onClick: onLoadMore, children: "Load more comments" }) }),
876
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDA375AI6cjs.Button, { variant: "outlined", color: "secondary", size: "sm", onClick: onLoadMore, children: "Load more comments" }) }),
877
877
  composerPosition === "bottom" && composer,
878
878
  readOnly && readOnlyMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "eidos-conversation-readonly", children: readOnlyMessage })
879
879
  ] });
@@ -885,4 +885,4 @@ CommentThread.displayName = "CommentThread";
885
885
 
886
886
 
887
887
  exports.MessageComposer = MessageComposer; exports.Chat = Chat; exports.CommentThread = CommentThread;
888
- //# sourceMappingURL=chunk-3S33BIN2.cjs.map
888
+ //# sourceMappingURL=chunk-X3BVQBXD.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-3S33BIN2.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-3S33BIN2.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 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 <time className=\"eidos-message-time\" dateTime={message.sentAt}>\n {timestamp}\n </time>\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 {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
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-X3BVQBXD.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-X3BVQBXD.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 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 <time className=\"eidos-message-time\" dateTime={message.sentAt}>\n {timestamp}\n </time>\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 {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,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkOWWPHXAFcjs = require('./chunk-OWWPHXAF.cjs');
3
+ var _chunkDA375AI6cjs = require('./chunk-DA375AI6.cjs');
4
4
 
5
5
  // src/components/Snackbar/Snackbar.context.tsx
6
6
  var _react = require('react');
@@ -163,7 +163,7 @@ var SnackbarComponent = ({ snackbar, onClose }) => {
163
163
  children: [
164
164
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-snackbar-content", children: renderContent() }),
165
165
  action && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-snackbar-actions", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
166
- _chunkOWWPHXAFcjs.Button,
166
+ _chunkDA375AI6cjs.Button,
167
167
  {
168
168
  variant: action.variant === "primary" ? "filled" : "text",
169
169
  size: "sm",
@@ -293,4 +293,4 @@ var useSnackbar = () => {
293
293
 
294
294
 
295
295
  exports.useSnackbarContext = useSnackbarContext; exports.SnackbarProvider = SnackbarProvider; exports.SnackbarContainer = SnackbarContainer; exports.useSnackbar = useSnackbar;
296
- //# sourceMappingURL=chunk-SKBKTNEQ.cjs.map
296
+ //# sourceMappingURL=chunk-XERBOFYZ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SKBKTNEQ.cjs","../src/components/Snackbar/Snackbar.context.tsx","../src/components/Snackbar/SnackbarContainer.component.tsx","../src/components/Snackbar/Snackbar.component.tsx","../src/components/Snackbar/Snackbar.hooks.ts"],"names":["jsx","useCallback"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SKBKTNEQ.cjs","sourcesContent":[null,"import React, { createContext, useContext, useState, useCallback, useRef } from 'react';\nimport type { SnackbarContextValue, SnackbarItem, SnackbarProps } from './Snackbar.types';\n\nconst SnackbarContext = createContext<SnackbarContextValue | undefined>(undefined);\n\nexport const useSnackbarContext = (): SnackbarContextValue => {\n const context = useContext(SnackbarContext);\n if (!context) {\n throw new Error('useSnackbarContext must be used within a SnackbarProvider');\n }\n return context;\n};\n\ninterface SnackbarProviderProps {\n children: React.ReactNode;\n}\n\nexport const SnackbarProvider: React.FC<SnackbarProviderProps> = ({ children }) => {\n const [snackbars, setSnackbars] = useState<SnackbarItem[]>([]);\n // Mirror state in a ref so removeSnackbar can read current items synchronously\n // without placing side-effects inside a setState updater (which StrictMode calls twice).\n const snackbarsRef = useRef<SnackbarItem[]>([]);\n snackbarsRef.current = snackbars;\n\n // ── Pausable auto-dismiss timers (SC 2.2.1 Timing Adjustable) ─────────────\n //\n // Auto-dismiss used to be a bare `setTimeout` that nothing could stop. That\n // is a Level A failure: a time limit the user can neither turn off, adjust\n // nor extend. It bites hardest exactly where a snackbar is most useful -\n // `action` puts a control (typically \"Undo\") inside something that deletes\n // itself on a stopwatch, so anyone reading slowly, using a screen reader,\n // or driving by keyboard can lose it mid-reach.\n //\n // Each timer therefore records when it started and how long is left, so it\n // can be cancelled on hover or focus and restarted with the remainder.\n const timers = useRef(\n new Map<string, { timeout: number; remaining: number; startedAt: number }>(),\n );\n\n const clearTimer = useCallback((id: string): void => {\n const timer = timers.current.get(id);\n if (timer) {\n window.clearTimeout(timer.timeout);\n timers.current.delete(id);\n }\n }, []);\n\n // `startTimer` needs `removeSnackbar`, and `removeSnackbar` needs\n // `clearTimer`. A ref breaks the cycle without reordering the file or\n // making every callback depend on the others' identity.\n const removeSnackbarRef = useRef<(id: string) => void>(() => {});\n\n const generateId = useCallback((): string => {\n return `snackbar-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }, []);\n\n const removeSnackbar = useCallback(\n (id: string): void => {\n // Read from ref - safe to call outside a setState updater.\n // Calling side-effects inside a setState updater is wrong: React StrictMode\n // deliberately invokes updaters twice, which would fire onClose 2× from here\n // plus 1× from SnackbarComponent.handleClose = 3× total.\n clearTimer(id);\n\n const snackbar = snackbarsRef.current.find((item) => item.id === id);\n if (snackbar?.onClose) {\n snackbar.onClose(id);\n }\n\n // Transition to exiting state, then remove after the CSS animation (300ms).\n setSnackbars((prev) =>\n prev.map((item) => (item.id === id ? { ...item, state: 'exiting' } : item)),\n );\n\n setTimeout(() => {\n setSnackbars((prev) => prev.filter((item) => item.id !== id));\n }, 300);\n },\n [clearTimer],\n );\n\n const startTimer = useCallback(\n (id: string, ms: number): void => {\n clearTimer(id);\n timers.current.set(id, {\n timeout: window.setTimeout(() => removeSnackbarRef.current(id), ms),\n remaining: ms,\n startedAt: Date.now(),\n });\n },\n [clearTimer],\n );\n\n const addSnackbar = useCallback(\n (snackbar: Omit<SnackbarProps, 'id'>): string => {\n const id = generateId();\n const newSnackbar: SnackbarItem = {\n ...snackbar,\n id,\n state: 'entering',\n createdAt: Date.now(),\n };\n\n setSnackbars((prev) => [newSnackbar, ...prev]); // Add to beginning (newest on top)\n\n // Use requestAnimationFrame to ensure DOM is rendered before animation\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setSnackbars((prev) =>\n prev.map((item) => (item.id === id ? { ...item, state: 'entered' } : item)),\n );\n });\n });\n\n // Auto-close if duration is set\n if (snackbar.duration && snackbar.duration > 0) {\n startTimer(id, snackbar.duration);\n }\n\n return id;\n },\n [generateId, startTimer],\n );\n\n // Pause on hover or focus, resume on leave or blur. Wired up by\n // `SnackbarContainer` - see the handlers there.\n const pauseSnackbar = useCallback((id: string): void => {\n const timer = timers.current.get(id);\n if (!timer) return;\n window.clearTimeout(timer.timeout);\n timers.current.set(id, {\n ...timer,\n // What is left of the countdown at the moment of pausing, floored at 0\n // so a timer that was already due does not resume with a negative delay.\n remaining: Math.max(0, timer.remaining - (Date.now() - timer.startedAt)),\n });\n }, []);\n\n const resumeSnackbar = useCallback(\n (id: string): void => {\n const timer = timers.current.get(id);\n if (!timer) return;\n startTimer(id, timer.remaining);\n },\n [startTimer],\n );\n\n removeSnackbarRef.current = removeSnackbar;\n\n const clearAllSnackbars = useCallback((): void => {\n // Transition all to exiting state\n setSnackbars((prev) => prev.map((item) => ({ ...item, state: 'exiting' })));\n\n // Remove all after animation completes (300ms to match CSS animation)\n setTimeout(() => {\n setSnackbars([]);\n }, 300);\n }, []);\n\n const contextValue: SnackbarContextValue = {\n snackbars,\n addSnackbar,\n removeSnackbar,\n clearAllSnackbars,\n pauseSnackbar,\n resumeSnackbar,\n };\n\n return <SnackbarContext.Provider value={contextValue}>{children}</SnackbarContext.Provider>;\n};\n\nSnackbarProvider.displayName = 'SnackbarProvider';\n","import React from 'react';\nimport { createPortal } from 'react-dom';\nimport { useSnackbarContext } from './Snackbar.context';\nimport { SnackbarComponent } from './Snackbar.component';\n\nexport const SnackbarContainer: React.FC = () => {\n const { snackbars, removeSnackbar, pauseSnackbar, resumeSnackbar } = useSnackbarContext();\n\n if (snackbars.length === 0) {\n return null;\n }\n\n return createPortal(\n <div className={'eidos-snackbar-container'}>\n {snackbars.map((snackbar) => (\n <div\n key={snackbar.id}\n className={'eidos-snackbar-wrapper'}\n // Holds the auto-dismiss countdown while the user is engaged with\n // this snackbar, which is what makes the time limit adjustable\n // (SC 2.2.1). Hover covers reading it; focus covers reaching its\n // action by keyboard - `onFocus`/`onBlur` bubble in React, so a\n // focused \"Undo\" button inside pauses the whole snackbar.\n onMouseEnter={() => pauseSnackbar(snackbar.id)}\n onMouseLeave={() => resumeSnackbar(snackbar.id)}\n onFocus={() => pauseSnackbar(snackbar.id)}\n onBlur={() => resumeSnackbar(snackbar.id)}\n >\n <SnackbarComponent snackbar={snackbar} onClose={removeSnackbar} />\n </div>\n ))}\n </div>,\n document.body,\n );\n};\n\nSnackbarContainer.displayName = 'SnackbarContainer';\n","import React from 'react';\nimport { X } from 'lucide-react';\nimport { Button } from '../Button/Button.component';\nimport type { SnackbarItem } from './Snackbar.types';\n\ninterface SnackbarComponentProps {\n snackbar: SnackbarItem;\n onClose: (id: string) => void;\n}\n\nexport const SnackbarComponent: React.FC<SnackbarComponentProps> = ({ snackbar, onClose }) => {\n const {\n id,\n variant = 'info',\n message,\n component: Component,\n componentProps,\n action,\n state,\n } = snackbar;\n\n const handleClose = () => {\n // onClose callback is fired by removeSnackbar (context) to cover both\n // manual and auto-close paths without duplication.\n onClose(id);\n };\n\n const handleActionClick = () => {\n if (action?.onClick) {\n action.onClick();\n }\n };\n\n // Render content based on type\n const renderContent = () => {\n if (message) {\n return <span className={'eidos-snackbar-message'}>{message}</span>;\n }\n if (Component) {\n return <Component {...(componentProps || {})} />;\n }\n return null;\n };\n\n return (\n <div\n className={`eidos-snackbar eidos-snackbar--${variant} eidos-snackbar--${state} ${snackbar.className || ''}`}\n role=\"alert\"\n aria-live=\"polite\"\n >\n <div className={'eidos-snackbar-content'}>{renderContent()}</div>\n\n {action && (\n <div className={'eidos-snackbar-actions'}>\n <Button\n variant={action.variant === 'primary' ? 'filled' : 'text'}\n size=\"sm\"\n onClick={handleActionClick}\n className={'eidos-snackbar-action-button'}\n >\n {action.label}\n </Button>\n </div>\n )}\n\n <button\n className={'eidos-snackbar-close-button'}\n onClick={handleClose}\n aria-label=\"Close notification\"\n type=\"button\"\n >\n <X className={'eidos-snackbar-close-icon'} />\n </button>\n </div>\n );\n};\n\nSnackbarComponent.displayName = 'SnackbarComponent';\n","import { useCallback } from 'react';\nimport { useSnackbarContext } from './Snackbar.context';\nimport type { UseSnackbarReturn, SnackbarProps } from './Snackbar.types';\n\nexport const useSnackbar = (): UseSnackbarReturn => {\n const { addSnackbar, removeSnackbar, clearAllSnackbars } = useSnackbarContext();\n\n const showSnackbar = useCallback(\n (snackbar: Omit<SnackbarProps, 'id'>): string => {\n return addSnackbar(snackbar);\n },\n [addSnackbar],\n );\n\n const hideSnackbar = useCallback(\n (id: string): void => {\n removeSnackbar(id);\n },\n [removeSnackbar],\n );\n\n const clearAll = useCallback((): void => {\n clearAllSnackbars();\n }, [clearAllSnackbars]);\n\n const showSuccess = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'success',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showError = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'danger',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showWarning = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'warning',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showInfo = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'info',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n return {\n showSnackbar,\n hideSnackbar,\n clearAll,\n showSuccess,\n showError,\n showWarning,\n showInfo,\n };\n};\n\nexport default useSnackbar;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XERBOFYZ.cjs","../src/components/Snackbar/Snackbar.context.tsx","../src/components/Snackbar/SnackbarContainer.component.tsx","../src/components/Snackbar/Snackbar.component.tsx","../src/components/Snackbar/Snackbar.hooks.ts"],"names":["jsx","useCallback"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XERBOFYZ.cjs","sourcesContent":[null,"import React, { createContext, useContext, useState, useCallback, useRef } from 'react';\nimport type { SnackbarContextValue, SnackbarItem, SnackbarProps } from './Snackbar.types';\n\nconst SnackbarContext = createContext<SnackbarContextValue | undefined>(undefined);\n\nexport const useSnackbarContext = (): SnackbarContextValue => {\n const context = useContext(SnackbarContext);\n if (!context) {\n throw new Error('useSnackbarContext must be used within a SnackbarProvider');\n }\n return context;\n};\n\ninterface SnackbarProviderProps {\n children: React.ReactNode;\n}\n\nexport const SnackbarProvider: React.FC<SnackbarProviderProps> = ({ children }) => {\n const [snackbars, setSnackbars] = useState<SnackbarItem[]>([]);\n // Mirror state in a ref so removeSnackbar can read current items synchronously\n // without placing side-effects inside a setState updater (which StrictMode calls twice).\n const snackbarsRef = useRef<SnackbarItem[]>([]);\n snackbarsRef.current = snackbars;\n\n // ── Pausable auto-dismiss timers (SC 2.2.1 Timing Adjustable) ─────────────\n //\n // Auto-dismiss used to be a bare `setTimeout` that nothing could stop. That\n // is a Level A failure: a time limit the user can neither turn off, adjust\n // nor extend. It bites hardest exactly where a snackbar is most useful -\n // `action` puts a control (typically \"Undo\") inside something that deletes\n // itself on a stopwatch, so anyone reading slowly, using a screen reader,\n // or driving by keyboard can lose it mid-reach.\n //\n // Each timer therefore records when it started and how long is left, so it\n // can be cancelled on hover or focus and restarted with the remainder.\n const timers = useRef(\n new Map<string, { timeout: number; remaining: number; startedAt: number }>(),\n );\n\n const clearTimer = useCallback((id: string): void => {\n const timer = timers.current.get(id);\n if (timer) {\n window.clearTimeout(timer.timeout);\n timers.current.delete(id);\n }\n }, []);\n\n // `startTimer` needs `removeSnackbar`, and `removeSnackbar` needs\n // `clearTimer`. A ref breaks the cycle without reordering the file or\n // making every callback depend on the others' identity.\n const removeSnackbarRef = useRef<(id: string) => void>(() => {});\n\n const generateId = useCallback((): string => {\n return `snackbar-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }, []);\n\n const removeSnackbar = useCallback(\n (id: string): void => {\n // Read from ref - safe to call outside a setState updater.\n // Calling side-effects inside a setState updater is wrong: React StrictMode\n // deliberately invokes updaters twice, which would fire onClose 2× from here\n // plus 1× from SnackbarComponent.handleClose = 3× total.\n clearTimer(id);\n\n const snackbar = snackbarsRef.current.find((item) => item.id === id);\n if (snackbar?.onClose) {\n snackbar.onClose(id);\n }\n\n // Transition to exiting state, then remove after the CSS animation (300ms).\n setSnackbars((prev) =>\n prev.map((item) => (item.id === id ? { ...item, state: 'exiting' } : item)),\n );\n\n setTimeout(() => {\n setSnackbars((prev) => prev.filter((item) => item.id !== id));\n }, 300);\n },\n [clearTimer],\n );\n\n const startTimer = useCallback(\n (id: string, ms: number): void => {\n clearTimer(id);\n timers.current.set(id, {\n timeout: window.setTimeout(() => removeSnackbarRef.current(id), ms),\n remaining: ms,\n startedAt: Date.now(),\n });\n },\n [clearTimer],\n );\n\n const addSnackbar = useCallback(\n (snackbar: Omit<SnackbarProps, 'id'>): string => {\n const id = generateId();\n const newSnackbar: SnackbarItem = {\n ...snackbar,\n id,\n state: 'entering',\n createdAt: Date.now(),\n };\n\n setSnackbars((prev) => [newSnackbar, ...prev]); // Add to beginning (newest on top)\n\n // Use requestAnimationFrame to ensure DOM is rendered before animation\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setSnackbars((prev) =>\n prev.map((item) => (item.id === id ? { ...item, state: 'entered' } : item)),\n );\n });\n });\n\n // Auto-close if duration is set\n if (snackbar.duration && snackbar.duration > 0) {\n startTimer(id, snackbar.duration);\n }\n\n return id;\n },\n [generateId, startTimer],\n );\n\n // Pause on hover or focus, resume on leave or blur. Wired up by\n // `SnackbarContainer` - see the handlers there.\n const pauseSnackbar = useCallback((id: string): void => {\n const timer = timers.current.get(id);\n if (!timer) return;\n window.clearTimeout(timer.timeout);\n timers.current.set(id, {\n ...timer,\n // What is left of the countdown at the moment of pausing, floored at 0\n // so a timer that was already due does not resume with a negative delay.\n remaining: Math.max(0, timer.remaining - (Date.now() - timer.startedAt)),\n });\n }, []);\n\n const resumeSnackbar = useCallback(\n (id: string): void => {\n const timer = timers.current.get(id);\n if (!timer) return;\n startTimer(id, timer.remaining);\n },\n [startTimer],\n );\n\n removeSnackbarRef.current = removeSnackbar;\n\n const clearAllSnackbars = useCallback((): void => {\n // Transition all to exiting state\n setSnackbars((prev) => prev.map((item) => ({ ...item, state: 'exiting' })));\n\n // Remove all after animation completes (300ms to match CSS animation)\n setTimeout(() => {\n setSnackbars([]);\n }, 300);\n }, []);\n\n const contextValue: SnackbarContextValue = {\n snackbars,\n addSnackbar,\n removeSnackbar,\n clearAllSnackbars,\n pauseSnackbar,\n resumeSnackbar,\n };\n\n return <SnackbarContext.Provider value={contextValue}>{children}</SnackbarContext.Provider>;\n};\n\nSnackbarProvider.displayName = 'SnackbarProvider';\n","import React from 'react';\nimport { createPortal } from 'react-dom';\nimport { useSnackbarContext } from './Snackbar.context';\nimport { SnackbarComponent } from './Snackbar.component';\n\nexport const SnackbarContainer: React.FC = () => {\n const { snackbars, removeSnackbar, pauseSnackbar, resumeSnackbar } = useSnackbarContext();\n\n if (snackbars.length === 0) {\n return null;\n }\n\n return createPortal(\n <div className={'eidos-snackbar-container'}>\n {snackbars.map((snackbar) => (\n <div\n key={snackbar.id}\n className={'eidos-snackbar-wrapper'}\n // Holds the auto-dismiss countdown while the user is engaged with\n // this snackbar, which is what makes the time limit adjustable\n // (SC 2.2.1). Hover covers reading it; focus covers reaching its\n // action by keyboard - `onFocus`/`onBlur` bubble in React, so a\n // focused \"Undo\" button inside pauses the whole snackbar.\n onMouseEnter={() => pauseSnackbar(snackbar.id)}\n onMouseLeave={() => resumeSnackbar(snackbar.id)}\n onFocus={() => pauseSnackbar(snackbar.id)}\n onBlur={() => resumeSnackbar(snackbar.id)}\n >\n <SnackbarComponent snackbar={snackbar} onClose={removeSnackbar} />\n </div>\n ))}\n </div>,\n document.body,\n );\n};\n\nSnackbarContainer.displayName = 'SnackbarContainer';\n","import React from 'react';\nimport { X } from 'lucide-react';\nimport { Button } from '../Button/Button.component';\nimport type { SnackbarItem } from './Snackbar.types';\n\ninterface SnackbarComponentProps {\n snackbar: SnackbarItem;\n onClose: (id: string) => void;\n}\n\nexport const SnackbarComponent: React.FC<SnackbarComponentProps> = ({ snackbar, onClose }) => {\n const {\n id,\n variant = 'info',\n message,\n component: Component,\n componentProps,\n action,\n state,\n } = snackbar;\n\n const handleClose = () => {\n // onClose callback is fired by removeSnackbar (context) to cover both\n // manual and auto-close paths without duplication.\n onClose(id);\n };\n\n const handleActionClick = () => {\n if (action?.onClick) {\n action.onClick();\n }\n };\n\n // Render content based on type\n const renderContent = () => {\n if (message) {\n return <span className={'eidos-snackbar-message'}>{message}</span>;\n }\n if (Component) {\n return <Component {...(componentProps || {})} />;\n }\n return null;\n };\n\n return (\n <div\n className={`eidos-snackbar eidos-snackbar--${variant} eidos-snackbar--${state} ${snackbar.className || ''}`}\n role=\"alert\"\n aria-live=\"polite\"\n >\n <div className={'eidos-snackbar-content'}>{renderContent()}</div>\n\n {action && (\n <div className={'eidos-snackbar-actions'}>\n <Button\n variant={action.variant === 'primary' ? 'filled' : 'text'}\n size=\"sm\"\n onClick={handleActionClick}\n className={'eidos-snackbar-action-button'}\n >\n {action.label}\n </Button>\n </div>\n )}\n\n <button\n className={'eidos-snackbar-close-button'}\n onClick={handleClose}\n aria-label=\"Close notification\"\n type=\"button\"\n >\n <X className={'eidos-snackbar-close-icon'} />\n </button>\n </div>\n );\n};\n\nSnackbarComponent.displayName = 'SnackbarComponent';\n","import { useCallback } from 'react';\nimport { useSnackbarContext } from './Snackbar.context';\nimport type { UseSnackbarReturn, SnackbarProps } from './Snackbar.types';\n\nexport const useSnackbar = (): UseSnackbarReturn => {\n const { addSnackbar, removeSnackbar, clearAllSnackbars } = useSnackbarContext();\n\n const showSnackbar = useCallback(\n (snackbar: Omit<SnackbarProps, 'id'>): string => {\n return addSnackbar(snackbar);\n },\n [addSnackbar],\n );\n\n const hideSnackbar = useCallback(\n (id: string): void => {\n removeSnackbar(id);\n },\n [removeSnackbar],\n );\n\n const clearAll = useCallback((): void => {\n clearAllSnackbars();\n }, [clearAllSnackbars]);\n\n const showSuccess = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'success',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showError = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'danger',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showWarning = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'warning',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n const showInfo = useCallback(\n (message: string, options?: Partial<Omit<SnackbarProps, 'id' | 'message' | 'variant'>>) => {\n return showSnackbar({\n message,\n variant: 'info',\n duration: 5000,\n ...options,\n });\n },\n [showSnackbar],\n );\n\n return {\n showSnackbar,\n hideSnackbar,\n clearAll,\n showSuccess,\n showError,\n showWarning,\n showInfo,\n };\n};\n\nexport default useSnackbar;\n"]}