@elabs-ai/components-ai 4.2.0 → 5.0.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 (462) hide show
  1. package/README.md +1 -1
  2. package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
  3. package/dist/_code-block-theme.js +12 -0
  4. package/dist/_code-block-theme.js.map +1 -0
  5. package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
  6. package/dist/_flow-boundary.js.map +1 -0
  7. package/dist/_flow-lazy.js +8 -0
  8. package/dist/_flow-lazy.js.map +1 -0
  9. package/dist/_lazy-boundary-conformance.js +2 -0
  10. package/dist/_lazy-boundary-conformance.js.map +1 -0
  11. package/dist/_lazy-cjk.js +13 -0
  12. package/dist/_lazy-cjk.js.map +1 -0
  13. package/dist/_lazy-engine-boundary.js +9 -0
  14. package/dist/_lazy-engine-boundary.js.map +1 -0
  15. package/dist/_lazy-math.js +13 -0
  16. package/dist/_lazy-math.js.map +1 -0
  17. package/dist/_lazy-mermaid.js +12 -0
  18. package/dist/_lazy-mermaid.js.map +1 -0
  19. package/dist/_mermaid-error-panel.js +9 -0
  20. package/dist/_mermaid-error-panel.js.map +1 -0
  21. package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
  22. package/dist/_persona-rive.js.map +1 -0
  23. package/dist/_streamdown-i18n.js +21 -0
  24. package/dist/_streamdown-i18n.js.map +1 -0
  25. package/dist/_streamdown-safety.js +10 -0
  26. package/dist/_streamdown-safety.js.map +1 -0
  27. package/dist/_theme-scope-store.js +13 -0
  28. package/dist/_theme-scope-store.js.map +1 -0
  29. package/dist/a2ui/a2ui-surface.js +17 -0
  30. package/dist/a2ui/a2ui-surface.js.map +1 -0
  31. package/dist/a2ui/core/catalog.generated.js +10 -0
  32. package/dist/a2ui/core/catalog.generated.js.map +1 -0
  33. package/dist/a2ui/core/complete-json.js +10 -0
  34. package/dist/a2ui/core/complete-json.js.map +1 -0
  35. package/dist/a2ui/core/index.js +37 -0
  36. package/dist/a2ui/core/index.js.map +1 -0
  37. package/dist/a2ui/core/schema.js +11 -0
  38. package/dist/a2ui/core/schema.js.map +1 -0
  39. package/dist/a2ui/core/spec.js +10 -0
  40. package/dist/a2ui/core/spec.js.map +1 -0
  41. package/dist/a2ui/core/validate.js +16 -0
  42. package/dist/a2ui/core/validate.js.map +1 -0
  43. package/dist/a2ui/index.js +54 -0
  44. package/dist/a2ui/index.js.map +1 -0
  45. package/dist/a2ui/ui-catalog.js +18 -0
  46. package/dist/a2ui/ui-catalog.js.map +1 -0
  47. package/dist/agent-event.js +10 -0
  48. package/dist/agent-event.js.map +1 -0
  49. package/dist/agent-timeline.js +11 -0
  50. package/dist/agent-timeline.js.map +1 -0
  51. package/dist/agent.js +25 -0
  52. package/dist/agent.js.map +1 -0
  53. package/dist/artifact.js +23 -0
  54. package/dist/artifact.js.map +1 -0
  55. package/dist/asset-preview.js +22 -0
  56. package/dist/asset-preview.js.map +1 -0
  57. package/dist/attachments.js +33 -0
  58. package/dist/attachments.js.map +1 -0
  59. package/dist/audio-player.js +30 -0
  60. package/dist/audio-player.js.map +1 -0
  61. package/dist/audio-visualizer.js +9 -0
  62. package/dist/audio-visualizer.js.map +1 -0
  63. package/dist/canvas.js +10 -0
  64. package/dist/canvas.js.map +1 -0
  65. package/dist/chain-of-thought.js +22 -0
  66. package/dist/chain-of-thought.js.map +1 -0
  67. package/dist/chat-greeting.js +8 -0
  68. package/dist/chat-greeting.js.map +1 -0
  69. package/dist/chat-shell.js +9 -0
  70. package/dist/chat-shell.js.map +1 -0
  71. package/dist/checkpoint.js +13 -0
  72. package/dist/checkpoint.js.map +1 -0
  73. package/dist/chunk-2XOIG5TG.js +72 -0
  74. package/dist/chunk-2XOIG5TG.js.map +1 -0
  75. package/dist/chunk-34CQ2VHM.js +172 -0
  76. package/dist/chunk-34CQ2VHM.js.map +1 -0
  77. package/dist/chunk-3TDHPXZZ.js +60 -0
  78. package/dist/chunk-3TDHPXZZ.js.map +1 -0
  79. package/dist/chunk-3XNPMDZY.js +478 -0
  80. package/dist/chunk-3XNPMDZY.js.map +1 -0
  81. package/dist/chunk-3YPT3V7L.js +56 -0
  82. package/dist/chunk-3YPT3V7L.js.map +1 -0
  83. package/dist/chunk-475UEU2Q.js +87 -0
  84. package/dist/chunk-475UEU2Q.js.map +1 -0
  85. package/dist/chunk-4CBTII2I.js +95 -0
  86. package/dist/chunk-4CBTII2I.js.map +1 -0
  87. package/dist/chunk-4XRNIZMG.js +22 -0
  88. package/dist/chunk-4XRNIZMG.js.map +1 -0
  89. package/dist/chunk-5NNWUKK5.js +2 -0
  90. package/dist/chunk-5NNWUKK5.js.map +1 -0
  91. package/dist/chunk-5UOMU27Z.js +164 -0
  92. package/dist/chunk-5UOMU27Z.js.map +1 -0
  93. package/dist/chunk-5VGYZ62I.js +264 -0
  94. package/dist/chunk-5VGYZ62I.js.map +1 -0
  95. package/dist/chunk-6IQPH2OS.js +21 -0
  96. package/dist/chunk-6IQPH2OS.js.map +1 -0
  97. package/dist/chunk-6PB2MXCI.js +482 -0
  98. package/dist/chunk-6PB2MXCI.js.map +1 -0
  99. package/dist/chunk-7FD5CZEU.js +215 -0
  100. package/dist/chunk-7FD5CZEU.js.map +1 -0
  101. package/dist/chunk-7RL6KZDF.js +266 -0
  102. package/dist/chunk-7RL6KZDF.js.map +1 -0
  103. package/dist/chunk-ADOYAGGK.js +482 -0
  104. package/dist/chunk-ADOYAGGK.js.map +1 -0
  105. package/dist/chunk-AXMNJMMH.js +984 -0
  106. package/dist/chunk-AXMNJMMH.js.map +1 -0
  107. package/dist/chunk-BA3AEMOU.js +21 -0
  108. package/dist/chunk-BA3AEMOU.js.map +1 -0
  109. package/dist/chunk-BBHFOYML.js +336 -0
  110. package/dist/chunk-BBHFOYML.js.map +1 -0
  111. package/dist/chunk-BHUVISIY.js +221 -0
  112. package/dist/chunk-BHUVISIY.js.map +1 -0
  113. package/dist/chunk-BM2LWERA.js +100 -0
  114. package/dist/chunk-BM2LWERA.js.map +1 -0
  115. package/dist/chunk-CMOYXNPJ.js +211 -0
  116. package/dist/chunk-CMOYXNPJ.js.map +1 -0
  117. package/dist/chunk-CZP3RHC4.js +74 -0
  118. package/dist/chunk-CZP3RHC4.js.map +1 -0
  119. package/dist/chunk-DDG5JHQT.js +112 -0
  120. package/dist/chunk-DDG5JHQT.js.map +1 -0
  121. package/dist/chunk-DJODXO7I.js +38 -0
  122. package/dist/chunk-DJODXO7I.js.map +1 -0
  123. package/dist/chunk-DWJDJLPU.js +192 -0
  124. package/dist/chunk-DWJDJLPU.js.map +1 -0
  125. package/dist/chunk-DZW6LM22.js +45 -0
  126. package/dist/chunk-DZW6LM22.js.map +1 -0
  127. package/dist/chunk-ELGCZLZF.js +305 -0
  128. package/dist/chunk-ELGCZLZF.js.map +1 -0
  129. package/dist/chunk-EV3U6ZZB.js +209 -0
  130. package/dist/chunk-EV3U6ZZB.js.map +1 -0
  131. package/dist/chunk-EY7LVUKQ.js +87 -0
  132. package/dist/chunk-EY7LVUKQ.js.map +1 -0
  133. package/dist/chunk-F4C74F7M.js +230 -0
  134. package/dist/chunk-F4C74F7M.js.map +1 -0
  135. package/dist/chunk-FERYDFZA.js +108 -0
  136. package/dist/chunk-FERYDFZA.js.map +1 -0
  137. package/dist/chunk-FUIYG2DS.js +166 -0
  138. package/dist/chunk-FUIYG2DS.js.map +1 -0
  139. package/dist/chunk-GKYZT3LA.js +136 -0
  140. package/dist/chunk-GKYZT3LA.js.map +1 -0
  141. package/dist/chunk-H42LRUQ4.js +102 -0
  142. package/dist/chunk-H42LRUQ4.js.map +1 -0
  143. package/dist/chunk-HVXM2G5F.js +222 -0
  144. package/dist/chunk-HVXM2G5F.js.map +1 -0
  145. package/dist/chunk-I4RDAGVY.js +53 -0
  146. package/dist/chunk-I4RDAGVY.js.map +1 -0
  147. package/dist/chunk-IQJG4IDX.js +208 -0
  148. package/dist/chunk-IQJG4IDX.js.map +1 -0
  149. package/dist/chunk-IX2PXG4Y.js +144 -0
  150. package/dist/chunk-IX2PXG4Y.js.map +1 -0
  151. package/dist/chunk-IXICSOOP.js +287 -0
  152. package/dist/chunk-IXICSOOP.js.map +1 -0
  153. package/dist/chunk-JY5HSN4D.js +84 -0
  154. package/dist/chunk-JY5HSN4D.js.map +1 -0
  155. package/dist/chunk-LMNSBOBG.js +19 -0
  156. package/dist/chunk-LMNSBOBG.js.map +1 -0
  157. package/dist/chunk-MBI7D6IL.js +42 -0
  158. package/dist/chunk-MBI7D6IL.js.map +1 -0
  159. package/dist/chunk-MLFMYCPH.js +192 -0
  160. package/dist/chunk-MLFMYCPH.js.map +1 -0
  161. package/dist/chunk-MOK66734.js +33 -0
  162. package/dist/chunk-MOK66734.js.map +1 -0
  163. package/dist/chunk-MXTAWPYM.js +196 -0
  164. package/dist/chunk-MXTAWPYM.js.map +1 -0
  165. package/dist/chunk-NCL2UZKI.js +96 -0
  166. package/dist/chunk-NCL2UZKI.js.map +1 -0
  167. package/dist/chunk-NET4YLRY.js +107 -0
  168. package/dist/chunk-NET4YLRY.js.map +1 -0
  169. package/dist/chunk-NIG67CFM.js +329 -0
  170. package/dist/chunk-NIG67CFM.js.map +1 -0
  171. package/dist/chunk-NMVNNO3L.js +10 -0
  172. package/dist/chunk-NMVNNO3L.js.map +1 -0
  173. package/dist/chunk-NP7VID4K.js +2 -0
  174. package/dist/chunk-NP7VID4K.js.map +1 -0
  175. package/dist/chunk-NPUCMAG2.js +382 -0
  176. package/dist/chunk-NPUCMAG2.js.map +1 -0
  177. package/dist/chunk-NSCLRHES.js +102 -0
  178. package/dist/chunk-NSCLRHES.js.map +1 -0
  179. package/dist/chunk-OEUXHVFW.js +86 -0
  180. package/dist/chunk-OEUXHVFW.js.map +1 -0
  181. package/dist/chunk-ONBHFIMZ.js +137 -0
  182. package/dist/chunk-ONBHFIMZ.js.map +1 -0
  183. package/dist/chunk-OPH2HHL2.js +228 -0
  184. package/dist/chunk-OPH2HHL2.js.map +1 -0
  185. package/dist/chunk-OQERHIQP.js +287 -0
  186. package/dist/chunk-OQERHIQP.js.map +1 -0
  187. package/dist/chunk-P6YJAZFG.js +103 -0
  188. package/dist/chunk-P6YJAZFG.js.map +1 -0
  189. package/dist/chunk-PG7ERDOD.js +65 -0
  190. package/dist/chunk-PG7ERDOD.js.map +1 -0
  191. package/dist/chunk-PIB5JBMA.js +90 -0
  192. package/dist/chunk-PIB5JBMA.js.map +1 -0
  193. package/dist/chunk-PQ5BPIWQ.js +22 -0
  194. package/dist/chunk-PQ5BPIWQ.js.map +1 -0
  195. package/dist/chunk-PQBF6ZIQ.js +55 -0
  196. package/dist/chunk-PQBF6ZIQ.js.map +1 -0
  197. package/dist/chunk-PTWCTSZL.js +173 -0
  198. package/dist/chunk-PTWCTSZL.js.map +1 -0
  199. package/dist/chunk-QJUNPVOS.js +101 -0
  200. package/dist/chunk-QJUNPVOS.js.map +1 -0
  201. package/dist/chunk-QUDZOTRV.js +43 -0
  202. package/dist/chunk-QUDZOTRV.js.map +1 -0
  203. package/dist/chunk-RB7G65JC.js +213 -0
  204. package/dist/chunk-RB7G65JC.js.map +1 -0
  205. package/dist/chunk-RPHAOR7P.js +45 -0
  206. package/dist/chunk-RPHAOR7P.js.map +1 -0
  207. package/dist/chunk-RTCSH2AN.js +331 -0
  208. package/dist/chunk-RTCSH2AN.js.map +1 -0
  209. package/dist/chunk-S3ISPW3A.js +28 -0
  210. package/dist/chunk-S3ISPW3A.js.map +1 -0
  211. package/dist/chunk-SEVN656N.js +36 -0
  212. package/dist/chunk-SEVN656N.js.map +1 -0
  213. package/dist/chunk-SKVG672B.js +64 -0
  214. package/dist/chunk-SKVG672B.js.map +1 -0
  215. package/dist/chunk-SPQIU7GA.js +236 -0
  216. package/dist/chunk-SPQIU7GA.js.map +1 -0
  217. package/dist/chunk-SSGJ6MEM.js +1076 -0
  218. package/dist/chunk-SSGJ6MEM.js.map +1 -0
  219. package/dist/chunk-SSKZ2AED.js +118 -0
  220. package/dist/chunk-SSKZ2AED.js.map +1 -0
  221. package/dist/chunk-T56JFGXI.js +561 -0
  222. package/dist/chunk-T56JFGXI.js.map +1 -0
  223. package/dist/chunk-TCB6O74A.js +137 -0
  224. package/dist/chunk-TCB6O74A.js.map +1 -0
  225. package/dist/chunk-TTEYPO6X.js +57 -0
  226. package/dist/chunk-TTEYPO6X.js.map +1 -0
  227. package/dist/chunk-TWPHAJNM.js +462 -0
  228. package/dist/chunk-TWPHAJNM.js.map +1 -0
  229. package/dist/chunk-TXJJLP4O.js +336 -0
  230. package/dist/chunk-TXJJLP4O.js.map +1 -0
  231. package/dist/chunk-TZ6TB7VM.js +245 -0
  232. package/dist/chunk-TZ6TB7VM.js.map +1 -0
  233. package/dist/chunk-UBLKJJ7K.js +278 -0
  234. package/dist/chunk-UBLKJJ7K.js.map +1 -0
  235. package/dist/chunk-UCJ6IUFS.js +52 -0
  236. package/dist/chunk-UCJ6IUFS.js.map +1 -0
  237. package/dist/chunk-UCKFJ3QF.js +197 -0
  238. package/dist/chunk-UCKFJ3QF.js.map +1 -0
  239. package/dist/chunk-UPXSLILN.js +20 -0
  240. package/dist/chunk-UPXSLILN.js.map +1 -0
  241. package/dist/chunk-V4DORGWJ.js +92 -0
  242. package/dist/chunk-V4DORGWJ.js.map +1 -0
  243. package/dist/chunk-V6E5DYZD.js +80 -0
  244. package/dist/chunk-V6E5DYZD.js.map +1 -0
  245. package/dist/chunk-VLLYKF3P.js +96 -0
  246. package/dist/chunk-VLLYKF3P.js.map +1 -0
  247. package/dist/chunk-VLNBTOH6.js +43 -0
  248. package/dist/chunk-VLNBTOH6.js.map +1 -0
  249. package/dist/chunk-WMDSMSPL.js +303 -0
  250. package/dist/chunk-WMDSMSPL.js.map +1 -0
  251. package/dist/chunk-WU5BLGTF.js +149 -0
  252. package/dist/chunk-WU5BLGTF.js.map +1 -0
  253. package/dist/chunk-WYTZVSGK.js +245 -0
  254. package/dist/chunk-WYTZVSGK.js.map +1 -0
  255. package/dist/chunk-X47QMM6K.js +187 -0
  256. package/dist/chunk-X47QMM6K.js.map +1 -0
  257. package/dist/chunk-XNBKO32Y.js +174 -0
  258. package/dist/chunk-XNBKO32Y.js.map +1 -0
  259. package/dist/chunk-YHUSW7D2.js +40 -0
  260. package/dist/chunk-YHUSW7D2.js.map +1 -0
  261. package/dist/chunk-YSKD7UXX.js +126 -0
  262. package/dist/chunk-YSKD7UXX.js.map +1 -0
  263. package/dist/chunk-YYRDGWOU.js +72 -0
  264. package/dist/chunk-YYRDGWOU.js.map +1 -0
  265. package/dist/chunk-ZIGZMWM7.js +19 -0
  266. package/dist/chunk-ZIGZMWM7.js.map +1 -0
  267. package/dist/chunk-ZKRTUKNP.js +40 -0
  268. package/dist/chunk-ZKRTUKNP.js.map +1 -0
  269. package/dist/code-block.js +38 -0
  270. package/dist/code-block.js.map +1 -0
  271. package/dist/commit.js +51 -0
  272. package/dist/commit.js.map +1 -0
  273. package/dist/composer.js +15 -0
  274. package/dist/composer.js.map +1 -0
  275. package/dist/confirmation.js +53 -0
  276. package/dist/confirmation.js.map +1 -0
  277. package/dist/connection.js +8 -0
  278. package/dist/connection.js.map +1 -0
  279. package/dist/context-panel.js +25 -0
  280. package/dist/context-panel.js.map +1 -0
  281. package/dist/controls.js +10 -0
  282. package/dist/controls.js.map +1 -0
  283. package/dist/conversation.js +19 -0
  284. package/dist/conversation.js.map +1 -0
  285. package/dist/diff-view.js +15 -0
  286. package/dist/diff-view.js.map +1 -0
  287. package/dist/edge.js +10 -0
  288. package/dist/edge.js.map +1 -0
  289. package/dist/environment-variables.js +29 -0
  290. package/dist/environment-variables.js.map +1 -0
  291. package/dist/file-tree.js +23 -0
  292. package/dist/file-tree.js.map +1 -0
  293. package/dist/gallery.js +9 -0
  294. package/dist/gallery.js.map +1 -0
  295. package/dist/grouped-parts.js +21 -0
  296. package/dist/grouped-parts.js.map +1 -0
  297. package/dist/image.js +9 -0
  298. package/dist/image.js.map +1 -0
  299. package/dist/index.d.ts +366 -98
  300. package/dist/index.js +672 -13577
  301. package/dist/index.js.map +1 -1
  302. package/dist/inline-citation.js +39 -0
  303. package/dist/inline-citation.js.map +1 -0
  304. package/dist/jsx-preview.js +17 -0
  305. package/dist/jsx-preview.js.map +1 -0
  306. package/dist/markdown-view.js +17 -0
  307. package/dist/markdown-view.js.map +1 -0
  308. package/dist/message-compare.js +12 -0
  309. package/dist/message-compare.js.map +1 -0
  310. package/dist/message-edit.js +19 -0
  311. package/dist/message-edit.js.map +1 -0
  312. package/dist/message-feedback.js +11 -0
  313. package/dist/message-feedback.js.map +1 -0
  314. package/dist/message-form-spec.js +38 -0
  315. package/dist/message-form-spec.js.map +1 -0
  316. package/dist/message-form.js +26 -0
  317. package/dist/message-form.js.map +1 -0
  318. package/dist/message-table-spec.js +26 -0
  319. package/dist/message-table-spec.js.map +1 -0
  320. package/dist/message-table.js +12 -0
  321. package/dist/message-table.js.map +1 -0
  322. package/dist/message.js +51 -0
  323. package/dist/message.js.map +1 -0
  324. package/dist/mic-selector.js +27 -0
  325. package/dist/mic-selector.js.map +1 -0
  326. package/dist/model-provider-logo.js +13 -0
  327. package/dist/model-provider-logo.js.map +1 -0
  328. package/dist/motion-config.js +9 -0
  329. package/dist/motion-config.js.map +1 -0
  330. package/dist/node.js +22 -0
  331. package/dist/node.js.map +1 -0
  332. package/dist/open-in-chat.js +31 -0
  333. package/dist/open-in-chat.js.map +1 -0
  334. package/dist/package-info.js +25 -0
  335. package/dist/package-info.js.map +1 -0
  336. package/dist/panel.js +10 -0
  337. package/dist/panel.js.map +1 -0
  338. package/dist/part-groups.js +18 -0
  339. package/dist/part-groups.js.map +1 -0
  340. package/dist/permission-mode-select.js +9 -0
  341. package/dist/permission-mode-select.js.map +1 -0
  342. package/dist/persona-sources.js +8 -0
  343. package/dist/persona-sources.js.map +1 -0
  344. package/dist/persona.js +14 -0
  345. package/dist/persona.js.map +1 -0
  346. package/dist/plan.js +32 -0
  347. package/dist/plan.js.map +1 -0
  348. package/dist/prompt-input-effort.js +9 -0
  349. package/dist/prompt-input-effort.js.map +1 -0
  350. package/dist/prompt-input-mode.js +10 -0
  351. package/dist/prompt-input-mode.js.map +1 -0
  352. package/dist/prompt-input-slash.js +12 -0
  353. package/dist/prompt-input-slash.js.map +1 -0
  354. package/dist/prompt-input.js +89 -0
  355. package/dist/prompt-input.js.map +1 -0
  356. package/dist/queue.js +37 -0
  357. package/dist/queue.js.map +1 -0
  358. package/dist/reasoning.js +23 -0
  359. package/dist/reasoning.js.map +1 -0
  360. package/dist/sandbox.js +28 -0
  361. package/dist/sandbox.js.map +1 -0
  362. package/dist/schema-display.js +33 -0
  363. package/dist/schema-display.js.map +1 -0
  364. package/dist/selection-toolbar.js +11 -0
  365. package/dist/selection-toolbar.js.map +1 -0
  366. package/dist/session-header.js +9 -0
  367. package/dist/session-header.js.map +1 -0
  368. package/dist/session-status-bar.js +9 -0
  369. package/dist/session-status-bar.js.map +1 -0
  370. package/dist/shimmer.js +9 -0
  371. package/dist/shimmer.js.map +1 -0
  372. package/dist/snippet.js +17 -0
  373. package/dist/snippet.js.map +1 -0
  374. package/dist/sources.js +17 -0
  375. package/dist/sources.js.map +1 -0
  376. package/dist/speech-input.js +9 -0
  377. package/dist/speech-input.js.map +1 -0
  378. package/dist/stack-trace.js +27 -0
  379. package/dist/stack-trace.js.map +1 -0
  380. package/dist/suggestion.js +16 -0
  381. package/dist/suggestion.js.map +1 -0
  382. package/dist/task.js +18 -0
  383. package/dist/task.js.map +1 -0
  384. package/dist/test-results.js +43 -0
  385. package/dist/test-results.js.map +1 -0
  386. package/dist/token-usage.js +27 -0
  387. package/dist/token-usage.js.map +1 -0
  388. package/dist/tool-result-card.js +9 -0
  389. package/dist/tool-result-card.js.map +1 -0
  390. package/dist/tool.js +27 -0
  391. package/dist/tool.js.map +1 -0
  392. package/dist/toolbar.js +10 -0
  393. package/dist/toolbar.js.map +1 -0
  394. package/dist/transcription.js +11 -0
  395. package/dist/transcription.js.map +1 -0
  396. package/dist/turn-status.js +9 -0
  397. package/dist/turn-status.js.map +1 -0
  398. package/dist/use-audio-level.js +9 -0
  399. package/dist/use-audio-level.js.map +1 -0
  400. package/dist/voice-selector.js +47 -0
  401. package/dist/voice-selector.js.map +1 -0
  402. package/dist/web-preview.js +19 -0
  403. package/dist/web-preview.js.map +1 -0
  404. package/package.json +11 -9
  405. package/schemas/a2ui-surface.v1.schema.json +5425 -0
  406. package/src/_chat-shell-rail.tsx +3 -1
  407. package/src/_flow-boundary.tsx +2 -2
  408. package/src/_persona-rive.tsx +27 -41
  409. package/src/a2ui/a2ui-surface.stories.tsx +662 -0
  410. package/src/a2ui/a2ui-surface.test.tsx +201 -0
  411. package/src/a2ui/a2ui-surface.tsx +354 -0
  412. package/src/a2ui/catalog.source.json +772 -0
  413. package/src/a2ui/core/a2ui-core.test.ts +178 -0
  414. package/src/a2ui/core/catalog.generated.ts +1006 -0
  415. package/src/a2ui/core/complete-json.ts +81 -0
  416. package/src/a2ui/core/index.ts +10 -0
  417. package/src/a2ui/core/schema.ts +111 -0
  418. package/src/a2ui/core/spec.ts +148 -0
  419. package/src/a2ui/core/validate.ts +266 -0
  420. package/src/a2ui/index.ts +7 -0
  421. package/src/a2ui/ui-catalog.tsx +225 -0
  422. package/src/a2ui/wave1-chart-spec.test.ts +87 -0
  423. package/src/a2ui/wave2-chart-spec.test.ts +108 -0
  424. package/src/agentic-workspace.stories.tsx +6 -45
  425. package/src/chat-shell.test.tsx +40 -10
  426. package/src/chat-shell.tsx +90 -44
  427. package/src/chat.stories.tsx +47 -42
  428. package/src/composer.stories.tsx +4 -5
  429. package/src/composer.test.tsx +45 -0
  430. package/src/composer.tsx +104 -21
  431. package/src/context-panel.stories.tsx +1 -1
  432. package/src/context-panel.tsx +1 -1
  433. package/src/conversation.stories.tsx +1 -0
  434. package/src/conversation.test.tsx +16 -0
  435. package/src/conversation.tsx +16 -2
  436. package/src/diff-view.stories.tsx +79 -0
  437. package/src/diff-view.tsx +3 -2
  438. package/src/file-tree.tsx +24 -17
  439. package/src/gallery.stories.tsx +42 -0
  440. package/src/gallery.tsx +2 -2
  441. package/src/grouped-parts.stories.tsx +23 -0
  442. package/src/grouped-parts.tsx +1 -1
  443. package/src/index.ts +1 -0
  444. package/src/message-compare.stories.tsx +40 -0
  445. package/src/message-compare.tsx +2 -1
  446. package/src/message-form.tsx +1 -1
  447. package/src/message-table.stories.tsx +24 -0
  448. package/src/message-table.tsx +1 -1
  449. package/src/package-info.tsx +12 -9
  450. package/src/reasoning.stories.tsx +27 -0
  451. package/src/reasoning.tsx +1 -1
  452. package/src/session-header.stories.tsx +35 -1
  453. package/src/session-header.tsx +1 -1
  454. package/src/sources.stories.tsx +30 -0
  455. package/src/sources.tsx +4 -1
  456. package/src/templates-ai-assistant.stories.tsx +151 -87
  457. package/src/tool.stories.tsx +39 -0
  458. package/src/tool.tsx +3 -3
  459. package/src/toolbar.tsx +0 -10
  460. package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
  461. package/dist/_persona-rive-JEG44YHX.js.map +0 -1
  462. /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
