eidos-ui 3.3.0 → 3.4.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 (322) hide show
  1. package/README.md +17 -5
  2. package/dist/{Input.types-XrJTI0JC.d.ts → Input.types-CSDMaZNe.d.ts} +10 -0
  3. package/dist/{Input.types-DFZNyJGF.d.cts → Input.types-D7-sWq-q.d.cts} +10 -0
  4. package/dist/{Select.types-CAkT2ACt.d.cts → Select.types-2p-mpaBL.d.cts} +1 -1
  5. package/dist/{Select.types-B4kDpkGB.d.ts → Select.types-BqaD7bmB.d.ts} +1 -1
  6. package/dist/alert/index.cjs +5 -5
  7. package/dist/alert/index.js +4 -4
  8. package/dist/button/index.cjs +4 -4
  9. package/dist/button/index.js +3 -3
  10. package/dist/checkbox/index.cjs +2 -2
  11. package/dist/checkbox/index.js +1 -1
  12. package/dist/chip/index.cjs +4 -4
  13. package/dist/chip/index.js +3 -3
  14. package/dist/{chunk-RYOQYYDO.js → chunk-22EKNTWJ.js} +9 -5
  15. package/dist/chunk-22EKNTWJ.js.map +1 -0
  16. package/dist/{chunk-3E7TKBQI.cjs → chunk-27UANGPW.cjs} +14 -6
  17. package/dist/chunk-27UANGPW.cjs.map +1 -0
  18. package/dist/{chunk-CJGFRPAS.cjs → chunk-3QQKWXMV.cjs} +4 -4
  19. package/dist/chunk-3QQKWXMV.cjs.map +1 -0
  20. package/dist/{chunk-Q5KQVBQP.cjs → chunk-3S33BIN2.cjs} +18 -16
  21. package/dist/chunk-3S33BIN2.cjs.map +1 -0
  22. package/dist/{chunk-ZPDETHQ7.js → chunk-3WV6RTXX.js} +3 -3
  23. package/dist/{chunk-ZPDETHQ7.js.map → chunk-3WV6RTXX.js.map} +1 -1
  24. package/dist/chunk-44T4HMFU.js +88 -0
  25. package/dist/chunk-44T4HMFU.js.map +1 -0
  26. package/dist/{chunk-3EPND5M2.cjs → chunk-4LET2PEX.cjs} +12 -12
  27. package/dist/chunk-4LET2PEX.cjs.map +1 -0
  28. package/dist/{chunk-R4M6SNUU.cjs → chunk-5E4DYTZ3.cjs} +48 -37
  29. package/dist/chunk-5E4DYTZ3.cjs.map +1 -0
  30. package/dist/{chunk-I5XR2V4A.cjs → chunk-5V7DXZWT.cjs} +79 -76
  31. package/dist/chunk-5V7DXZWT.cjs.map +1 -0
  32. package/dist/{chunk-6CPCNQKV.js → chunk-67EXHO4T.js} +2 -2
  33. package/dist/{chunk-K7T25Y3A.cjs → chunk-6LCYO2HG.cjs} +10 -9
  34. package/dist/chunk-6LCYO2HG.cjs.map +1 -0
  35. package/dist/{chunk-ZNWIHUHZ.cjs → chunk-7KVXXHFX.cjs} +9 -3
  36. package/dist/chunk-7KVXXHFX.cjs.map +1 -0
  37. package/dist/{chunk-55J4LEKT.cjs → chunk-AHVDTWVM.cjs} +11 -8
  38. package/dist/chunk-AHVDTWVM.cjs.map +1 -0
  39. package/dist/{chunk-OAPOGYA5.cjs → chunk-AXPEJ5IG.cjs} +7 -4
  40. package/dist/chunk-AXPEJ5IG.cjs.map +1 -0
  41. package/dist/{chunk-GDQWSCJQ.js → chunk-BBU7QG7L.js} +4 -4
  42. package/dist/{chunk-T2KB3DJJ.js → chunk-BGBAZMFE.js} +9 -3
  43. package/dist/chunk-BGBAZMFE.js.map +1 -0
  44. package/dist/{chunk-EH42ND5Q.cjs → chunk-BIANFFJL.cjs} +5 -5
  45. package/dist/{chunk-EH42ND5Q.cjs.map → chunk-BIANFFJL.cjs.map} +1 -1
  46. package/dist/{chunk-O2AACVSF.js → chunk-BO4LTIJI.js} +3 -3
  47. package/dist/{chunk-G5N3C6AU.cjs → chunk-CBT3TTQR.cjs} +36 -36
  48. package/dist/{chunk-G5N3C6AU.cjs.map → chunk-CBT3TTQR.cjs.map} +1 -1
  49. package/dist/{chunk-JRLUZDKA.js → chunk-CEC3GO4R.js} +20 -7
  50. package/dist/chunk-CEC3GO4R.js.map +1 -0
  51. package/dist/{chunk-BNVQ34FD.js → chunk-CP335H4Q.js} +4 -4
  52. package/dist/{chunk-H6CWTMAH.js → chunk-DGXHR6FV.js} +5 -2
  53. package/dist/chunk-DGXHR6FV.js.map +1 -0
  54. package/dist/{chunk-OU7DCYIA.js → chunk-DMOZURHV.js} +2 -2
  55. package/dist/{chunk-L5W2XC6M.js → chunk-EWTMX4DK.js} +8 -8
  56. package/dist/{chunk-SXGCRHBZ.js → chunk-FDMYDIGI.js} +8 -6
  57. package/dist/chunk-FDMYDIGI.js.map +1 -0
  58. package/dist/{chunk-AOGNTG3W.cjs → chunk-FODRHCRP.cjs} +5 -4
  59. package/dist/chunk-FODRHCRP.cjs.map +1 -0
  60. package/dist/{chunk-E7FGY7BJ.js → chunk-FPLX5FIK.js} +4 -4
  61. package/dist/{chunk-NEYLNRVG.cjs → chunk-FXMRDNR6.cjs} +21 -16
  62. package/dist/chunk-FXMRDNR6.cjs.map +1 -0
  63. package/dist/{chunk-VXO2MYYH.cjs → chunk-G74DHVHV.cjs} +9 -9
  64. package/dist/{chunk-VXO2MYYH.cjs.map → chunk-G74DHVHV.cjs.map} +1 -1
  65. package/dist/{chunk-O3LG7B2J.cjs → chunk-GMDVW76A.cjs} +13 -5
  66. package/dist/chunk-GMDVW76A.cjs.map +1 -0
  67. package/dist/{chunk-NQ4L6PYW.js → chunk-GOF6A4M4.js} +2 -2
  68. package/dist/{chunk-NQ4L6PYW.js.map → chunk-GOF6A4M4.js.map} +1 -1
  69. package/dist/{chunk-TQGO5WK6.js → chunk-GPK2THIG.js} +7 -6
  70. package/dist/chunk-GPK2THIG.js.map +1 -0
  71. package/dist/{chunk-HYDNZXPU.js → chunk-H2RS6Z6Q.js} +41 -25
  72. package/dist/{chunk-HYDNZXPU.js.map → chunk-H2RS6Z6Q.js.map} +1 -1
  73. package/dist/{chunk-OJDYTCMO.cjs → chunk-H5D3ZFBM.cjs} +68 -61
  74. package/dist/chunk-H5D3ZFBM.cjs.map +1 -0
  75. package/dist/{chunk-MHZIBV2C.js → chunk-IXVM2XNS.js} +7 -6
  76. package/dist/chunk-IXVM2XNS.js.map +1 -0
  77. package/dist/{chunk-MCWTQT3M.cjs → chunk-JKBBHAIC.cjs} +21 -20
  78. package/dist/chunk-JKBBHAIC.cjs.map +1 -0
  79. package/dist/{chunk-QQIALSTX.cjs → chunk-KPMIIGHH.cjs} +27 -17
  80. package/dist/chunk-KPMIIGHH.cjs.map +1 -0
  81. package/dist/{chunk-QJRKXT54.cjs → chunk-KWDOOMHR.cjs} +7 -7
  82. package/dist/{chunk-QJRKXT54.cjs.map → chunk-KWDOOMHR.cjs.map} +1 -1
  83. package/dist/{chunk-HALVTKW7.js → chunk-LH5A2SIX.js} +78 -75
  84. package/dist/{chunk-HALVTKW7.js.map → chunk-LH5A2SIX.js.map} +1 -1
  85. package/dist/{chunk-27ZWUEPE.js → chunk-LIKSLFX6.js} +20 -9
  86. package/dist/chunk-LIKSLFX6.js.map +1 -0
  87. package/dist/{chunk-Z5U4NJTW.cjs → chunk-MLUHM3VG.cjs} +48 -19
  88. package/dist/chunk-MLUHM3VG.cjs.map +1 -0
  89. package/dist/{chunk-M7PF4FKQ.js → chunk-MP7LN5CW.js} +6 -3
  90. package/dist/chunk-MP7LN5CW.js.map +1 -0
  91. package/dist/{chunk-ACNTH2OQ.cjs → chunk-MW4YZVP5.cjs} +96 -97
  92. package/dist/chunk-MW4YZVP5.cjs.map +1 -0
  93. package/dist/{chunk-ZXJU3TQD.cjs → chunk-N5ZWQJTM.cjs} +11 -10
  94. package/dist/chunk-N5ZWQJTM.cjs.map +1 -0
  95. package/dist/{chunk-OGC44JFZ.cjs → chunk-N77VOYCK.cjs} +7 -7
  96. package/dist/{chunk-OGC44JFZ.cjs.map → chunk-N77VOYCK.cjs.map} +1 -1
  97. package/dist/{chunk-OAK7HFOJ.js → chunk-NACA72XI.js} +21 -16
  98. package/dist/chunk-NACA72XI.js.map +1 -0
  99. package/dist/{chunk-TEVZHN5I.cjs → chunk-NCZUQFKL.cjs} +28 -15
  100. package/dist/chunk-NCZUQFKL.cjs.map +1 -0
  101. package/dist/{chunk-NZO2I5AL.cjs → chunk-NF55JG2V.cjs} +7 -7
  102. package/dist/{chunk-NZO2I5AL.cjs.map → chunk-NF55JG2V.cjs.map} +1 -1
  103. package/dist/{chunk-ZWAFL2B3.cjs → chunk-O46UJUZM.cjs} +21 -19
  104. package/dist/chunk-O46UJUZM.cjs.map +1 -0
  105. package/dist/{chunk-25PSIDPC.js → chunk-OIXPYXR6.js} +79 -21
  106. package/dist/chunk-OIXPYXR6.js.map +1 -0
  107. package/dist/chunk-OWWPHXAF.cjs +88 -0
  108. package/dist/chunk-OWWPHXAF.cjs.map +1 -0
  109. package/dist/{chunk-65PC3YDT.cjs → chunk-OZQKWDFH.cjs} +7 -7
  110. package/dist/{chunk-65PC3YDT.cjs.map → chunk-OZQKWDFH.cjs.map} +1 -1
  111. package/dist/{chunk-P2JM22BF.cjs → chunk-P7J6WZTO.cjs} +3 -3
  112. package/dist/{chunk-P2JM22BF.cjs.map → chunk-P7J6WZTO.cjs.map} +1 -1
  113. package/dist/{chunk-KQJ3IAFT.js → chunk-PG2EII6U.js} +2 -2
  114. package/dist/{chunk-E6SE552P.cjs → chunk-RUDXHT76.cjs} +41 -25
  115. package/dist/chunk-RUDXHT76.cjs.map +1 -0
  116. package/dist/{chunk-S4S4LB63.js → chunk-SCLFJHN2.js} +11 -3
  117. package/dist/chunk-SCLFJHN2.js.map +1 -0
  118. package/dist/{chunk-WKDT2XFQ.js → chunk-SFRILDXY.js} +4 -4
  119. package/dist/{chunk-J5RMLZ4P.cjs → chunk-SKBKTNEQ.cjs} +80 -22
  120. package/dist/chunk-SKBKTNEQ.cjs.map +1 -0
  121. package/dist/{chunk-YW65NAKH.cjs → chunk-SKLMBE3Z.cjs} +7 -7
  122. package/dist/{chunk-YW65NAKH.cjs.map → chunk-SKLMBE3Z.cjs.map} +1 -1
  123. package/dist/{chunk-PY2376XI.js → chunk-SVCWSW2F.js} +8 -8
  124. package/dist/chunk-SVCWSW2F.js.map +1 -0
  125. package/dist/{chunk-2RXQMQYW.js → chunk-SXL4DMZ7.js} +27 -17
  126. package/dist/{chunk-2RXQMQYW.js.map → chunk-SXL4DMZ7.js.map} +1 -1
  127. package/dist/{chunk-7SVDYEKM.js → chunk-TLECMA3B.js} +5 -4
  128. package/dist/{chunk-7SVDYEKM.js.map → chunk-TLECMA3B.js.map} +1 -1
  129. package/dist/{chunk-FXZJL2AM.cjs → chunk-TWXZKUCG.cjs} +9 -5
  130. package/dist/chunk-TWXZKUCG.cjs.map +1 -0
  131. package/dist/{chunk-GG5YGESX.js → chunk-TYGVKSL6.js} +4 -3
  132. package/dist/chunk-TYGVKSL6.js.map +1 -0
  133. package/dist/{chunk-TTO7JXDB.cjs → chunk-TYSNOPMQ.cjs} +3 -3
  134. package/dist/{chunk-TTO7JXDB.cjs.map → chunk-TYSNOPMQ.cjs.map} +1 -1
  135. package/dist/{chunk-ZZSPQFMX.js → chunk-U233KQZX.js} +2 -1
  136. package/dist/{chunk-ZZSPQFMX.js.map → chunk-U233KQZX.js.map} +1 -1
  137. package/dist/{chunk-5HUZYY54.js → chunk-UDRAM6JU.js} +13 -5
  138. package/dist/chunk-UDRAM6JU.js.map +1 -0
  139. package/dist/{chunk-3EUTXQDB.js → chunk-UGTKZ7HP.js} +30 -23
  140. package/dist/chunk-UGTKZ7HP.js.map +1 -0
  141. package/dist/{chunk-FKXKBJUI.cjs → chunk-VHO6JPRR.cjs} +2 -1
  142. package/dist/chunk-VHO6JPRR.cjs.map +1 -0
  143. package/dist/chunk-VHSTB7GB.cjs +123 -0
  144. package/dist/chunk-VHSTB7GB.cjs.map +1 -0
  145. package/dist/{chunk-VPRUHE35.js → chunk-VRNCXYEP.js} +4 -4
  146. package/dist/{chunk-ITXDSQT2.js → chunk-WGCGSM5P.js} +96 -97
  147. package/dist/chunk-WGCGSM5P.js.map +1 -0
  148. package/dist/chunk-WT4WBHBR.js +123 -0
  149. package/dist/chunk-WT4WBHBR.js.map +1 -0
  150. package/dist/{chunk-L23WWO3D.js → chunk-X6UKU3C3.js} +2 -2
  151. package/dist/{chunk-5WH3JMR5.js → chunk-XK5USBHB.js} +7 -4
  152. package/dist/chunk-XK5USBHB.js.map +1 -0
  153. package/dist/{chunk-ND526PMG.cjs → chunk-XMXL6NVJ.cjs} +11 -11
  154. package/dist/{chunk-ND526PMG.cjs.map → chunk-XMXL6NVJ.cjs.map} +1 -1
  155. package/dist/{chunk-YV57RSPD.js → chunk-XO7WTE6O.js} +47 -18
  156. package/dist/chunk-XO7WTE6O.js.map +1 -0
  157. package/dist/{chunk-3CXULV4G.cjs → chunk-XRVRGRC3.cjs} +2 -2
  158. package/dist/{chunk-3CXULV4G.cjs.map → chunk-XRVRGRC3.cjs.map} +1 -1
  159. package/dist/{chunk-GPRJYPIG.js → chunk-Y7VSWZWM.js} +6 -4
  160. package/dist/chunk-Y7VSWZWM.js.map +1 -0
  161. package/dist/{chunk-2QEKSRVX.js → chunk-YIBPOLNN.js} +4 -4
  162. package/dist/{chunk-IJ4DXG7D.cjs → chunk-YJNQLH2L.cjs} +5 -2
  163. package/dist/chunk-YJNQLH2L.cjs.map +1 -0
  164. package/dist/{chunk-6CSUUQ5M.cjs → chunk-YUH56CJI.cjs} +6 -6
  165. package/dist/{chunk-6CSUUQ5M.cjs.map → chunk-YUH56CJI.cjs.map} +1 -1
  166. package/dist/color-picker/index.cjs +4 -4
  167. package/dist/color-picker/index.d.cts +8 -0
  168. package/dist/color-picker/index.d.ts +8 -0
  169. package/dist/color-picker/index.js +3 -3
  170. package/dist/combobox/index.cjs +6 -6
  171. package/dist/combobox/index.js +5 -5
  172. package/dist/command-palette/index.cjs +3 -2
  173. package/dist/command-palette/index.cjs.map +1 -1
  174. package/dist/command-palette/index.js +2 -1
  175. package/dist/context-menu/index.cjs +6 -6
  176. package/dist/context-menu/index.js +5 -5
  177. package/dist/conversation/index.cjs +8 -8
  178. package/dist/conversation/index.js +7 -7
  179. package/dist/data-grid/index.cjs +16 -16
  180. package/dist/data-grid/index.d.cts +2 -2
  181. package/dist/data-grid/index.d.ts +2 -2
  182. package/dist/data-grid/index.js +15 -15
  183. package/dist/date-picker/index.cjs +8 -8
  184. package/dist/date-picker/index.d.cts +1 -1
  185. package/dist/date-picker/index.d.ts +1 -1
  186. package/dist/date-picker/index.js +7 -7
  187. package/dist/drawer/index.cjs +5 -5
  188. package/dist/drawer/index.js +4 -4
  189. package/dist/dropdown/index.cjs +3 -3
  190. package/dist/dropdown/index.js +2 -2
  191. package/dist/file-upload/index.cjs +2 -2
  192. package/dist/file-upload/index.js +1 -1
  193. package/dist/header/index.cjs +6 -6
  194. package/dist/header/index.js +5 -5
  195. package/dist/index.cjs +42 -42
  196. package/dist/index.css +93 -38
  197. package/dist/index.d.cts +2 -2
  198. package/dist/index.d.ts +2 -2
  199. package/dist/index.js +41 -41
  200. package/dist/inline-edit/index.cjs +5 -5
  201. package/dist/inline-edit/index.js +4 -4
  202. package/dist/input/index.cjs +4 -4
  203. package/dist/input/index.d.cts +2 -2
  204. package/dist/input/index.d.ts +2 -2
  205. package/dist/input/index.js +3 -3
  206. package/dist/menu/index.cjs +5 -5
  207. package/dist/menu/index.js +4 -4
  208. package/dist/modal/index.cjs +5 -5
  209. package/dist/modal/index.js +4 -4
  210. package/dist/navigation/index.cjs +5 -5
  211. package/dist/navigation/index.js +4 -4
  212. package/dist/number-input/index.cjs +3 -3
  213. package/dist/number-input/index.js +2 -2
  214. package/dist/o-t-p-input/index.cjs +2 -2
  215. package/dist/o-t-p-input/index.js +1 -1
  216. package/dist/page-layout/index.cjs +12 -12
  217. package/dist/page-layout/index.js +11 -11
  218. package/dist/pagination/index.cjs +8 -8
  219. package/dist/pagination/index.js +7 -7
  220. package/dist/popover/index.cjs +3 -2
  221. package/dist/popover/index.cjs.map +1 -1
  222. package/dist/popover/index.js +2 -1
  223. package/dist/radio/index.cjs +2 -2
  224. package/dist/radio/index.js +1 -1
  225. package/dist/segmented-control/index.cjs +4 -4
  226. package/dist/segmented-control/index.js +3 -3
  227. package/dist/select/index.cjs +6 -6
  228. package/dist/select/index.d.cts +3 -3
  229. package/dist/select/index.d.ts +3 -3
  230. package/dist/select/index.js +5 -5
  231. package/dist/snackbar/index.cjs +5 -5
  232. package/dist/snackbar/index.d.cts +4 -0
  233. package/dist/snackbar/index.d.ts +4 -0
  234. package/dist/snackbar/index.js +4 -4
  235. package/dist/split-button/index.cjs +6 -6
  236. package/dist/split-button/index.js +5 -5
  237. package/dist/table/index.cjs +14 -14
  238. package/dist/table/index.js +13 -13
  239. package/dist/tabs/index.cjs +3 -3
  240. package/dist/tabs/index.js +2 -2
  241. package/dist/tag-input/index.cjs +6 -6
  242. package/dist/tag-input/index.js +5 -5
  243. package/dist/textarea/index.cjs +4 -4
  244. package/dist/textarea/index.js +3 -3
  245. package/dist/theme-editor/index.cjs +11 -11
  246. package/dist/theme-editor/index.js +10 -10
  247. package/dist/theme-provider/index.cjs +3 -3
  248. package/dist/theme-provider/index.js +2 -2
  249. package/dist/toolbar/index.cjs +8 -8
  250. package/dist/toolbar/index.js +7 -7
  251. package/dist/tooltip/index.cjs +3 -2
  252. package/dist/tooltip/index.cjs.map +1 -1
  253. package/dist/tooltip/index.js +2 -1
  254. package/dist/virtual-list/index.cjs +2 -2
  255. package/dist/virtual-list/index.js +1 -1
  256. package/package.json +7 -2
  257. package/dist/chunk-25PSIDPC.js.map +0 -1
  258. package/dist/chunk-27ZWUEPE.js.map +0 -1
  259. package/dist/chunk-3E7TKBQI.cjs.map +0 -1
  260. package/dist/chunk-3EPND5M2.cjs.map +0 -1
  261. package/dist/chunk-3EUTXQDB.js.map +0 -1
  262. package/dist/chunk-55J4LEKT.cjs.map +0 -1
  263. package/dist/chunk-5HUZYY54.js.map +0 -1
  264. package/dist/chunk-5WH3JMR5.js.map +0 -1
  265. package/dist/chunk-ACNTH2OQ.cjs.map +0 -1
  266. package/dist/chunk-AOGNTG3W.cjs.map +0 -1
  267. package/dist/chunk-CJGFRPAS.cjs.map +0 -1
  268. package/dist/chunk-CUJBNRJJ.cjs +0 -38
  269. package/dist/chunk-CUJBNRJJ.cjs.map +0 -1
  270. package/dist/chunk-E6SE552P.cjs.map +0 -1
  271. package/dist/chunk-EAJHEYZM.js +0 -38
  272. package/dist/chunk-EAJHEYZM.js.map +0 -1
  273. package/dist/chunk-FKXKBJUI.cjs.map +0 -1
  274. package/dist/chunk-FXZJL2AM.cjs.map +0 -1
  275. package/dist/chunk-GG5YGESX.js.map +0 -1
  276. package/dist/chunk-GPRJYPIG.js.map +0 -1
  277. package/dist/chunk-H6CWTMAH.js.map +0 -1
  278. package/dist/chunk-I5XR2V4A.cjs.map +0 -1
  279. package/dist/chunk-IJ4DXG7D.cjs.map +0 -1
  280. package/dist/chunk-ITXDSQT2.js.map +0 -1
  281. package/dist/chunk-J5RMLZ4P.cjs.map +0 -1
  282. package/dist/chunk-JRLUZDKA.js.map +0 -1
  283. package/dist/chunk-K7T25Y3A.cjs.map +0 -1
  284. package/dist/chunk-L5Q6UDY3.js +0 -69
  285. package/dist/chunk-L5Q6UDY3.js.map +0 -1
  286. package/dist/chunk-M7PF4FKQ.js.map +0 -1
  287. package/dist/chunk-MCWTQT3M.cjs.map +0 -1
  288. package/dist/chunk-MHZIBV2C.js.map +0 -1
  289. package/dist/chunk-NEYLNRVG.cjs.map +0 -1
  290. package/dist/chunk-O3LG7B2J.cjs.map +0 -1
  291. package/dist/chunk-OAK7HFOJ.js.map +0 -1
  292. package/dist/chunk-OAPOGYA5.cjs.map +0 -1
  293. package/dist/chunk-OJDYTCMO.cjs.map +0 -1
  294. package/dist/chunk-PY2376XI.js.map +0 -1
  295. package/dist/chunk-Q5KQVBQP.cjs.map +0 -1
  296. package/dist/chunk-QQIALSTX.cjs.map +0 -1
  297. package/dist/chunk-R4M6SNUU.cjs.map +0 -1
  298. package/dist/chunk-RYOQYYDO.js.map +0 -1
  299. package/dist/chunk-S4S4LB63.js.map +0 -1
  300. package/dist/chunk-SXGCRHBZ.js.map +0 -1
  301. package/dist/chunk-T2KB3DJJ.js.map +0 -1
  302. package/dist/chunk-TEVZHN5I.cjs.map +0 -1
  303. package/dist/chunk-TQGO5WK6.js.map +0 -1
  304. package/dist/chunk-WWYJNWJY.cjs +0 -69
  305. package/dist/chunk-WWYJNWJY.cjs.map +0 -1
  306. package/dist/chunk-YV57RSPD.js.map +0 -1
  307. package/dist/chunk-Z5U4NJTW.cjs.map +0 -1
  308. package/dist/chunk-ZNWIHUHZ.cjs.map +0 -1
  309. package/dist/chunk-ZWAFL2B3.cjs.map +0 -1
  310. package/dist/chunk-ZXJU3TQD.cjs.map +0 -1
  311. /package/dist/{chunk-6CPCNQKV.js.map → chunk-67EXHO4T.js.map} +0 -0
  312. /package/dist/{chunk-GDQWSCJQ.js.map → chunk-BBU7QG7L.js.map} +0 -0
  313. /package/dist/{chunk-O2AACVSF.js.map → chunk-BO4LTIJI.js.map} +0 -0
  314. /package/dist/{chunk-BNVQ34FD.js.map → chunk-CP335H4Q.js.map} +0 -0
  315. /package/dist/{chunk-OU7DCYIA.js.map → chunk-DMOZURHV.js.map} +0 -0
  316. /package/dist/{chunk-L5W2XC6M.js.map → chunk-EWTMX4DK.js.map} +0 -0
  317. /package/dist/{chunk-E7FGY7BJ.js.map → chunk-FPLX5FIK.js.map} +0 -0
  318. /package/dist/{chunk-KQJ3IAFT.js.map → chunk-PG2EII6U.js.map} +0 -0
  319. /package/dist/{chunk-WKDT2XFQ.js.map → chunk-SFRILDXY.js.map} +0 -0
  320. /package/dist/{chunk-VPRUHE35.js.map → chunk-VRNCXYEP.js.map} +0 -0
  321. /package/dist/{chunk-L23WWO3D.js.map → chunk-X6UKU3C3.js.map} +0 -0
  322. /package/dist/{chunk-2QEKSRVX.js.map → chunk-YIBPOLNN.js.map} +0 -0