@@ -0,0 +1,478 @@
1
+ "use client";
2
+
3
+ // src/gallery.tsx
4
+ import {
5
+ AspectRatio,
6
+ Button,
7
+ Card,
8
+ Carousel,
9
+ CarouselContent,
10
+ CarouselItem,
11
+ CarouselNext,
12
+ CarouselPrevious,
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogTitle,
17
+ Skeleton,
18
+ downloadUrl
19
+ } from "@elabs-ai/components-ui";
20
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
21
+ import { useLocale } from "@elabs-ai/components-ui";
22
+ import { useReducedMotion } from "@elabs-ai/components-tokens";
23
+ import { Download, ImageOff, Maximize2 } from "lucide-react";
24
+ import {
25
+ createContext,
26
+ forwardRef,
27
+ use,
28
+ useCallback,
29
+ useEffect,
30
+ useMemo,
31
+ useRef,
32
+ useState
33
+ } from "react";
34
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
35
+ var GalleryContext = createContext(null);
36
+ function useGallery() {
37
+ const ctx = use(GalleryContext);
38
+ if (!ctx) throw new Error("useGallery must be used within <Gallery />");
39
+ return ctx;
40
+ }
41
+ function triggerDownload(image) {
42
+ void downloadUrl(image.downloadUrl ?? image.src, image.downloadName);
43
+ }
44
+ function GalleryImg({
45
+ src,
46
+ alt,
47
+ className,
48
+ loading,
49
+ showSkeleton = true
50
+ }) {
51
+ const [failed, setFailed] = useState(false);
52
+ const [loaded, setLoaded] = useState(false);
53
+ const imgRef = useRef(null);
54
+ useEffect(() => {
55
+ if (imgRef.current?.complete) setLoaded(true);
56
+ }, []);
57
+ if (failed) {
58
+ return /* @__PURE__ */ jsx(
59
+ "div",
60
+ {
61
+ className: cn(
62
+ "flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground",
63
+ className
64
+ ),
65
+ ...alt ? { role: "img", "aria-label": alt } : { "aria-hidden": true },
66
+ children: /* @__PURE__ */ jsx(ImageOff, { className: "size-6", "aria-hidden": "true" })
67
+ }
68
+ );
69
+ }
70
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ showSkeleton && !loaded ? /* @__PURE__ */ jsx(Skeleton, { className: "absolute inset-0 size-full rounded-none" }) : null,
72
+ /* @__PURE__ */ jsx(
73
+ "img",
74
+ {
75
+ ref: imgRef,
76
+ src,
77
+ alt,
78
+ loading,
79
+ onError: () => setFailed(true),
80
+ onLoad: () => setLoaded(true),
81
+ className
82
+ }
83
+ )
84
+ ] });
85
+ }
86
+ function GalleryDownloadButton({
87
+ image,
88
+ className,
89
+ tabIndex
90
+ }) {
91
+ const { t } = useLocale();
92
+ return /* @__PURE__ */ jsx(
93
+ Button,
94
+ {
95
+ type: "button",
96
+ size: "icon-sm",
97
+ variant: "outline",
98
+ tabIndex,
99
+ "aria-label": t("ai.gallery.downloadImage"),
100
+ className: cn("rounded-full bg-background/80 backdrop-blur-sm", className),
101
+ onClick: (event) => {
102
+ event.stopPropagation();
103
+ triggerDownload(image);
104
+ },
105
+ children: /* @__PURE__ */ jsx(Download, { "aria-hidden": "true" })
106
+ }
107
+ );
108
+ }
109
+ var HOVER_TOOLBAR = "opacity-0 transition-opacity duration-fast ease-standard group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none";
110
+ var TILT_PERSPECTIVE = 800;
111
+ var TILT_MAX_DEG = 6;
112
+ var TILT_SCALE = 1.04;
113
+ var tiltTransform = (rotateX, rotateY, scale) => `perspective(${TILT_PERSPECTIVE}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(${scale})`;
114
+ var TILT_REST = tiltTransform(0, 0, 1);
115
+ function GalleryTilt({ className, children }) {
116
+ const reduced = useReducedMotion();
117
+ const ref = useRef(null);
118
+ const rect = useRef(null);
119
+ const raf = useRef(null);
120
+ useEffect(
121
+ () => () => {
122
+ if (raf.current != null) cancelAnimationFrame(raf.current);
123
+ },
124
+ []
125
+ );
126
+ if (reduced) {
127
+ return /* @__PURE__ */ jsx(
128
+ "div",
129
+ {
130
+ className: cn(
131
+ "group relative rounded-lg transition-shadow duration-base ease-standard hover:z-10 hover:shadow-xl",
132
+ className
133
+ ),
134
+ children
135
+ }
136
+ );
137
+ }
138
+ const write = (transform) => {
139
+ if (ref.current) ref.current.style.transform = transform;
140
+ };
141
+ return /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ ref,
145
+ style: { transform: TILT_REST },
146
+ onPointerEnter: () => {
147
+ rect.current = ref.current?.getBoundingClientRect() ?? null;
148
+ write(tiltTransform(0, 0, TILT_SCALE));
149
+ },
150
+ onPointerMove: (event) => {
151
+ const r = rect.current;
152
+ if (!r) return;
153
+ const px = (event.clientX - r.left) / r.width - 0.5;
154
+ const py = (event.clientY - r.top) / r.height - 0.5;
155
+ if (raf.current != null) cancelAnimationFrame(raf.current);
156
+ raf.current = requestAnimationFrame(
157
+ () => write(tiltTransform(-py * TILT_MAX_DEG, px * TILT_MAX_DEG, TILT_SCALE))
158
+ );
159
+ },
160
+ onPointerLeave: () => {
161
+ if (raf.current != null) cancelAnimationFrame(raf.current);
162
+ write(TILT_REST);
163
+ },
164
+ className: cn(
165
+ "group relative rounded-lg transition-[transform,box-shadow] duration-base ease-standard hover:z-10 hover:shadow-2xl",
166
+ className
167
+ ),
168
+ children
169
+ }
170
+ );
171
+ }
172
+ function GalleryEmpty() {
173
+ const { t } = useLocale();
174
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-32 flex-col items-center justify-center gap-2 p-6 text-center text-muted-foreground", children: [
175
+ /* @__PURE__ */ jsx(ImageOff, { className: "size-6", "aria-hidden": "true" }),
176
+ /* @__PURE__ */ jsx("p", { className: "text-body", children: t("ai.gallery.noImages") })
177
+ ] });
178
+ }
179
+ function GallerySingle() {
180
+ const { t } = useLocale();
181
+ const { images, aspectRatio, hideDownload, actions } = useGallery();
182
+ const image = images[0];
183
+ if (!image) return null;
184
+ return /* @__PURE__ */ jsxs(GalleryTilt, { children: [
185
+ /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
186
+ GalleryImg,
187
+ {
188
+ src: image.src,
189
+ alt: image.alt,
190
+ loading: "lazy",
191
+ className: "size-full object-cover"
192
+ }
193
+ ) }),
194
+ /* @__PURE__ */ jsxs("div", { className: "absolute end-2 top-2 z-10 flex items-center gap-1", children: [
195
+ hideDownload ? null : /* @__PURE__ */ jsx(GalleryDownloadButton, { image, className: HOVER_TOOLBAR }),
196
+ /* @__PURE__ */ jsx(
197
+ Button,
198
+ {
199
+ type: "button",
200
+ size: "icon-sm",
201
+ variant: "outline",
202
+ "aria-label": t("ai.gallery.expandImage"),
203
+ className: "rounded-full bg-background/80 backdrop-blur-sm",
204
+ onClick: () => actions.open(0),
205
+ children: /* @__PURE__ */ jsx(Maximize2, { "aria-hidden": "true" })
206
+ }
207
+ )
208
+ ] })
209
+ ] });
210
+ }
211
+ function GalleryTile({ image, index }) {
212
+ const { images, aspectRatio, hideDownload, actions } = useGallery();
213
+ const total = images.length;
214
+ const label = image.title ?? (image.alt || `Image ${index + 1} of ${total}`);
215
+ return /* @__PURE__ */ jsxs(GalleryTilt, { children: [
216
+ /* @__PURE__ */ jsx(
217
+ "button",
218
+ {
219
+ type: "button",
220
+ onClick: () => actions.open(index),
221
+ "aria-label": label,
222
+ className: "block w-full rounded-lg focus-ring",
223
+ children: /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
224
+ GalleryImg,
225
+ {
226
+ src: image.thumbnailSrc ?? image.src,
227
+ alt: "",
228
+ loading: "lazy",
229
+ className: "size-full object-cover"
230
+ }
231
+ ) })
232
+ }
233
+ ),
234
+ hideDownload ? null : /* @__PURE__ */ jsx(
235
+ GalleryDownloadButton,
236
+ {
237
+ image,
238
+ className: cn("absolute end-2 top-2 z-10", HOVER_TOOLBAR)
239
+ }
240
+ )
241
+ ] });
242
+ }
243
+ function GalleryOverflowTile() {
244
+ const { images, maxVisible, aspectRatio, actions } = useGallery();
245
+ const total = images.length;
246
+ const count = total - maxVisible;
247
+ return /* @__PURE__ */ jsx(GalleryTilt, { children: /* @__PURE__ */ jsx(
248
+ "button",
249
+ {
250
+ type: "button",
251
+ onClick: () => actions.open(maxVisible),
252
+ "aria-label": `Show all ${total} images`,
253
+ className: "block w-full rounded-lg border border-border bg-muted focus-ring",
254
+ children: /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsxs("span", { className: "flex size-full items-center justify-center text-subtitle text-muted-foreground transition-colors duration-fast ease-standard group-hover:text-foreground motion-reduce:transition-none", children: [
255
+ "+",
256
+ count,
257
+ " more"
258
+ ] }) })
259
+ }
260
+ ) });
261
+ }
262
+ function GallerySkeletonTile() {
263
+ const { aspectRatio } = useGallery();
264
+ return /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(Skeleton, { className: "size-full rounded-none" }) });
265
+ }
266
+ function GallerySkeletonGrid() {
267
+ const { skeletonCount } = useGallery();
268
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", "aria-hidden": "true", children: Array.from({ length: Math.max(1, skeletonCount) }).map((_, i) => /* @__PURE__ */ jsx(GallerySkeletonTile, {}, `skeleton-${i}`)) });
269
+ }
270
+ function GalleryGrid() {
271
+ const { images, maxVisible, incoming, skeletonCount } = useGallery();
272
+ const total = images.length;
273
+ if (incoming) {
274
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", children: [
275
+ images.map((image, index) => /* @__PURE__ */ jsx(GalleryTile, { image, index }, `${image.src}-${index}`)),
276
+ Array.from({ length: skeletonCount }).map((_, i) => /* @__PURE__ */ jsx(GallerySkeletonTile, {}, `skeleton-${i}`))
277
+ ] });
278
+ }
279
+ const hasOverflow = total > maxVisible;
280
+ const visible = hasOverflow ? images.slice(0, maxVisible) : images;
281
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", children: [
282
+ visible.map((image, index) => /* @__PURE__ */ jsx(GalleryTile, { image, index }, `${image.src}-${index}`)),
283
+ hasOverflow ? /* @__PURE__ */ jsx(GalleryOverflowTile, {}) : null
284
+ ] });
285
+ }
286
+ function GalleryDetailPanel({
287
+ image,
288
+ index,
289
+ renderMeta
290
+ }) {
291
+ const { t } = useLocale();
292
+ const custom = renderMeta?.(image, index);
293
+ if (custom != null) {
294
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: custom });
295
+ }
296
+ const hasMeta = image.meta != null && image.meta.length > 0;
297
+ const hasBody = Boolean(image.description) || hasMeta;
298
+ if (!hasBody) {
299
+ return /* @__PURE__ */ jsx("p", { className: "text-body text-muted-foreground", children: t("ai.gallery.noDetails") });
300
+ }
301
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
302
+ image.description ? /* @__PURE__ */ jsx("p", { className: "text-body text-muted-foreground", children: image.description }) : null,
303
+ hasMeta ? /* @__PURE__ */ jsx("dl", { className: "flex flex-col gap-3", children: image.meta?.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
304
+ /* @__PURE__ */ jsx("dt", { className: "text-meta text-muted-foreground", children: item.label }),
305
+ /* @__PURE__ */ jsx("dd", { className: "text-body text-foreground", children: item.value })
306
+ ] }, `${item.label}-${i}`)) }) : null
307
+ ] });
308
+ }
309
+ function GalleryCarousel() {
310
+ const { t } = useLocale();
311
+ const { images, state, actions, hideDownload } = useGallery();
312
+ const [api, setApi] = useState();
313
+ const showArrows = images.length > 1;
314
+ const { setActiveIndex } = actions;
315
+ const [startIndex] = useState(() => state.activeIndex);
316
+ const opts = useMemo(() => ({ startIndex, loop: false }), [startIndex]);
317
+ useEffect(() => {
318
+ if (!api) return;
319
+ const onSelect = () => setActiveIndex(api.selectedScrollSnap());
320
+ onSelect();
321
+ api.on("select", onSelect);
322
+ api.on("reInit", onSelect);
323
+ return () => {
324
+ api.off("select", onSelect);
325
+ api.off("reInit", onSelect);
326
+ };
327
+ }, [api, setActiveIndex]);
328
+ useEffect(() => {
329
+ if (!api) return;
330
+ const raf = requestAnimationFrame(() => api.reInit());
331
+ return () => cancelAnimationFrame(raf);
332
+ }, [api]);
333
+ return /* @__PURE__ */ jsxs(
334
+ Carousel,
335
+ {
336
+ setApi,
337
+ opts,
338
+ className: "relative w-full",
339
+ "aria-label": t("ai.gallery.label"),
340
+ children: [
341
+ /* @__PURE__ */ jsx(CarouselContent, { children: images.map((image, index) => /* @__PURE__ */ jsx(
342
+ CarouselItem,
343
+ {
344
+ "aria-label": `${index + 1} of ${images.length}`,
345
+ children: /* @__PURE__ */ jsxs("div", { className: "group/slide relative flex items-center justify-center px-4", children: [
346
+ /* @__PURE__ */ jsx(
347
+ GalleryImg,
348
+ {
349
+ src: image.src,
350
+ alt: image.alt,
351
+ showSkeleton: false,
352
+ className: "max-h-[80vh] w-auto max-w-full object-contain"
353
+ }
354
+ ),
355
+ hideDownload ? null : /* @__PURE__ */ jsx(
356
+ GalleryDownloadButton,
357
+ {
358
+ image,
359
+ tabIndex: index === state.activeIndex ? 0 : -1,
360
+ className: "absolute end-14 top-3 opacity-0 transition-opacity duration-fast ease-standard group-hover/slide:opacity-100 group-focus-within/slide:opacity-100 motion-reduce:transition-none"
361
+ }
362
+ )
363
+ ] })
364
+ },
365
+ `${image.src}-${index}`
366
+ )) }),
367
+ showArrows ? /* @__PURE__ */ jsxs(Fragment, { children: [
368
+ /* @__PURE__ */ jsx(CarouselPrevious, { className: "start-4" }),
369
+ /* @__PURE__ */ jsx(CarouselNext, { className: "end-4" })
370
+ ] }) : null
371
+ ]
372
+ }
373
+ );
374
+ }
375
+ function GalleryModal() {
376
+ const { images, state, actions, renderMeta } = useGallery();
377
+ const total = images.length;
378
+ const active = images[state.activeIndex];
379
+ const fallbackTitle = active ? active.alt || `Image ${state.activeIndex + 1} of ${total}` : "Image preview";
380
+ return /* @__PURE__ */ jsx(Dialog, { open: state.open, onOpenChange: actions.setOpen, children: /* @__PURE__ */ jsxs(
381
+ DialogContent,
382
+ {
383
+ size: "full",
384
+ className: "grid grid-rows-[minmax(0,3fr)_minmax(0,2fr)] gap-0 overflow-hidden p-0 md:grid-cols-[minmax(0,1fr)_22rem] md:grid-rows-1",
385
+ children: [
386
+ /* @__PURE__ */ jsx("div", { className: "relative flex min-h-0 items-center justify-center overflow-hidden bg-muted", children: active ? /* @__PURE__ */ jsx(GalleryCarousel, {}) : null }),
387
+ /* @__PURE__ */ jsxs("aside", { className: "flex min-h-0 flex-col overflow-y-auto border-border bg-card p-6 max-md:border-t md:border-s", children: [
388
+ active?.title ? /* @__PURE__ */ jsx(DialogTitle, { className: "pe-8 text-title", children: active.title }) : /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: fallbackTitle }),
389
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: total > 1 ? `Image ${state.activeIndex + 1} of ${total}. Use the arrow keys to browse.` : "Image preview" }),
390
+ active ? /* @__PURE__ */ jsx("div", { className: "mt-4 flex-1", children: /* @__PURE__ */ jsx(
391
+ GalleryDetailPanel,
392
+ {
393
+ image: active,
394
+ index: state.activeIndex,
395
+ renderMeta
396
+ }
397
+ ) }) : null
398
+ ] }),
399
+ /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: active && total > 1 ? `${active.title ?? active.alt} \u2014 image ${state.activeIndex + 1} of ${total}` : "" })
400
+ ]
401
+ }
402
+ ) });
403
+ }
404
+ var Gallery = forwardRef(function Gallery2({
405
+ images,
406
+ maxVisible = 5,
407
+ aspectRatio = 1,
408
+ hideDownload = false,
409
+ loading = false,
410
+ expectedCount,
411
+ renderMeta,
412
+ className,
413
+ ...props
414
+ }, ref) {
415
+ const [open, setOpen] = useState(false);
416
+ const [activeIndex, setActiveIndex] = useState(0);
417
+ const openAt = useCallback((index) => {
418
+ setActiveIndex(index);
419
+ setOpen(true);
420
+ }, []);
421
+ const close = useCallback(() => setOpen(false), []);
422
+ const target = expectedCount ?? (loading ? maxVisible : void 0);
423
+ const incoming = target !== void 0 && images.length < target;
424
+ const skeletonCount = incoming ? Math.max(0, Math.min(target, maxVisible) - images.length) : 0;
425
+ const loadingFromEmpty = incoming && images.length === 0;
426
+ const [liveMsg, setLiveMsg] = useState("");
427
+ useEffect(() => {
428
+ setLiveMsg(loadingFromEmpty ? "Loading images" : "");
429
+ }, [loadingFromEmpty]);
430
+ const value = useMemo(
431
+ () => ({
432
+ images,
433
+ maxVisible,
434
+ aspectRatio,
435
+ hideDownload,
436
+ incoming,
437
+ skeletonCount,
438
+ renderMeta,
439
+ state: { open, activeIndex },
440
+ actions: { open: openAt, close, setActiveIndex, setOpen }
441
+ }),
442
+ [
443
+ images,
444
+ maxVisible,
445
+ aspectRatio,
446
+ hideDownload,
447
+ incoming,
448
+ skeletonCount,
449
+ renderMeta,
450
+ open,
451
+ activeIndex,
452
+ openAt,
453
+ close
454
+ ]
455
+ );
456
+ let body;
457
+ if (images.length === 0) {
458
+ body = incoming ? /* @__PURE__ */ jsx(GallerySkeletonGrid, {}) : /* @__PURE__ */ jsx(GalleryEmpty, {});
459
+ } else if (incoming) {
460
+ body = /* @__PURE__ */ jsx(GalleryGrid, {});
461
+ } else if (images.length === 1) {
462
+ body = /* @__PURE__ */ jsx(GallerySingle, {});
463
+ } else {
464
+ body = /* @__PURE__ */ jsx(GalleryGrid, {});
465
+ }
466
+ return /* @__PURE__ */ jsxs(GalleryContext.Provider, { value, children: [
467
+ /* @__PURE__ */ jsxs(Card, { ref, className: cn("p-2", className), ...props, children: [
468
+ /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: liveMsg }),
469
+ body
470
+ ] }),
471
+ images.length > 0 ? /* @__PURE__ */ jsx(GalleryModal, {}) : null
472
+ ] });
473
+ });
474
+
475
+ export {
476
+ Gallery
477
+ };
478
+ //# sourceMappingURL=chunk-3XNPMDZY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gallery.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Gallery — an OUTPUT-oriented image gallery for AI results / chat surfaces.\n *\n * A `Card` that renders a single image, or a thumbnail grid that collapses the\n * overflow into a \"+N more\" tile, and opens a unified lightbox `Dialog` with a\n * `Carousel` and a right-side metadata panel that tracks the active slide.\n *\n * Distinct from the other image surfaces in this package — don't merge them:\n * - `attachments.tsx` — composer INPUT (removable, file-typed thumbnails).\n * - `asset-preview.tsx` — a SINGLE asset in the context rail (type-switched).\n * - `image.tsx` — a base64 `<img>` wrapper for AI-SDK generated images.\n * Gallery is the multi-image lightbox: expand / download / carousel / metadata.\n *\n * The data model is framework-agnostic (plain `{ src, alt, … }`) — it does NOT\n * couple to the AI-SDK message model. Map a `UIMessage` part onto `GalleryImage`\n * in the app or a thin adapter.\n */\n\nimport {\n AspectRatio,\n Button,\n Card,\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogTitle,\n Skeleton,\n downloadUrl,\n type CarouselApi,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport { Download, ImageOff, Maximize2 } from \"lucide-react\";\nimport {\n createContext,\n forwardRef,\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\n/** One labelled metadata row rendered in the detail panel `<dl>`. */\nexport interface GalleryMetaItem {\n /** Field label (rendered muted, in the `<dt>`). */\n label: string;\n /** Field value — a string in the common case; `ReactNode` allows a link/badge. */\n value: ReactNode;\n}\n\n/** A single gallery image plus its optional metadata. */\nexport interface GalleryImage {\n /** Full-resolution source shown in the lightbox and standalone tile. */\n src: string;\n /** Required alt text (a11y). Pass `\"\"` to mark the image decorative. */\n alt: string;\n /** Cheaper thumbnail for the grid; falls back to `src`. */\n thumbnailSrc?: string;\n /** Headline shown in the panel; also feeds the dialog's accessible name. */\n title?: string;\n /** Long-form copy shown under the title in the detail panel. */\n description?: string;\n /** Structured key/value rows rendered as a `<dl>` in the detail panel. */\n meta?: GalleryMetaItem[];\n /** Overrides `src` for the download (e.g. a signed full-res URL). */\n downloadUrl?: string;\n /** Suggested download filename. */\n downloadName?: string;\n}\n\nexport interface GalleryProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Images to display. `0` → empty state, `1` → standalone, `≥2` → grid. */\n images: GalleryImage[];\n /** Thumbnails to show before collapsing the rest into a \"+N more\" tile. @default 5 */\n maxVisible?: number;\n /** Aspect ratio for grid tiles and the standalone image. @default 1 (square) */\n aspectRatio?: number;\n /** Hide every download affordance (e.g. rights-restricted assets). @default false */\n hideDownload?: boolean;\n /**\n * The set is still arriving (loading-states.md `loading` signal). Renders\n * skeleton tiles in the grid footprint instead of the empty state, and fills\n * the gap up to `expectedCount` (or `maxVisible`) while images stream in.\n * @default false\n */\n loading?: boolean;\n /**\n * Total images expected once the set finishes arriving. When\n * `images.length < expectedCount`, the arrived tiles render alongside\n * skeleton tiles for the missing ones, so the grid fills in place (no reflow).\n */\n expectedCount?: number;\n /**\n * Replace the default metadata body in the detail panel. Receives the\n * currently-active image, so it stays in sync with the carousel. The panel\n * chrome + title heading remain.\n */\n renderMeta?: (image: GalleryImage, index: number) => ReactNode;\n}\n\ninterface GalleryContextValue {\n images: GalleryImage[];\n maxVisible: number;\n aspectRatio: number;\n hideDownload: boolean;\n /** The set is still arriving — render skeleton tiles to fill the gap. */\n incoming: boolean;\n /** How many skeleton tiles to append after the arrived images. */\n skeletonCount: number;\n renderMeta?: GalleryProps[\"renderMeta\"];\n state: { open: boolean; activeIndex: number };\n actions: {\n open: (index: number) => void;\n close: () => void;\n setActiveIndex: (index: number) => void;\n setOpen: (open: boolean) => void;\n };\n}\n\nconst GalleryContext = createContext<GalleryContextValue | null>(null);\n\nfunction useGallery(): GalleryContextValue {\n const ctx = use(GalleryContext);\n if (!ctx) throw new Error(\"useGallery must be used within <Gallery />\");\n return ctx;\n}\n\nfunction triggerDownload(image: GalleryImage): void {\n void downloadUrl(image.downloadUrl ?? image.src, image.downloadName);\n}\n\n/**\n * `<img>` with a `Skeleton` placeholder until it loads (loading-states.md — the\n * box is already reserved by `AspectRatio`, so no CLS) and a token-styled\n * fallback box when the source fails to load.\n */\nfunction GalleryImg({\n src,\n alt,\n className,\n loading,\n showSkeleton = true,\n}: {\n src: string;\n alt: string;\n className?: string;\n loading?: \"lazy\" | \"eager\";\n /**\n * Show the `Skeleton` placeholder until load. Only meaningful inside a\n * space-reserved box (`AspectRatio`); the lightbox sets `false` because the\n * slide isn't sized to the image, so an `absolute inset-0` skeleton would fill\n * the whole padded slide rather than the image footprint.\n */\n showSkeleton?: boolean;\n}) {\n const [failed, setFailed] = useState(false);\n const [loaded, setLoaded] = useState(false);\n const imgRef = useRef<HTMLImageElement>(null);\n\n // Cached images can finish before React attaches `onLoad` — read `complete`.\n useEffect(() => {\n if (imgRef.current?.complete) setLoaded(true);\n }, []);\n\n if (failed) {\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\",\n className,\n )}\n {...(alt ? { role: \"img\", \"aria-label\": alt } : { \"aria-hidden\": true })}\n >\n <ImageOff className=\"size-6\" aria-hidden=\"true\" />\n </div>\n );\n }\n\n return (\n <>\n {showSkeleton && !loaded ? (\n <Skeleton className=\"absolute inset-0 size-full rounded-none\" />\n ) : null}\n <img\n ref={imgRef}\n src={src}\n alt={alt}\n loading={loading}\n onError={() => setFailed(true)}\n onLoad={() => setLoaded(true)}\n className={className}\n />\n </>\n );\n}\n\n/** Round, frosted icon button that downloads `image`. */\nfunction GalleryDownloadButton({\n image,\n className,\n tabIndex,\n}: {\n image: GalleryImage;\n className?: string;\n /** Off-screen carousel slides pass -1 so they're not phantom tab stops. */\n tabIndex?: number;\n}) {\n const { t } = useLocale();\n\n return (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"outline\"\n tabIndex={tabIndex}\n aria-label={t(\"ai.gallery.downloadImage\")}\n className={cn(\"rounded-full bg-background/80 backdrop-blur-sm\", className)}\n onClick={(event) => {\n event.stopPropagation();\n triggerDownload(image);\n }}\n >\n <Download aria-hidden=\"true\" />\n </Button>\n );\n}\n\nconst HOVER_TOOLBAR =\n \"opacity-0 transition-opacity duration-fast ease-standard group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none\";\n\n// \"Comes closer\" hover for a tile: a gentle scale-up + drop shadow, plus a 3D\n// tilt that tracks the cursor. Movement (tilt + scale) is gated by\n// useReducedMotion — the shadow still responds (\"reduced != none\", like Card).\n// The wrapper carries `group relative` (toolbar reveal + absolute children) and\n// NO overflow, so the shadow and the inner control's focus ring aren't clipped.\nconst TILT_PERSPECTIVE = 800; // px — larger = subtler tilt\nconst TILT_MAX_DEG = 6;\nconst TILT_SCALE = 1.04;\nconst tiltTransform = (rotateX: number, rotateY: number, scale: number) =>\n `perspective(${TILT_PERSPECTIVE}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(${scale})`;\nconst TILT_REST = tiltTransform(0, 0, 1);\n\nfunction GalleryTilt({ className, children }: { className?: string; children: ReactNode }) {\n const reduced = useReducedMotion();\n const ref = useRef<HTMLDivElement>(null);\n const rect = useRef<DOMRect | null>(null);\n const raf = useRef<number | null>(null);\n\n useEffect(\n () => () => {\n if (raf.current != null) cancelAnimationFrame(raf.current);\n },\n [],\n );\n\n // Reduced motion: no tilt/scale; the shadow alone signals the lift.\n if (reduced) {\n return (\n <div\n className={cn(\n \"group relative rounded-lg transition-shadow duration-base ease-standard hover:z-10 hover:shadow-xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n }\n\n const write = (transform: string) => {\n if (ref.current) ref.current.style.transform = transform;\n };\n\n return (\n <div\n ref={ref}\n style={{ transform: TILT_REST }}\n onPointerEnter={() => {\n // Cache geometry once per hover (no per-move layout read); grow on enter.\n rect.current = ref.current?.getBoundingClientRect() ?? null;\n write(tiltTransform(0, 0, TILT_SCALE));\n }}\n onPointerMove={(event) => {\n const r = rect.current;\n if (!r) return;\n const px = (event.clientX - r.left) / r.width - 0.5;\n const py = (event.clientY - r.top) / r.height - 0.5;\n if (raf.current != null) cancelAnimationFrame(raf.current);\n raf.current = requestAnimationFrame(() =>\n write(tiltTransform(-py * TILT_MAX_DEG, px * TILT_MAX_DEG, TILT_SCALE)),\n );\n }}\n onPointerLeave={() => {\n if (raf.current != null) cancelAnimationFrame(raf.current);\n write(TILT_REST);\n }}\n className={cn(\n \"group relative rounded-lg transition-[transform,box-shadow] duration-base ease-standard hover:z-10 hover:shadow-2xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\nfunction GalleryEmpty() {\n const { t } = useLocale();\n return (\n <div className=\"flex min-h-32 flex-col items-center justify-center gap-2 p-6 text-center text-muted-foreground\">\n <ImageOff className=\"size-6\" aria-hidden=\"true\" />\n <p className=\"text-body\">{t(\"ai.gallery.noImages\")}</p>\n </div>\n );\n}\n\n/** The single-image branch — a framed image with an always-on expand button. */\nfunction GallerySingle() {\n const { t } = useLocale();\n const { images, aspectRatio, hideDownload, actions } = useGallery();\n const image = images[0];\n if (!image) return null;\n\n return (\n <GalleryTilt>\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n <GalleryImg\n src={image.src}\n alt={image.alt}\n loading=\"lazy\"\n className=\"size-full object-cover\"\n />\n </AspectRatio>\n <div className=\"absolute end-2 top-2 z-10 flex items-center gap-1\">\n {hideDownload ? null : <GalleryDownloadButton image={image} className={HOVER_TOOLBAR} />}\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"outline\"\n aria-label={t(\"ai.gallery.expandImage\")}\n className=\"rounded-full bg-background/80 backdrop-blur-sm\"\n onClick={() => actions.open(0)}\n >\n <Maximize2 aria-hidden=\"true\" />\n </Button>\n </div>\n </GalleryTilt>\n );\n}\n\n/** One thumbnail in the grid — a button that opens the lightbox at `index`. */\nfunction GalleryTile({ image, index }: { image: GalleryImage; index: number }) {\n const { images, aspectRatio, hideDownload, actions } = useGallery();\n const total = images.length;\n const label = image.title ?? (image.alt || `Image ${index + 1} of ${total}`);\n\n return (\n <GalleryTilt>\n <button\n type=\"button\"\n onClick={() => actions.open(index)}\n aria-label={label}\n className=\"block w-full rounded-lg focus-ring\"\n >\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n {/* Decorative: the button carries the accessible name. */}\n <GalleryImg\n src={image.thumbnailSrc ?? image.src}\n alt=\"\"\n loading=\"lazy\"\n className=\"size-full object-cover\"\n />\n </AspectRatio>\n </button>\n {hideDownload ? null : (\n <GalleryDownloadButton\n image={image}\n className={cn(\"absolute end-2 top-2 z-10\", HOVER_TOOLBAR)}\n />\n )}\n </GalleryTilt>\n );\n}\n\n/** The \"+N more\" tile — same footprint as a thumbnail; opens the carousel. */\nfunction GalleryOverflowTile() {\n const { images, maxVisible, aspectRatio, actions } = useGallery();\n const total = images.length;\n const count = total - maxVisible;\n\n return (\n <GalleryTilt>\n <button\n type=\"button\"\n onClick={() => actions.open(maxVisible)}\n aria-label={`Show all ${total} images`}\n className=\"block w-full rounded-lg border border-border bg-muted focus-ring\"\n >\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n <span className=\"flex size-full items-center justify-center text-subtitle text-muted-foreground transition-colors duration-fast ease-standard group-hover:text-foreground motion-reduce:transition-none\">\n +{count} more\n </span>\n </AspectRatio>\n </button>\n </GalleryTilt>\n );\n}\n\n/** A grid tile placeholder — same footprint as a thumbnail, shown while loading. */\nfunction GallerySkeletonTile() {\n const { aspectRatio } = useGallery();\n return (\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n <Skeleton className=\"size-full rounded-none\" />\n </AspectRatio>\n );\n}\n\n/**\n * The all-skeleton grid for the loading-from-empty case (no images yet).\n * Decorative — the loading announcement comes from the persistent live region on\n * the always-mounted `Card` (so it fires reliably; ARIA22).\n */\nfunction GallerySkeletonGrid() {\n const { skeletonCount } = useGallery();\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\" aria-hidden=\"true\">\n {Array.from({ length: Math.max(1, skeletonCount) }).map((_, i) => (\n <GallerySkeletonTile key={`skeleton-${i}`} />\n ))}\n </div>\n );\n}\n\nfunction GalleryGrid() {\n const { images, maxVisible, incoming, skeletonCount } = useGallery();\n const total = images.length;\n\n // While the set is still arriving, render the arrived tiles plus skeleton\n // tiles for the missing ones — no \"+N more\" collapse until the set settles.\n if (incoming) {\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {images.map((image, index) => (\n <GalleryTile key={`${image.src}-${index}`} image={image} index={index} />\n ))}\n {Array.from({ length: skeletonCount }).map((_, i) => (\n <GallerySkeletonTile key={`skeleton-${i}`} />\n ))}\n </div>\n );\n }\n\n const hasOverflow = total > maxVisible;\n const visible = hasOverflow ? images.slice(0, maxVisible) : images;\n\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {visible.map((image, index) => (\n <GalleryTile key={`${image.src}-${index}`} image={image} index={index} />\n ))}\n {hasOverflow ? <GalleryOverflowTile /> : null}\n </div>\n );\n}\n\n/** Default detail-panel body: description + a `<dl>` of `meta` rows. */\nfunction GalleryDetailPanel({\n image,\n index,\n renderMeta,\n}: {\n image: GalleryImage;\n index: number;\n renderMeta?: GalleryProps[\"renderMeta\"];\n}) {\n const { t } = useLocale();\n const custom = renderMeta?.(image, index);\n if (custom != null) {\n return <div className=\"flex flex-col gap-4\">{custom}</div>;\n }\n\n const hasMeta = image.meta != null && image.meta.length > 0;\n const hasBody = Boolean(image.description) || hasMeta;\n\n if (!hasBody) {\n return <p className=\"text-body text-muted-foreground\">{t(\"ai.gallery.noDetails\")}</p>;\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n {image.description ? (\n <p className=\"text-body text-muted-foreground\">{image.description}</p>\n ) : null}\n {hasMeta ? (\n <dl className=\"flex flex-col gap-3\">\n {image.meta?.map((item, i) => (\n <div key={`${item.label}-${i}`} className=\"flex flex-col gap-0.5\">\n <dt className=\"text-meta text-muted-foreground\">{item.label}</dt>\n <dd className=\"text-body text-foreground\">{item.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n );\n}\n\n/** The lightbox carousel — one slide per image; syncs `activeIndex` on select. */\nfunction GalleryCarousel() {\n const { t } = useLocale();\n const { images, state, actions, hideDownload } = useGallery();\n const [api, setApi] = useState<CarouselApi>();\n const showArrows = images.length > 1;\n const { setActiveIndex } = actions;\n // Capture the open-at index ONCE (the modal remounts the carousel each open).\n // Tying opts.startIndex to the live activeIndex hands Embla a changed options\n // object on every slide (it deep-compares) → constant reInit → the native\n // slide animation never plays. A stable opts keeps Embla's scroll intact;\n // activeIndex flows one-way (carousel select → panel), never back into opts.\n const [startIndex] = useState(() => state.activeIndex);\n const opts = useMemo(() => ({ startIndex, loop: false }), [startIndex]);\n\n useEffect(() => {\n if (!api) return;\n const onSelect = () => setActiveIndex(api.selectedScrollSnap());\n onSelect();\n api.on(\"select\", onSelect);\n api.on(\"reInit\", onSelect);\n return () => {\n api.off(\"select\", onSelect);\n api.off(\"reInit\", onSelect);\n };\n }, [api, setActiveIndex]);\n\n // The dialog animates in (zoom-in-95); re-measure once laid out so Embla\n // aligns and lands on the opened slide.\n useEffect(() => {\n if (!api) return;\n const raf = requestAnimationFrame(() => api.reInit());\n return () => cancelAnimationFrame(raf);\n }, [api]);\n\n return (\n <Carousel\n setApi={setApi}\n opts={opts}\n className=\"relative w-full\"\n aria-label={t(\"ai.gallery.label\")}\n >\n <CarouselContent>\n {images.map((image, index) => (\n <CarouselItem\n key={`${image.src}-${index}`}\n aria-label={`${index + 1} of ${images.length}`}\n >\n <div className=\"group/slide relative flex items-center justify-center px-4\">\n <GalleryImg\n src={image.src}\n alt={image.alt}\n showSkeleton={false}\n className=\"max-h-[80vh] w-auto max-w-full object-contain\"\n />\n {hideDownload ? null : (\n <GalleryDownloadButton\n image={image}\n // Off-screen slides stay in the DOM (Embla); keep only the\n // active slide's download button in the tab order.\n tabIndex={index === state.activeIndex ? 0 : -1}\n className=\"absolute end-14 top-3 opacity-0 transition-opacity duration-fast ease-standard group-hover/slide:opacity-100 group-focus-within/slide:opacity-100 motion-reduce:transition-none\"\n />\n )}\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n {showArrows ? (\n <>\n <CarouselPrevious className=\"start-4\" />\n <CarouselNext className=\"end-4\" />\n </>\n ) : null}\n </Carousel>\n );\n}\n\nfunction GalleryModal() {\n const { images, state, actions, renderMeta } = useGallery();\n const total = images.length;\n const active = images[state.activeIndex];\n\n const fallbackTitle = active\n ? active.alt || `Image ${state.activeIndex + 1} of ${total}`\n : \"Image preview\";\n\n return (\n <Dialog open={state.open} onOpenChange={actions.setOpen}>\n <DialogContent\n size=\"full\"\n className=\"grid grid-rows-[minmax(0,3fr)_minmax(0,2fr)] gap-0 overflow-hidden p-0 md:grid-cols-[minmax(0,1fr)_22rem] md:grid-rows-1\"\n >\n <div className=\"relative flex min-h-0 items-center justify-center overflow-hidden bg-muted\">\n {active ? <GalleryCarousel /> : null}\n </div>\n <aside className=\"flex min-h-0 flex-col overflow-y-auto border-border bg-card p-6 max-md:border-t md:border-s\">\n {active?.title ? (\n <DialogTitle className=\"pe-8 text-title\">{active.title}</DialogTitle>\n ) : (\n <DialogTitle className=\"sr-only\">{fallbackTitle}</DialogTitle>\n )}\n <DialogDescription className=\"sr-only\">\n {total > 1\n ? `Image ${state.activeIndex + 1} of ${total}. Use the arrow keys to browse.`\n : \"Image preview\"}\n </DialogDescription>\n {active ? (\n <div className=\"mt-4 flex-1\">\n <GalleryDetailPanel\n image={active}\n index={state.activeIndex}\n renderMeta={renderMeta}\n />\n </div>\n ) : null}\n </aside>\n {/* Carousel navigation only changes the title's text, which doesn't\n re-announce. A polite live region orients screen-reader users on each\n slide change. (sr-only is position:absolute, so it adds no grid cell.) */}\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {active && total > 1\n ? `${active.title ?? active.alt} — image ${state.activeIndex + 1} of ${total}`\n : \"\"}\n </span>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport const Gallery = forwardRef<HTMLDivElement, GalleryProps>(function Gallery(\n {\n images,\n maxVisible = 5,\n aspectRatio = 1,\n hideDownload = false,\n loading = false,\n expectedCount,\n renderMeta,\n className,\n ...props\n },\n ref,\n) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n\n const openAt = useCallback((index: number) => {\n setActiveIndex(index);\n setOpen(true);\n }, []);\n const close = useCallback(() => setOpen(false), []);\n\n // The set is \"incoming\" while it's still arriving — either an explicit\n // `loading` flag, or fewer images than the `expectedCount`. Fill the grid up\n // to the target (capped at the visible footprint) with skeleton tiles.\n const target = expectedCount ?? (loading ? maxVisible : undefined);\n const incoming = target !== undefined && images.length < target;\n const skeletonCount = incoming ? Math.max(0, Math.min(target, maxVisible) - images.length) : 0;\n\n // Loading announcement: the live region must exist BEFORE its text appears\n // (ARIA22), so the `sr-only` status span below is always mounted and its label\n // is set one tick later via this effect — reliable on NVDA + Chrome.\n const loadingFromEmpty = incoming && images.length === 0;\n const [liveMsg, setLiveMsg] = useState(\"\");\n useEffect(() => {\n setLiveMsg(loadingFromEmpty ? \"Loading images\" : \"\");\n }, [loadingFromEmpty]);\n\n const value = useMemo<GalleryContextValue>(\n () => ({\n images,\n maxVisible,\n aspectRatio,\n hideDownload,\n incoming,\n skeletonCount,\n renderMeta,\n state: { open, activeIndex },\n actions: { open: openAt, close, setActiveIndex, setOpen },\n }),\n [\n images,\n maxVisible,\n aspectRatio,\n hideDownload,\n incoming,\n skeletonCount,\n renderMeta,\n open,\n activeIndex,\n openAt,\n close,\n ],\n );\n\n let body: ReactNode;\n if (images.length === 0) {\n body = incoming ? <GallerySkeletonGrid /> : <GalleryEmpty />;\n } else if (incoming) {\n body = <GalleryGrid />;\n } else if (images.length === 1) {\n body = <GallerySingle />;\n } else {\n body = <GalleryGrid />;\n }\n\n return (\n <GalleryContext.Provider value={value}>\n <Card ref={ref} className={cn(\"p-2\", className)} {...props}>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {liveMsg}\n </span>\n {body}\n </Card>\n {images.length > 0 ? <GalleryModal /> : null}\n </GalleryContext.Provider>\n );\n});\n"],"mappings":"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","names":["Gallery"]}
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ // src/shimmer.tsx
4
+ import { useReducedMotion } from "@elabs-ai/components-tokens";
5
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
6
+ import { motion } from "motion/react";
7
+ import { memo, useMemo } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var motionComponentCache = /* @__PURE__ */ new Map();
10
+ var getMotionComponent = (element) => {
11
+ let component = motionComponentCache.get(element);
12
+ if (!component) {
13
+ component = motion.create(element);
14
+ motionComponentCache.set(element, component);
15
+ }
16
+ return component;
17
+ };
18
+ var ShimmerComponent = ({
19
+ children,
20
+ as: Component = "p",
21
+ className,
22
+ duration = 2,
23
+ spread = 2
24
+ }) => {
25
+ const MotionComponent = getMotionComponent(Component);
26
+ const reducedMotion = useReducedMotion();
27
+ const dynamicSpread = useMemo(() => (children?.length ?? 0) * spread, [children, spread]);
28
+ return /* @__PURE__ */ jsx(
29
+ MotionComponent,
30
+ {
31
+ animate: { backgroundPosition: "0% center" },
32
+ className: cn(
33
+ "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
34
+ "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
35
+ className
36
+ ),
37
+ initial: { backgroundPosition: "100% center" },
38
+ style: {
39
+ "--spread": `${dynamicSpread}px`,
40
+ backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
41
+ },
42
+ transition: reducedMotion ? { duration: 0, repeat: 0 } : {
43
+ duration,
44
+ ease: "linear",
45
+ repeat: Number.POSITIVE_INFINITY
46
+ },
47
+ children
48
+ }
49
+ );
50
+ };
51
+ var Shimmer = memo(ShimmerComponent);
52
+
53
+ export {
54
+ Shimmer
55
+ };
56
+ //# sourceMappingURL=chunk-3YPT3V7L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shimmer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { MotionProps } from \"motion/react\";\nimport { motion } from \"motion/react\";\nimport type { CSSProperties, ElementType, JSX } from \"react\";\nimport { memo, useMemo } from \"react\";\n\ntype MotionHTMLProps = MotionProps & Record<string, unknown>;\n\n// Cache motion components at module level to avoid creating during render\nconst motionComponentCache = new Map<\n keyof JSX.IntrinsicElements,\n React.ComponentType<MotionHTMLProps>\n>();\n\nconst getMotionComponent = (element: keyof JSX.IntrinsicElements) => {\n let component = motionComponentCache.get(element);\n if (!component) {\n component = motion.create(element);\n motionComponentCache.set(element, component);\n }\n return component;\n};\n\nexport interface TextShimmerProps {\n children: string;\n as?: ElementType;\n className?: string;\n duration?: number;\n spread?: number;\n}\n\nconst ShimmerComponent = ({\n children,\n as: Component = \"p\",\n className,\n duration = 2,\n spread = 2,\n}: TextShimmerProps) => {\n const MotionComponent = getMotionComponent(Component as keyof JSX.IntrinsicElements);\n\n // The CSS --motion-factor gate cannot reach this JS timeline; resolve the\n // motion preference (user > OS) here and stop the infinite sweep when reduced.\n const reducedMotion = useReducedMotion();\n\n const dynamicSpread = useMemo(() => (children?.length ?? 0) * spread, [children, spread]);\n\n return (\n <MotionComponent\n animate={{ backgroundPosition: \"0% center\" }}\n className={cn(\n \"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent\",\n \"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]\",\n className,\n )}\n initial={{ backgroundPosition: \"100% center\" }}\n style={\n {\n \"--spread\": `${dynamicSpread}px`,\n backgroundImage:\n \"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))\",\n } as CSSProperties\n }\n transition={\n reducedMotion\n ? { duration: 0, repeat: 0 }\n : {\n duration,\n ease: \"linear\",\n repeat: Number.POSITIVE_INFINITY,\n }\n }\n >\n {children}\n </MotionComponent>\n );\n};\n\nexport const Shimmer = memo(ShimmerComponent);\n"],"mappings":";;;AAEA,SAAS,wBAAwB;AACjC,SAAS,UAAU;AAEnB,SAAS,cAAc;AAEvB,SAAS,MAAM,eAAe;AA2C1B;AAtCJ,IAAM,uBAAuB,oBAAI,IAG/B;AAEF,IAAM,qBAAqB,CAAC,YAAyC;AACnE,MAAI,YAAY,qBAAqB,IAAI,OAAO;AAChD,MAAI,CAAC,WAAW;AACd,gBAAY,OAAO,OAAO,OAAO;AACjC,yBAAqB,IAAI,SAAS,SAAS;AAAA,EAC7C;AACA,SAAO;AACT;AAUA,IAAM,mBAAmB,CAAC;AAAA,EACxB;AAAA,EACA,IAAI,YAAY;AAAA,EAChB;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AACX,MAAwB;AACtB,QAAM,kBAAkB,mBAAmB,SAAwC;AAInF,QAAM,gBAAgB,iBAAiB;AAEvC,QAAM,gBAAgB,QAAQ,OAAO,UAAU,UAAU,KAAK,QAAQ,CAAC,UAAU,MAAM,CAAC;AAExF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,EAAE,oBAAoB,YAAY;AAAA,MAC3C,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,SAAS,EAAE,oBAAoB,cAAc;AAAA,MAC7C,OACE;AAAA,QACE,YAAY,GAAG,aAAa;AAAA,QAC5B,iBACE;AAAA,MACJ;AAAA,MAEF,YACE,gBACI,EAAE,UAAU,GAAG,QAAQ,EAAE,IACzB;AAAA,QACE;AAAA,QACA,MAAM;AAAA,QACN,QAAQ,OAAO;AAAA,MACjB;AAAA,MAGL;AAAA;AAAA,EACH;AAEJ;AAEO,IAAM,UAAU,KAAK,gBAAgB;","names":[]}