@@ -0,0 +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,6 +1,6 @@
1
1
  import {
2
2
  Input
3
- } from "./chunk-JRLUZDKA.js";
3
+ } from "./chunk-CEC3GO4R.js";
4
4
 
5
5
  // src/components/InlineEdit/InlineEdit.component.tsx
6
6
  import { useState, useRef, useCallback } from "react";
@@ -95,7 +95,7 @@ var InlineEdit = ({
95
95
  {
96
96
  role: "button",
97
97
  tabIndex: disabled ? void 0 : 0,
98
- "aria-label": disabled ? void 0 : "Click to edit",
98
+ "aria-label": disabled || isEmpty ? void 0 : `${value}, click to edit`,
99
99
  className: [
100
100
  "eidos-inline-edit",
101
101
  `eidos-inline-edit--${size}`,
@@ -122,4 +122,4 @@ var InlineEdit = ({
122
122
  export {
123
123
  InlineEdit
124
124
  };
125
- //# sourceMappingURL=chunk-ZPDETHQ7.js.map
125
+ //# sourceMappingURL=chunk-3WV6RTXX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/InlineEdit/InlineEdit.component.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback } from 'react';\nimport { Pencil } from 'lucide-react';\nimport { Input } from '../Input/Input.component';\nimport type { InlineEditProps } from './InlineEdit.types';\nimport './InlineEdit.scss';\n\nexport const InlineEdit: React.FC<InlineEditProps> = ({\n value,\n onChange,\n onConfirm,\n onCancel,\n editing: editingProp,\n onEditingChange,\n type = 'text',\n placeholder,\n trigger = 'click',\n size = 'md',\n fullWidth = false,\n disabled = false,\n confirmOnBlur = true,\n renderDisplay,\n showEditIcon = true,\n inputVariant = 'outlined',\n className = '',\n}) => {\n const isControlled = editingProp !== undefined;\n const [internalEditing, setInternalEditing] = useState(false);\n const editing = isControlled ? editingProp : internalEditing;\n\n // Track the value at the moment editing begins so Esc can revert to it.\n const valueAtEditStart = useRef(value);\n\n // Local draft while editing - keeps Input value in sync without forcing the\n // parent to wire `onChange` just to see live keystrokes.\n const [draft, setDraft] = useState(value);\n\n // ------------------------------------------------------------------\n // Editing state helpers\n // ------------------------------------------------------------------\n\n const openEdit = useCallback(() => {\n if (disabled) return;\n valueAtEditStart.current = value;\n setDraft(value);\n if (!isControlled) setInternalEditing(true);\n onEditingChange?.(true);\n }, [disabled, isControlled, onEditingChange, value]);\n\n const closeEdit = useCallback(() => {\n if (!isControlled) setInternalEditing(false);\n onEditingChange?.(false);\n }, [isControlled, onEditingChange]);\n\n const confirm = useCallback(() => {\n closeEdit();\n onConfirm?.(draft);\n }, [closeEdit, draft, onConfirm]);\n\n const cancel = useCallback(() => {\n // Revert the draft AND propagate the original value so the parent stays in\n // sync when `onChange` is used to maintain external state.\n setDraft(valueAtEditStart.current);\n onChange?.(valueAtEditStart.current);\n closeEdit();\n onCancel?.();\n }, [closeEdit, onChange, onCancel]);\n\n // ------------------------------------------------------------------\n // Input handlers\n // ------------------------------------------------------------------\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setDraft(e.target.value);\n onChange?.(e.target.value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n // Stop propagation so parent grids/tables don't double-handle Enter.\n e.stopPropagation();\n confirm();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n cancel();\n }\n // Tab and Arrow keys intentionally bubble - consumers like DataGrid own\n // Tab-to-next-cell and arrow-key navigation at the container level.\n };\n\n const handleBlur = () => {\n if (confirmOnBlur) confirm();\n };\n\n // ------------------------------------------------------------------\n // Trigger props for display mode\n // ------------------------------------------------------------------\n\n const displayTriggerProps =\n trigger === 'doubleClick' ? { onDoubleClick: openEdit } : { onClick: openEdit };\n\n // ------------------------------------------------------------------\n // Render: edit mode\n // ------------------------------------------------------------------\n\n if (editing) {\n return (\n <Input\n type={type}\n variant={inputVariant}\n size={size}\n value={draft}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n placeholder={placeholder}\n fullWidth={fullWidth}\n clearable={false}\n autoFocus\n className={`eidos-inline-edit eidos-inline-edit--editing ${className}`}\n />\n );\n }\n\n // ------------------------------------------------------------------\n // Render: display mode\n // ------------------------------------------------------------------\n\n const isEmpty = !value;\n\n return (\n <span\n role=\"button\"\n tabIndex={disabled ? undefined : 0}\n aria-label={disabled ? undefined : 'Click to edit'}\n className={[\n 'eidos-inline-edit',\n `eidos-inline-edit--${size}`,\n fullWidth ? 'eidos-inline-edit--full-width' : '',\n disabled ? 'eidos-inline-edit--disabled' : '',\n isEmpty ? 'eidos-inline-edit--empty' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n {...(disabled ? {} : displayTriggerProps)}\n onKeyDown={\n disabled\n ? undefined\n : (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openEdit();\n }\n }\n }\n >\n <span className=\"eidos-inline-edit__text\">\n {renderDisplay\n ? renderDisplay(value)\n : value || <span className=\"eidos-inline-edit__placeholder\">{placeholder}</span>}\n </span>\n {!disabled && showEditIcon && (\n <span className=\"eidos-inline-edit__icon\" aria-hidden>\n <Pencil />\n </span>\n )}\n </span>\n );\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/components/InlineEdit/InlineEdit.component.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback } from 'react';\nimport { Pencil } from 'lucide-react';\nimport { Input } from '../Input/Input.component';\nimport type { InlineEditProps } from './InlineEdit.types';\nimport './InlineEdit.scss';\n\nexport const InlineEdit: React.FC<InlineEditProps> = ({\n value,\n onChange,\n onConfirm,\n onCancel,\n editing: editingProp,\n onEditingChange,\n type = 'text',\n placeholder,\n trigger = 'click',\n size = 'md',\n fullWidth = false,\n disabled = false,\n confirmOnBlur = true,\n renderDisplay,\n showEditIcon = true,\n inputVariant = 'outlined',\n className = '',\n}) => {\n const isControlled = editingProp !== undefined;\n const [internalEditing, setInternalEditing] = useState(false);\n const editing = isControlled ? editingProp : internalEditing;\n\n // Track the value at the moment editing begins so Esc can revert to it.\n const valueAtEditStart = useRef(value);\n\n // Local draft while editing - keeps Input value in sync without forcing the\n // parent to wire `onChange` just to see live keystrokes.\n const [draft, setDraft] = useState(value);\n\n // ------------------------------------------------------------------\n // Editing state helpers\n // ------------------------------------------------------------------\n\n const openEdit = useCallback(() => {\n if (disabled) return;\n valueAtEditStart.current = value;\n setDraft(value);\n if (!isControlled) setInternalEditing(true);\n onEditingChange?.(true);\n }, [disabled, isControlled, onEditingChange, value]);\n\n const closeEdit = useCallback(() => {\n if (!isControlled) setInternalEditing(false);\n onEditingChange?.(false);\n }, [isControlled, onEditingChange]);\n\n const confirm = useCallback(() => {\n closeEdit();\n onConfirm?.(draft);\n }, [closeEdit, draft, onConfirm]);\n\n const cancel = useCallback(() => {\n // Revert the draft AND propagate the original value so the parent stays in\n // sync when `onChange` is used to maintain external state.\n setDraft(valueAtEditStart.current);\n onChange?.(valueAtEditStart.current);\n closeEdit();\n onCancel?.();\n }, [closeEdit, onChange, onCancel]);\n\n // ------------------------------------------------------------------\n // Input handlers\n // ------------------------------------------------------------------\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setDraft(e.target.value);\n onChange?.(e.target.value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n // Stop propagation so parent grids/tables don't double-handle Enter.\n e.stopPropagation();\n confirm();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n cancel();\n }\n // Tab and Arrow keys intentionally bubble - consumers like DataGrid own\n // Tab-to-next-cell and arrow-key navigation at the container level.\n };\n\n const handleBlur = () => {\n if (confirmOnBlur) confirm();\n };\n\n // ------------------------------------------------------------------\n // Trigger props for display mode\n // ------------------------------------------------------------------\n\n const displayTriggerProps =\n trigger === 'doubleClick' ? { onDoubleClick: openEdit } : { onClick: openEdit };\n\n // ------------------------------------------------------------------\n // Render: edit mode\n // ------------------------------------------------------------------\n\n if (editing) {\n return (\n <Input\n type={type}\n variant={inputVariant}\n size={size}\n value={draft}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n placeholder={placeholder}\n fullWidth={fullWidth}\n clearable={false}\n autoFocus\n className={`eidos-inline-edit eidos-inline-edit--editing ${className}`}\n />\n );\n }\n\n // ------------------------------------------------------------------\n // Render: display mode\n // ------------------------------------------------------------------\n\n const isEmpty = !value;\n\n return (\n <span\n role=\"button\"\n tabIndex={disabled ? undefined : 0}\n // The visible text has to be *in* the name (SC 2.5.3 Label in Name).\n //\n // This was a flat \"Click to edit\", which shares nothing with the value\n // on screen - so a speech-input user saying what they can see (\"Page\n // title\") matched nothing, and a screen reader announced an edit\n // affordance without ever saying what it edits. Every `InlineEdit` on\n // a page was also announced identically.\n //\n // Left off entirely when there is no value: the placeholder is then\n // the visible text and `textContent` already provides the name.\n aria-label={disabled || isEmpty ? undefined : `${value}, click to edit`}\n className={[\n 'eidos-inline-edit',\n `eidos-inline-edit--${size}`,\n fullWidth ? 'eidos-inline-edit--full-width' : '',\n disabled ? 'eidos-inline-edit--disabled' : '',\n isEmpty ? 'eidos-inline-edit--empty' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n {...(disabled ? {} : displayTriggerProps)}\n onKeyDown={\n disabled\n ? undefined\n : (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openEdit();\n }\n }\n }\n >\n <span className=\"eidos-inline-edit__text\">\n {renderDisplay\n ? renderDisplay(value)\n : value || <span className=\"eidos-inline-edit__placeholder\">{placeholder}</span>}\n </span>\n {!disabled && showEditIcon && (\n <span className=\"eidos-inline-edit__icon\" aria-hidden>\n <Pencil />\n </span>\n )}\n </span>\n );\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,88 @@
1
+ import {
2
+ Tooltip
3
+ } from "./chunk-XO7WTE6O.js";
4
+ import {
5
+ devWarn,
6
+ renderIcon
7
+ } from "./chunk-WT4WBHBR.js";
8
+
9
+ // src/components/Button/Button.component.tsx
10
+ import { LoaderCircle } from "lucide-react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ var Button = ({
13
+ variant = "filled",
14
+ color = "primary",
15
+ size = "md",
16
+ disabled = false,
17
+ loading = false,
18
+ loadingText = "Loading...",
19
+ tooltip,
20
+ className = "",
21
+ preIcon,
22
+ posIcon,
23
+ icon,
24
+ children,
25
+ ...buttonProps
26
+ }) => {
27
+ const isIconOnly = !!icon;
28
+ if (isIconOnly) {
29
+ if (children) {
30
+ throw new Error("Icon-only buttons cannot have children");
31
+ }
32
+ if (preIcon) {
33
+ throw new Error("Icon-only buttons cannot have preIcon");
34
+ }
35
+ if (posIcon) {
36
+ throw new Error("Icon-only buttons cannot have posIcon");
37
+ }
38
+ }
39
+ const suppliedLabel = buttonProps["aria-label"] ?? buttonProps["aria-labelledby"];
40
+ const iconOnlyLabel = isIconOnly && !suppliedLabel ? tooltip : void 0;
41
+ if (isIconOnly && !suppliedLabel && !tooltip) {
42
+ devWarn(
43
+ "button-icon-only-name",
44
+ 'Button: an icon-only button needs an accessible name. Pass `tooltip` (which now also names it) or `aria-label`, otherwise it is announced as just "button".'
45
+ );
46
+ }
47
+ const buttonClasses = [
48
+ "eidos-button",
49
+ `eidos-button--${variant}`,
50
+ `eidos-button--${color}`,
51
+ `eidos-button--${size}`,
52
+ isIconOnly && "eidos-button--icon-only",
53
+ disabled && "eidos-button--disabled",
54
+ loading && "eidos-button--loading",
55
+ className
56
+ ].filter(Boolean).join(" ");
57
+ const buttonContent = /* @__PURE__ */ jsx(
58
+ "button",
59
+ {
60
+ type: "button",
61
+ ...buttonProps,
62
+ "aria-label": iconOnlyLabel ?? buttonProps["aria-label"],
63
+ className: buttonClasses,
64
+ disabled: disabled || loading,
65
+ children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
66
+ !isIconOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx("span", { className: "eidos-button--loading-spinner", children: /* @__PURE__ */ jsx(LoaderCircle, { className: "eidos-button--spinner-icon" }) }),
68
+ /* @__PURE__ */ jsx("span", { className: "eidos-button--copy", children: loadingText })
69
+ ] }),
70
+ isIconOnly && /* @__PURE__ */ jsx("span", { className: "eidos-button--loading-spinner", children: /* @__PURE__ */ jsx(LoaderCircle, { className: "eidos-button--spinner-icon" }) })
71
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
72
+ preIcon && renderIcon(preIcon, "eidos-button--pre-icon"),
73
+ isIconOnly && icon ? renderIcon(icon, "eidos-button--icon") : /* @__PURE__ */ jsx("span", { className: "eidos-button--copy", children }),
74
+ posIcon && renderIcon(posIcon, "eidos-button--pos-icon")
75
+ ] })
76
+ }
77
+ );
78
+ return tooltip ? /* @__PURE__ */ jsx(Tooltip, { message: tooltip, children: buttonContent }) : buttonContent;
79
+ };
80
+ var IconButton = (props) => {
81
+ return /* @__PURE__ */ jsx(Button, { ...props });
82
+ };
83
+
84
+ export {
85
+ Button,
86
+ IconButton
87
+ };
88
+ //# sourceMappingURL=chunk-44T4HMFU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/Button/Button.component.tsx"],"sourcesContent":["import React from 'react';\nimport { LoaderCircle } from 'lucide-react';\nimport { Tooltip } from '../Tooltip';\nimport { ButtonProps, IconButtonProps } from './Button.types';\nimport { renderIcon, devWarn } from '../../utils';\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'filled',\n color = 'primary',\n size = 'md',\n disabled = false,\n loading = false,\n loadingText = 'Loading...',\n tooltip,\n className = '',\n preIcon,\n posIcon,\n icon,\n children,\n ...buttonProps\n}) => {\n const isIconOnly = !!icon;\n\n if (isIconOnly) {\n if (children) {\n throw new Error('Icon-only buttons cannot have children');\n }\n if (preIcon) {\n throw new Error('Icon-only buttons cannot have preIcon');\n }\n if (posIcon) {\n throw new Error('Icon-only buttons cannot have posIcon');\n }\n }\n\n // ── Accessible name for icon-only buttons ──────────────────────────────\n //\n // An icon-only button has no text, so without `aria-label` it is announced\n // as just \"button\". `tooltip` alone did NOT fix this: `Tooltip` renders a\n // floating panel and adds no naming attributes to its child, so the\n // library's own documented example - `<IconButton icon={Plus}\n // tooltip=\"Add item\" />` - shipped a nameless control.\n //\n // Falling back to `tooltip` makes that documented pattern correct without\n // asking anyone to change their code: if you already described the button\n // for sighted users, that description now names it for everyone.\n //\n // Only ever a fallback - an explicit `aria-label` or `aria-labelledby`\n // always wins, since the caller may want a longer name than the tooltip.\n const suppliedLabel = buttonProps['aria-label'] ?? buttonProps['aria-labelledby'];\n const iconOnlyLabel = isIconOnly && !suppliedLabel ? tooltip : undefined;\n\n if (isIconOnly && !suppliedLabel && !tooltip) {\n devWarn(\n 'button-icon-only-name',\n 'Button: an icon-only button needs an accessible name. Pass `tooltip` (which now also names it) or `aria-label`, otherwise it is announced as just \"button\".',\n );\n }\n\n const buttonClasses = [\n 'eidos-button',\n `eidos-button--${variant}`,\n `eidos-button--${color}`,\n `eidos-button--${size}`,\n isIconOnly && 'eidos-button--icon-only',\n disabled && 'eidos-button--disabled',\n loading && 'eidos-button--loading',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const buttonContent = (\n <button\n type=\"button\"\n {...buttonProps}\n aria-label={iconOnlyLabel ?? buttonProps['aria-label']}\n className={buttonClasses}\n disabled={disabled || loading}\n >\n {loading ? (\n <>\n {!isIconOnly && (\n <>\n <span className=\"eidos-button--loading-spinner\">\n <LoaderCircle className=\"eidos-button--spinner-icon\" />\n </span>\n <span className=\"eidos-button--copy\">{loadingText}</span>\n </>\n )}\n {isIconOnly && (\n <span className=\"eidos-button--loading-spinner\">\n <LoaderCircle className=\"eidos-button--spinner-icon\" />\n </span>\n )}\n </>\n ) : (\n <>\n {preIcon && renderIcon(preIcon, 'eidos-button--pre-icon')}\n\n {isIconOnly && icon ? (\n renderIcon(icon, 'eidos-button--icon')\n ) : (\n <span className=\"eidos-button--copy\">{children}</span>\n )}\n\n {posIcon && renderIcon(posIcon, 'eidos-button--pos-icon')}\n </>\n )}\n </button>\n );\n\n return tooltip ? <Tooltip message={tooltip}>{buttonContent}</Tooltip> : buttonContent;\n};\n\n/**\n * IconButton - Convenience wrapper for icon-only buttons\n *\n * @example\n * ```tsx\n * import { IconButton } from 'eidos-ui';\n * import { Plus } from 'lucide-react';\n *\n * <IconButton icon={Plus} tooltip=\"Add item\" />\n * ```\n */\nexport const IconButton: React.FC<IconButtonProps> = (props) => {\n return <Button {...props} />;\n};\n"],"mappings":";;;;;;;;;AACA,SAAS,oBAAoB;AAkFjB,mBAEI,KAFJ;AA7EL,IAAM,SAAgC,CAAC;AAAA,EAC5C,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,cAAc;AAAA,EACd;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,aAAa,CAAC,CAAC;AAErB,MAAI,YAAY;AACd,QAAI,UAAU;AACZ,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AACA,QAAI,SAAS;AACX,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,QAAI,SAAS;AACX,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAAA,EACF;AAgBA,QAAM,gBAAgB,YAAY,YAAY,KAAK,YAAY,iBAAiB;AAChF,QAAM,gBAAgB,cAAc,CAAC,gBAAgB,UAAU;AAE/D,MAAI,cAAc,CAAC,iBAAiB,CAAC,SAAS;AAC5C;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA,iBAAiB,OAAO;AAAA,IACxB,iBAAiB,KAAK;AAAA,IACtB,iBAAiB,IAAI;AAAA,IACrB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,WAAW;AAAA,IACX;AAAA,EACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,QAAM,gBACJ;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACJ,GAAG;AAAA,MACJ,cAAY,iBAAiB,YAAY,YAAY;AAAA,MACrD,WAAW;AAAA,MACX,UAAU,YAAY;AAAA,MAErB,oBACC,iCACG;AAAA,SAAC,cACA,iCACE;AAAA,8BAAC,UAAK,WAAU,iCACd,8BAAC,gBAAa,WAAU,8BAA6B,GACvD;AAAA,UACA,oBAAC,UAAK,WAAU,sBAAsB,uBAAY;AAAA,WACpD;AAAA,QAED,cACC,oBAAC,UAAK,WAAU,iCACd,8BAAC,gBAAa,WAAU,8BAA6B,GACvD;AAAA,SAEJ,IAEA,iCACG;AAAA,mBAAW,WAAW,SAAS,wBAAwB;AAAA,QAEvD,cAAc,OACb,WAAW,MAAM,oBAAoB,IAErC,oBAAC,UAAK,WAAU,sBAAsB,UAAS;AAAA,QAGhD,WAAW,WAAW,SAAS,wBAAwB;AAAA,SAC1D;AAAA;AAAA,EAEJ;AAGF,SAAO,UAAU,oBAAC,WAAQ,SAAS,SAAU,yBAAc,IAAa;AAC1E;AAaO,IAAM,aAAwC,CAAC,UAAU;AAC9D,SAAO,oBAAC,UAAQ,GAAG,OAAO;AAC5B;","names":[]}
@@ -2,6 +2,9 @@
2
2
 
3
3
  var _chunkKVIL4EP4cjs = require('./chunk-KVIL4EP4.cjs');
4
4
 
5
+
6
+ var _chunkVHSTB7GBcjs = require('./chunk-VHSTB7GB.cjs');
7
+
5
8
  // src/components/CommandPalette/CommandPalette.component.tsx
6
9
  var _react = require('react');
7
10
  var _reactdom = require('react-dom');
@@ -96,6 +99,7 @@ var CommandPalette = ({
96
99
  const [query, setQuery] = _react.useState.call(void 0, "");
97
100
  const [focusedIndex, setFocusedIndex] = _react.useState.call(void 0, 0);
98
101
  const inputRef = _react.useRef.call(void 0, null);
102
+ const dialogRef = _react.useRef.call(void 0, null);
99
103
  const itemRefs = _react.useRef.call(void 0, /* @__PURE__ */ new Map());
100
104
  const filtered = _react.useMemo.call(void 0, () => filterItems(items, query), [items, query]);
101
105
  const navigable = _react.useMemo.call(void 0, () => filtered.filter((item) => !item.disabled), [filtered]);
@@ -106,18 +110,14 @@ var CommandPalette = ({
106
110
  setFocusedIndex(0);
107
111
  }
108
112
  }, [open]);
109
- _react.useEffect.call(void 0, () => {
110
- if (isVisible) {
111
- _optionalChain([inputRef, 'access', _9 => _9.current, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]);
112
- }
113
- }, [isVisible]);
113
+ _chunkVHSTB7GBcjs.useDialogFocus.call(void 0, open && isVisible, dialogRef, { initialFocus: inputRef });
114
114
  _react.useEffect.call(void 0, () => {
115
115
  setFocusedIndex(0);
116
116
  }, [query]);
117
117
  _react.useEffect.call(void 0, () => {
118
118
  const focusedItem = navigable[focusedIndex];
119
119
  if (!focusedItem) return;
120
- _optionalChain([itemRefs, 'access', _12 => _12.current, 'access', _13 => _13.get, 'call', _14 => _14(focusedItem.id), 'optionalAccess', _15 => _15.scrollIntoView, 'call', _16 => _16({ block: "nearest" })]);
120
+ _optionalChain([itemRefs, 'access', _9 => _9.current, 'access', _10 => _10.get, 'call', _11 => _11(focusedItem.id), 'optionalAccess', _12 => _12.scrollIntoView, 'call', _13 => _13({ block: "nearest" })]);
121
121
  }, [focusedIndex, navigable]);
122
122
  _react.useEffect.call(void 0, () => {
123
123
  if (open) {
@@ -130,8 +130,8 @@ var CommandPalette = ({
130
130
  const selectItem = _react.useCallback.call(void 0,
131
131
  (item) => {
132
132
  if (item.disabled) return;
133
- _optionalChain([item, 'access', _17 => _17.action, 'optionalCall', _18 => _18()]);
134
- _optionalChain([onSelect, 'optionalCall', _19 => _19(item)]);
133
+ _optionalChain([item, 'access', _14 => _14.action, 'optionalCall', _15 => _15()]);
134
+ _optionalChain([onSelect, 'optionalCall', _16 => _16(item)]);
135
135
  close();
136
136
  },
137
137
  [close, onSelect]
@@ -181,11 +181,10 @@ var CommandPalette = ({
181
181
  shortcutKey.toUpperCase()
182
182
  ] })
183
183
  ] }) : trigger ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-cmd-trigger", role: "button", tabIndex: 0, onClick: openPalette, children: trigger }) : null;
184
- if (!isMounted) return triggerNode;
185
184
  const maxHeightValue = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
186
185
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
187
186
  triggerNode,
188
- _reactdom.createPortal.call(void 0,
187
+ isMounted && _reactdom.createPortal.call(void 0,
189
188
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
190
189
  "div",
191
190
  {
@@ -195,6 +194,7 @@ var CommandPalette = ({
195
194
  children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
196
195
  "div",
197
196
  {
197
+ ref: dialogRef,
198
198
  role: "dialog",
199
199
  "aria-modal": "true",
200
200
  "aria-label": "Command palette",
@@ -243,7 +243,7 @@ var CommandPalette = ({
243
243
  children: [
244
244
  group.label && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-cmd-group-label", "aria-hidden": "true", children: group.label }),
245
245
  group.items.map((item) => {
246
- const isFocused = _optionalChain([navigable, 'access', _20 => _20[focusedIndex], 'optionalAccess', _21 => _21.id]) === item.id;
246
+ const isFocused = _optionalChain([navigable, 'access', _17 => _17[focusedIndex], 'optionalAccess', _18 => _18.id]) === item.id;
247
247
  const Icon = item.icon;
248
248
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
249
249
  "div",
@@ -324,4 +324,4 @@ CommandPalette.displayName = "CommandPalette";
324
324
 
325
325
 
326
326
  exports.CommandPalette = CommandPalette;
327
- //# sourceMappingURL=chunk-3EPND5M2.cjs.map
327
+ //# sourceMappingURL=chunk-4LET2PEX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-4LET2PEX.cjs","../src/components/CommandPalette/CommandPalette.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-4LET2PEX.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ArrowDown, ArrowUp, Command, CornerDownLeft, Search } from 'lucide-react';\nimport type { CommandItem, CommandPaletteProps } from './CommandPalette.types';\nimport { useDialogFocus } from '../../utils';\nimport { Kbd } from '../Kbd';\nimport './CommandPalette.scss';\n\n// ── Constants ──────────────────────────────────────────────────────────────────\n\n/** Must match the CSS transition duration in CommandPalette.scss */\nconst TRANSITION_MS = 200;\n\n// ── Pure helpers ───────────────────────────────────────────────────────────────\n\nfunction filterItems(items: CommandItem[], query: string): CommandItem[] {\n const q = query.trim().toLowerCase();\n if (!q) return items;\n return items.filter(({ label, description, keywords }) => {\n return (\n label.toLowerCase().includes(q) ||\n (description?.toLowerCase().includes(q) ?? false) ||\n (keywords?.some((k) => k.toLowerCase().includes(q)) ?? false)\n );\n });\n}\n\ninterface Group {\n label: string | null;\n items: CommandItem[];\n}\n\n/**\n * Groups items while preserving the order in which groups first appear.\n * Items with no `group` property are placed in a leading implicit section\n * (label: null) so that labelled groups always follow them.\n */\nfunction buildGroups(items: CommandItem[]): Group[] {\n const groups: Group[] = [];\n const groupMap = new Map<string, Group>();\n\n for (const item of items) {\n const key = item.group ?? '';\n let group = groupMap.get(key);\n if (!group) {\n group = { label: item.group ?? null, items: [] };\n groups.push(group);\n groupMap.set(key, group);\n }\n group.items.push(item);\n }\n\n return groups;\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nexport const CommandPalette: React.FC<CommandPaletteProps> = ({\n open: openProp,\n defaultOpen = false,\n onClose,\n onOpen,\n shortcutKey = 'k',\n items,\n onSelect,\n placeholder = 'Search commands…',\n emptyText = 'No commands found',\n maxHeight = 360,\n className = '',\n footer,\n trigger,\n triggerLabel = 'Search',\n}) => {\n const dialogId = useId();\n const listId = `${dialogId}-list`;\n\n // ── Controlled / uncontrolled open state ───────────────────────────────────\n // Omitting `open` lets the palette own its state entirely, which combined\n // with the shortcut listener below means it needs zero external\n // useState/useEffect to work. Passing `open` (+ `onClose`, optionally\n // `onOpen`) keeps full external control for consumers who need it (syncing\n // with a URL, another piece of app state, etc).\n const isControlled = openProp !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = isControlled ? (openProp as boolean) : internalOpen;\n\n const close = useCallback(() => {\n if (!isControlled) setInternalOpen(false);\n onClose?.();\n }, [isControlled, onClose]);\n\n // Shared \"open\" path - uncontrolled flips the internal state directly;\n // controlled defers to `onOpen` since the palette can't change `open`\n // itself. Used by both the keyboard shortcut below and the inline\n // `trigger`'s click handler.\n const openPalette = useCallback(() => {\n if (isControlled) {\n onOpen?.();\n } else {\n setInternalOpen(true);\n }\n }, [isControlled, onOpen]);\n\n // Global keyboard shortcut - Cmd/Ctrl+<shortcutKey> opens the palette.\n useEffect(() => {\n if (!shortcutKey) return;\n\n const handler = (e: KeyboardEvent) => {\n if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== shortcutKey.toLowerCase()) return;\n e.preventDefault();\n openPalette();\n };\n\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [shortcutKey, openPalette]);\n\n // ── Animation state machine (mirrors the Drawer pattern) ─────────────────\n // isMounted → portal exists in the DOM (controls render/null)\n // isVisible → CSS --visible modifier is applied (drives transitions)\n //\n // Opening: isMounted=true → double rAF → isVisible=true → CSS transitions fire\n // Closing: isVisible=false → CSS transitions reverse → after TRANSITION_MS → isMounted=false\n const [isMounted, setIsMounted] = useState(open);\n const [isVisible, setIsVisible] = useState(open);\n\n useEffect(() => {\n let raf1 = 0;\n let raf2 = 0;\n let timerId: ReturnType<typeof setTimeout>;\n\n if (open) {\n setIsMounted(true);\n // Double rAF: ensures the browser has painted the initial off-screen\n // state before the CSS transition begins (React 18 automatic batching\n // means a single rAF is not always sufficient).\n raf1 = requestAnimationFrame(() => {\n raf2 = requestAnimationFrame(() => setIsVisible(true));\n });\n } else {\n setIsVisible(false);\n timerId = setTimeout(() => setIsMounted(false), TRANSITION_MS);\n }\n\n return () => {\n cancelAnimationFrame(raf1);\n cancelAnimationFrame(raf2);\n clearTimeout(timerId);\n };\n }, [open]);\n\n // ── Search state ───────────────────────────────────────────────────────────\n const [query, setQuery] = useState('');\n const [focusedIndex, setFocusedIndex] = useState(0);\n\n const inputRef = useRef<HTMLInputElement>(null);\n const dialogRef = useRef<HTMLDivElement>(null);\n const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());\n\n // Derived lists - memoised to stabilise scroll-effect deps\n const filtered = useMemo(() => filterItems(items, query), [items, query]);\n const navigable = useMemo(() => filtered.filter((item) => !item.disabled), [filtered]);\n const groups = useMemo(() => buildGroups(filtered), [filtered]);\n\n // Reset search query and selection when the palette is opened\n useEffect(() => {\n if (open) {\n setQuery('');\n setFocusedIndex(0);\n }\n }, [open]);\n\n // Focus the search input on open, trap Tab inside the dialog, and return\n // focus to the trigger on close.\n //\n // This replaces a bare `inputRef.current?.focus()` effect. That moved focus\n // in correctly but nothing kept it there: the first Tab left the dialog for\n // the page behind it, and closing the palette stranded focus wherever it\n // happened to be. `initialFocus` preserves the original behaviour of\n // landing on the search field rather than the first focusable node.\n useDialogFocus(open && isVisible, dialogRef, { initialFocus: inputRef });\n\n // Reset focused index to 0 whenever the filtered list changes\n useEffect(() => {\n setFocusedIndex(0);\n }, [query]);\n\n // Scroll the focused item into view during keyboard navigation\n useEffect(() => {\n const focusedItem = navigable[focusedIndex];\n if (!focusedItem) return;\n itemRefs.current.get(focusedItem.id)?.scrollIntoView({ block: 'nearest' });\n }, [focusedIndex, navigable]);\n\n // Body scroll lock (tied to open, not to isMounted, so it releases promptly\n // when the consumer decides to close - even before the exit animation ends)\n useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [open]);\n\n // ── Interaction handlers ───────────────────────────────────────────────────\n\n const selectItem = useCallback(\n (item: CommandItem) => {\n if (item.disabled) return;\n item.action?.();\n onSelect?.(item);\n close();\n },\n [close, onSelect],\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n if (navigable.length === 0) return;\n setFocusedIndex((i) => (i + 1) % navigable.length);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n if (navigable.length === 0) return;\n setFocusedIndex((i) => (i - 1 + navigable.length) % navigable.length);\n break;\n }\n case 'Enter': {\n e.preventDefault();\n const item = navigable[focusedIndex];\n if (item) selectItem(item);\n break;\n }\n case 'Escape': {\n e.preventDefault();\n close();\n break;\n }\n }\n },\n [focusedIndex, navigable, close, selectItem],\n );\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n // Only fire when the click lands directly on the backdrop element,\n // not on any child (the dialog itself or its contents).\n if (e.target === e.currentTarget) {\n close();\n }\n },\n [close],\n );\n\n // ── Render ─────────────────────────────────────────────────────────────────\n\n // The inline trigger (if any) must render regardless of `isMounted` - it's\n // what opens the palette in the first place, so it can't be gated behind\n // the portal's own mount state.\n const triggerNode =\n trigger === true ? (\n <button type=\"button\" className=\"eidos-cmd-trigger-default\" onClick={openPalette}>\n <span>{triggerLabel}</span>\n {shortcutKey && (\n <Kbd size=\"sm\" className=\"eidos-cmd-trigger-kbd\">\n <Command size={10} />\n {shortcutKey.toUpperCase()}\n </Kbd>\n )}\n </button>\n ) : trigger ? (\n <div className=\"eidos-cmd-trigger\" role=\"button\" tabIndex={0} onClick={openPalette}>\n {trigger}\n </div>\n ) : null;\n\n const maxHeightValue = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight;\n\n // The root element must not change shape between the closed and open\n // states.\n //\n // This used to `return triggerNode` while closed and a fragment once\n // mounted. React compares the root by element type, so flipping from\n // `<button>` to `<Fragment>` unmounted the trigger and mounted a brand new\n // one - the very element the user had just clicked was destroyed. Focus\n // was lost with it, which made returning focus on close impossible: the\n // node to return to no longer existed.\n //\n // Always returning the fragment keeps `triggerNode` in a stable position,\n // so React preserves the DOM node across open and close.\n return (\n <>\n {triggerNode}\n {isMounted &&\n createPortal(\n <div\n className={['eidos-cmd-backdrop', isVisible && 'eidos-cmd-backdrop--visible']\n .filter(Boolean)\n .join(' ')}\n onClick={handleBackdropClick}\n aria-hidden={!open}\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Command palette\"\n id={dialogId}\n className={['eidos-cmd-dialog', isVisible && 'eidos-cmd-dialog--visible', className]\n .filter(Boolean)\n .join(' ')}\n onKeyDown={handleKeyDown}\n >\n {/* ── Search ──────────────────────────────────────────────────────── */}\n <div className=\"eidos-cmd-search\">\n <Search className=\"eidos-cmd-search-icon\" size={18} aria-hidden=\"true\" />\n\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded=\"true\"\n aria-controls={listId}\n aria-autocomplete=\"list\"\n aria-label={placeholder}\n className=\"eidos-cmd-input\"\n placeholder={placeholder}\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n spellCheck={false}\n autoComplete=\"off\"\n />\n\n {/* Reminds users of the configured shortcut, if any */}\n {shortcutKey && (\n <div className=\"eidos-cmd-search-badge\" aria-hidden=\"true\">\n <Command size={11} />\n <span>{shortcutKey.toUpperCase()}</span>\n </div>\n )}\n </div>\n\n {/* ── Results list ─────────────────────────────────────────────────── */}\n <div\n id={listId}\n role=\"listbox\"\n aria-label=\"Commands\"\n className=\"eidos-cmd-list\"\n style={{ maxHeight: maxHeightValue }}\n >\n {filtered.length === 0 ? (\n <p className=\"eidos-cmd-empty\" role=\"status\">\n {emptyText}\n </p>\n ) : (\n groups.map((group) => (\n <div\n key={group.label ?? '__ungrouped__'}\n role=\"group\"\n aria-label={group.label ?? undefined}\n >\n {group.label && (\n <div className=\"eidos-cmd-group-label\" aria-hidden=\"true\">\n {group.label}\n </div>\n )}\n\n {group.items.map((item) => {\n const isFocused = navigable[focusedIndex]?.id === item.id;\n const Icon = item.icon;\n\n return (\n <div\n key={item.id}\n ref={(el) => {\n if (el) {\n itemRefs.current.set(item.id, el);\n } else {\n itemRefs.current.delete(item.id);\n }\n }}\n role=\"option\"\n aria-selected={isFocused}\n aria-disabled={item.disabled}\n className={[\n 'eidos-cmd-item',\n isFocused && 'eidos-cmd-item--focused',\n item.disabled && 'eidos-cmd-item--disabled',\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={() => selectItem(item)}\n onMouseEnter={() => {\n if (!item.disabled) {\n const idx = navigable.findIndex((n) => n.id === item.id);\n if (idx !== -1) setFocusedIndex(idx);\n }\n }}\n >\n {Icon && (\n <span className=\"eidos-cmd-item-icon\" aria-hidden=\"true\">\n <Icon size={16} />\n </span>\n )}\n\n <div className=\"eidos-cmd-item-body\">\n <div className=\"eidos-cmd-item-label\">{item.label}</div>\n {item.description && (\n <div className=\"eidos-cmd-item-description\">{item.description}</div>\n )}\n </div>\n\n {item.shortcut && item.shortcut.length > 0 && (\n <div\n className=\"eidos-cmd-item-shortcut\"\n aria-label={`Shortcut: ${item.shortcut.join(' ')}`}\n >\n {item.shortcut.map((key, i) => (\n <kbd key={i} className=\"eidos-cmd-kbd\">\n {key}\n </kbd>\n ))}\n </div>\n )}\n </div>\n );\n })}\n </div>\n ))\n )}\n </div>\n\n {/* ── Footer ───────────────────────────────────────────────────────── */}\n <div className=\"eidos-cmd-footer\">\n <div className=\"eidos-cmd-hints\" aria-hidden=\"true\">\n <span>\n <ArrowUp size={11} />\n <ArrowDown size={11} /> Navigate\n </span>\n <span>\n <CornerDownLeft size={11} /> Select\n </span>\n <span>Esc Close</span>\n </div>\n\n {footer && <div className=\"eidos-cmd-footer-slot\">{footer}</div>}\n </div>\n </div>\n </div>,\n document.body,\n )}\n </>\n );\n};\n\nCommandPalette.displayName = 'CommandPalette';\n"]}