@djangocfg/ui-tools 2.1.404 → 2.1.408

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 (336) hide show
  1. package/README.md +9 -11
  2. package/dist/file-icon/index.cjs +449 -61
  3. package/dist/file-icon/index.cjs.map +1 -1
  4. package/dist/file-icon/index.d.cts +56 -18
  5. package/dist/file-icon/index.d.ts +56 -18
  6. package/dist/file-icon/index.mjs +448 -62
  7. package/dist/file-icon/index.mjs.map +1 -1
  8. package/dist/tree/index.cjs +49 -22
  9. package/dist/tree/index.cjs.map +1 -1
  10. package/dist/tree/index.d.cts +9 -3
  11. package/dist/tree/index.d.ts +9 -3
  12. package/dist/tree/index.mjs +49 -22
  13. package/dist/tree/index.mjs.map +1 -1
  14. package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
  15. package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
  16. package/package.json +13 -16
  17. package/src/components/FloatingToolbar/index.tsx +37 -3
  18. package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
  19. package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
  20. package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
  21. package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
  22. package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
  23. package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
  24. package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
  25. package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
  26. package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
  27. package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
  28. package/src/lib/page-snapshot/capture/budget.ts +95 -0
  29. package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
  30. package/src/lib/page-snapshot/capture/classify.ts +111 -0
  31. package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
  32. package/src/lib/page-snapshot/capture/fold.ts +96 -0
  33. package/src/lib/page-snapshot/capture/scope.ts +169 -0
  34. package/src/lib/page-snapshot/capture/walk.ts +250 -0
  35. package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
  36. package/src/lib/page-snapshot/cst/directives.ts +47 -0
  37. package/src/lib/page-snapshot/cst/payload.ts +50 -0
  38. package/src/lib/page-snapshot/cst/serialize.ts +84 -0
  39. package/src/lib/page-snapshot/cst/types.ts +115 -0
  40. package/src/lib/page-snapshot/engine.ts +176 -0
  41. package/src/lib/page-snapshot/index.ts +93 -0
  42. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
  43. package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
  44. package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
  45. package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
  46. package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
  47. package/src/lib/page-snapshot/react/provider.tsx +162 -0
  48. package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
  49. package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
  50. package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
  51. package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
  52. package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
  53. package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
  54. package/src/lib/page-snapshot/redaction/audit.ts +58 -0
  55. package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
  56. package/src/lib/page-snapshot/redaction/index.ts +75 -0
  57. package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
  58. package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
  59. package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
  60. package/src/lib/page-snapshot/refs/registry.ts +46 -0
  61. package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
  62. package/src/lib/page-snapshot/staleness/hash.ts +20 -0
  63. package/src/lib/page-snapshot/tokens.ts +15 -0
  64. package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
  65. package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
  66. package/src/tools/AudioPlayer/lazy.tsx +13 -27
  67. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  68. package/src/tools/Chat/README.md +267 -39
  69. package/src/tools/Chat/composer/Composer.tsx +471 -0
  70. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  71. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  72. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  73. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  74. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  75. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  76. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  77. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  78. package/src/tools/Chat/composer/index.ts +45 -0
  79. package/src/tools/Chat/composer/size-context.tsx +26 -0
  80. package/src/tools/Chat/composer/types.ts +143 -0
  81. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  82. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  83. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  84. package/src/tools/Chat/core/index.ts +23 -1
  85. package/src/tools/Chat/core/markdown.ts +1 -1
  86. package/src/tools/Chat/core/metadata.ts +47 -0
  87. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  88. package/src/tools/Chat/core/transport/http.ts +71 -32
  89. package/src/tools/Chat/core/transport/sse.ts +18 -10
  90. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  91. package/src/tools/Chat/highlight/README.md +103 -0
  92. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  93. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  94. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  95. package/src/tools/Chat/highlight/index.ts +21 -0
  96. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  97. package/src/tools/Chat/highlight/types.ts +49 -0
  98. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  99. package/src/tools/Chat/hooks/index.ts +0 -5
  100. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  101. package/src/tools/Chat/hooks/useChat.ts +47 -14
  102. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  103. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  104. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  105. package/src/tools/Chat/index.ts +25 -219
  106. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  107. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  108. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  109. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  110. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  111. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  112. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  113. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  114. package/src/tools/Chat/launcher/header/index.ts +26 -0
  115. package/src/tools/Chat/launcher/index.ts +3 -10
  116. package/src/tools/Chat/lazy.tsx +38 -284
  117. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  118. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  119. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  120. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  121. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  122. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  123. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  124. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  125. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  126. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  127. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  128. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  130. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  131. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  132. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  133. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  134. package/src/tools/Chat/public.ts +212 -0
  135. package/src/tools/Chat/shell/ChatRoot.tsx +326 -0
  136. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  137. package/src/tools/Chat/shell/index.ts +15 -0
  138. package/src/tools/Chat/types/block.ts +120 -0
  139. package/src/tools/Chat/types/config.ts +0 -5
  140. package/src/tools/Chat/types/index.ts +17 -0
  141. package/src/tools/Chat/types/message.ts +3 -0
  142. package/src/tools/Chat/utils/index.ts +4 -0
  143. package/src/tools/CodeEditor/README.md +4 -6
  144. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  145. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  146. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  147. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  148. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  149. package/src/tools/CodeEditor/lazy.tsx +6 -0
  150. package/src/tools/CodeEditor/lib/index.ts +1 -1
  151. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  152. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  153. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  154. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  155. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  156. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  157. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  158. package/src/tools/CronScheduler/index.tsx +1 -1
  159. package/src/tools/CronScheduler/types/index.ts +8 -3
  160. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  161. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  162. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  163. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  164. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  165. package/src/tools/FileIcon/index.ts +4 -0
  166. package/src/tools/FileIcon/loader.ts +17 -35
  167. package/src/tools/FileIcon/specialFolders.ts +18 -0
  168. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  169. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  170. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  171. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  172. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  173. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  174. package/src/tools/ImageViewer/components/ImageViewer.tsx +106 -26
  175. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  176. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  177. package/src/tools/ImageViewer/utils/index.ts +1 -0
  178. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  179. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  180. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  181. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  182. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  183. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  184. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  185. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  186. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  187. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  188. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  189. package/src/tools/JsonForm/widgets/index.ts +1 -0
  190. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  191. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  192. package/src/tools/LottiePlayer/index.tsx +14 -4
  193. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  194. package/src/tools/LottiePlayer/types.ts +31 -1
  195. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  196. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  197. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  198. package/src/tools/Map/components/MapCluster.tsx +28 -21
  199. package/src/tools/Map/components/MapContainer.tsx +11 -4
  200. package/src/tools/Map/components/MapLegend.tsx +46 -15
  201. package/src/tools/Map/components/MapMarker.tsx +31 -2
  202. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  203. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  204. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  205. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  206. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  207. package/src/tools/MarkdownEditor/styles.css +13 -0
  208. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  209. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  210. package/src/tools/MarkdownMessage/components.tsx +22 -9
  211. package/src/tools/MarkdownMessage/types.ts +24 -1
  212. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  213. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  214. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  215. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  216. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  217. package/src/tools/Mermaid/index.tsx +6 -0
  218. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  221. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  222. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  223. package/src/tools/OpenapiViewer/constants.ts +3 -0
  224. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  225. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  226. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  227. package/src/tools/PrettyCode/lazy.tsx +1 -1
  228. package/src/tools/SpeechRecognition/README.md +1 -1
  229. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  230. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  231. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  232. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  233. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  234. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  235. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  236. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  237. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  238. package/src/tools/Tree/README.md +4 -8
  239. package/src/tools/Tree/TreeRoot.tsx +22 -10
  240. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  241. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  242. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  243. package/src/tools/Tree/data/flatten.ts +10 -4
  244. package/src/tools/Tree/types.ts +4 -0
  245. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  246. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  247. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  248. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  249. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  250. package/src/tools/Uploader/utils/formatters.ts +8 -3
  251. package/src/tools/VideoPlayer/README.md +87 -230
  252. package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
  253. package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
  254. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +39 -0
  255. package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
  256. package/src/tools/VideoPlayer/canvas/index.ts +12 -0
  257. package/src/tools/VideoPlayer/canvas/jsx-augmentation.ts +47 -0
  258. package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
  259. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +40 -0
  260. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +78 -0
  261. package/src/tools/VideoPlayer/index.ts +51 -65
  262. package/src/tools/VideoPlayer/lazy.tsx +11 -54
  263. package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
  264. package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
  265. package/src/tools/VideoPlayer/parts/index.ts +15 -0
  266. package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
  267. package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
  268. package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
  269. package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
  270. package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
  271. package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
  272. package/src/tools/VideoPlayer/styles/video-player.css +141 -0
  273. package/src/tools/VideoPlayer/types.ts +82 -0
  274. package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
  275. package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
  276. package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
  277. package/src/tools/index.ts +37 -29
  278. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  279. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  280. package/src/tools/Chat/components/Composer.tsx +0 -216
  281. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  282. package/src/tools/Chat/types.ts +0 -9
  283. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  284. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  285. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  286. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  287. package/src/tools/Tour/README.md +0 -373
  288. package/src/tools/Tour/components/Tour.tsx +0 -12
  289. package/src/tools/Tour/components/TourContent.tsx +0 -171
  290. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  291. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  292. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  293. package/src/tools/Tour/components/index.ts +0 -5
  294. package/src/tools/Tour/context/TourContext.ts +0 -19
  295. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  296. package/src/tools/Tour/context/index.ts +0 -2
  297. package/src/tools/Tour/hooks/index.ts +0 -3
  298. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  299. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  300. package/src/tools/Tour/hooks/useTour.ts +0 -42
  301. package/src/tools/Tour/index.ts +0 -38
  302. package/src/tools/Tour/types/index.ts +0 -224
  303. package/src/tools/Tour/utils/dom.ts +0 -98
  304. package/src/tools/Tour/utils/index.ts +0 -3
  305. package/src/tools/Tour/utils/logger.ts +0 -3
  306. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  307. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  308. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
  309. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  310. package/src/tools/VideoPlayer/components/index.ts +0 -14
  311. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  312. package/src/tools/VideoPlayer/context/index.ts +0 -8
  313. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  314. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
  315. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
  316. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  317. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  318. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
  319. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  320. package/src/tools/VideoPlayer/types/index.ts +0 -38
  321. package/src/tools/VideoPlayer/types/player.ts +0 -116
  322. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  323. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  324. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  325. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  326. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  327. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
  328. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  329. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  330. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  331. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  332. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  333. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  334. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  335. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  336. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -1,305 +0,0 @@
1
- 'use client';
2
-
3
- import { type ReactNode, useMemo, useRef, useState } from 'react';
4
-
5
- import { cn } from '@djangocfg/ui-core/lib';
6
-
7
- import type { ChatAttachment, ChatConfig, ChatMessage, ChatTransport } from '../types';
8
- import type { ChatAudioConfig } from '../core/audio/types';
9
- import {
10
- ChatProvider,
11
- useChatContext,
12
- useChatContextOptional,
13
- type ChatContextValue,
14
- } from '../context';
15
- import { useAutoFocusOnStreamEnd } from '../hooks/useAutoFocusOnStreamEnd';
16
- import { useChatComposer, type UseChatComposerReturn } from '../hooks/useChatComposer';
17
- import { useFocusOnEmptyClick } from '../hooks/useFocusOnEmptyClick';
18
- import { Composer, type ComposerSize } from './Composer';
19
- import { EmptyState } from './EmptyState';
20
- import { ErrorBanner } from './ErrorBanner';
21
- import { JumpToLatest } from './JumpToLatest';
22
- import { MessageBubble } from './MessageBubble';
23
- import { MessageList, type MessageListHandle } from './MessageList';
24
- import type { AttachmentRendererMap } from './Attachments';
25
- import type { ToolCallsProps } from './ToolCalls';
26
-
27
- export interface ChatRootProps {
28
- // ---- core wiring -------------------------------------------------------
29
- /**
30
- * Transport. Required UNLESS `<ChatRoot>` is rendered inside an
31
- * existing `<ChatProvider>` (e.g. mounted by `<ChatLauncher>`), in
32
- * which case the ambient provider is reused and `transport` is
33
- * ignored.
34
- */
35
- transport?: ChatTransport;
36
- config?: ChatConfig;
37
- initialSessionId?: string;
38
- autoCreateSession?: boolean;
39
- streaming?: boolean;
40
- /** Audio-trigger configuration. Off by default (no `sounds` map). */
41
- audio?: ChatAudioConfig;
42
- /**
43
- * Verbose dev-mode logging via `consola` (namespace `chat:*`).
44
- * Defaults to `isDev` from `@djangocfg/ui-core/lib`. Pass `false` to silence
45
- * even in development, or `true` to force on in production for debugging.
46
- */
47
- debug?: boolean;
48
- className?: string;
49
-
50
- // ---- named ReactNode slots --------------------------------------------
51
- /** Sticky banner above the message list (e.g. quota warning). */
52
- banner?: ReactNode;
53
- /** Header row below the banner — title / actions / session switcher. */
54
- header?: ReactNode;
55
- /** Footer slot below the composer (disclaimers, model picker). */
56
- footer?: ReactNode;
57
- /** Replaces the default `<EmptyState>` rendered when the conversation is empty. */
58
- empty?: ReactNode;
59
- /** Slot left of the textarea inside `<Composer>`. */
60
- composerToolbarStart?: ReactNode;
61
- /** Slot right of the textarea inside `<Composer>`. */
62
- composerToolbarEnd?: ReactNode;
63
- /** Replaces the default attachment tray inside `<Composer>`. */
64
- composerAttachmentTray?: ReactNode;
65
- /** Replaces the default `<JumpToLatest>` floating pill. */
66
- jumpToLatest?: ReactNode;
67
-
68
- // ---- render-prop slots (need access to data) --------------------------
69
- /** Replace `<MessageBubble>` per message. */
70
- renderMessage?: (m: ChatMessage, i: number) => ReactNode;
71
- /**
72
- * Render arbitrary content beneath every default `<MessageBubble>`
73
- * (not invoked when `renderMessage` is set — the host owns layout
74
- * in that case). Useful for product widgets driven by a side channel
75
- * (e.g. `ui_payload` SSE frames) where the widget is per-message but
76
- * not tied to the bubble's tool-calls array.
77
- */
78
- renderAfterMessage?: (m: ChatMessage) => ReactNode;
79
- /** Render the header lazily — receives the chat context. */
80
- renderHeader?: (ctx: ChatContextValue) => ReactNode;
81
- /** Render the empty-state lazily — receives a `setValue` to seed the composer. */
82
- renderEmpty?: (api: { setValue: (v: string) => void; focus: () => void }) => ReactNode;
83
- /**
84
- * Replace the default `<Composer>` entirely. Receives the live
85
- * `useChatComposer` return and the composer-related slot props
86
- * (placeholder, size, attach button, toolbar slots) — host can wire
87
- * them into a custom widget (e.g. a MarkdownEditor with mentions).
88
- *
89
- * When set, `composerToolbarStart/End`, `composerAttachmentTray`,
90
- * `composerSize`, `showAttachmentButton`, `onPickFiles` are passed
91
- * to the render-prop but are no longer auto-rendered.
92
- */
93
- renderComposer?: (api: {
94
- composer: UseChatComposerReturn;
95
- placeholder?: string;
96
- size?: ComposerSize;
97
- showAttachmentButton?: boolean;
98
- onPickFiles?: () => void;
99
- toolbarStart?: ReactNode;
100
- toolbarEnd?: ReactNode;
101
- attachmentTray?: ReactNode;
102
- }) => ReactNode;
103
- /** Forwarded into `<MessageBubble toolCallsProps>` so hosts can swap payload renderers. */
104
- toolCallsProps?: Omit<ToolCallsProps, 'calls'>;
105
- /** Per-type attachment renderers — `{ image, audio, video, file, default }`. */
106
- attachmentRenderers?: AttachmentRendererMap;
107
- /** Called when an attachment tile is clicked (e.g. open lightbox). */
108
- onAttachmentOpen?: (attachment: ChatAttachment) => void;
109
-
110
- // ---- composer customization -------------------------------------------
111
- /** Show the paperclip "attach" button in the composer. */
112
- showAttachmentButton?: boolean;
113
- /** Called when the user clicks the attach button (host opens its file picker). */
114
- onPickFiles?: () => void;
115
- /** Hide the composer input area entirely (e.g. while waiting for human approval). */
116
- hideComposer?: boolean;
117
- /** Composer size variant. Default ``md`` (36px slot). Use ``lg`` for primary
118
- * surfaces (onboarding, full-page chat), ``sm`` for dense sidebars. */
119
- composerSize?: ComposerSize;
120
- /** Extra className forwarded to the `<MessageList>` scroll container.
121
- * Use to add padding inside the scroll area so the scrollbar stays flush
122
- * with the edge (e.g. `"px-6 pt-6 sm:px-8"`). */
123
- listClassName?: string;
124
- /** Extra className forwarded to the `<Composer>` wrapper div. */
125
- composerClassName?: string;
126
- /**
127
- * Click in the message area → focus the composer (Slack / ChatGPT / Linear).
128
- * Honours selection drag, interactive elements, and touch input.
129
- * @default true
130
- */
131
- focusOnEmptyClick?: boolean;
132
- }
133
-
134
- export function ChatRoot(props: ChatRootProps) {
135
- const { transport, config, initialSessionId, autoCreateSession, streaming, audio, debug, className, listClassName, ...slots } = props;
136
- // When mounted under a launcher-owned `<ChatProvider>`, reuse that
137
- // provider instead of wrapping in a second one. This lets host code
138
- // freely nest `<ChatRoot>` inside `<ChatLauncher>` without losing
139
- // `headerSlots` access to the same session / clearMessages / audio.
140
- const ambient = useChatContextOptional();
141
- if (ambient) {
142
- return <ChatRootShell className={className} listClassName={listClassName} slots={slots} />;
143
- }
144
- if (!transport) {
145
- throw new Error(
146
- '<ChatRoot> requires `transport` when mounted outside a <ChatProvider>.',
147
- );
148
- }
149
- return (
150
- <ChatProvider
151
- transport={transport}
152
- config={config}
153
- initialSessionId={initialSessionId}
154
- autoCreateSession={autoCreateSession}
155
- streaming={streaming}
156
- audio={audio}
157
- debug={debug}
158
- >
159
- <ChatRootShell className={className} listClassName={listClassName} slots={slots} />
160
- </ChatProvider>
161
- );
162
- }
163
-
164
- interface ChatRootShellProps {
165
- className?: string;
166
- listClassName?: string;
167
- slots: Omit<ChatRootProps, 'transport' | 'config' | 'initialSessionId' | 'autoCreateSession' | 'streaming' | 'audio' | 'debug' | 'className' | 'listClassName'>;
168
- }
169
-
170
- function ChatRootShell({ className, listClassName, slots }: ChatRootShellProps) {
171
- const chat = useChatContext();
172
- const composer = useChatComposer({
173
- onSubmit: (content, attachments) => chat.sendMessage(content, attachments),
174
- disabled: chat.isStreaming,
175
- });
176
- const onMessagesMouseUp = useFocusOnEmptyClick({
177
- enabled: slots.focusOnEmptyClick !== false,
178
- });
179
- // Re-focus the composer on the streaming → idle edge. Was dropped in
180
- // a previous refactor — restored here so the standard chat UX (type
181
- // → send → read → keep typing without clicking) works again.
182
- useAutoFocusOnStreamEnd();
183
- // MessageList (virtuoso) owns the scroll viewport. We talk to it
184
- // via the imperative handle (scrollToBottom on JumpToLatest click)
185
- // and via the `onAtBottomChange` callback (drives the pill).
186
- const listRef = useRef<MessageListHandle | null>(null);
187
- const [isAtBottom, setIsAtBottom] = useState(true);
188
- // The id of the most-recent user-sent message. We pass it as
189
- // `scrollAnchorId` to MessageList so every send re-anchors the
190
- // viewport — fixes "I sent a message but the chat didn't scroll
191
- // down". Recomputed lazily; the resulting id only flips when a new
192
- // user message lands.
193
- const lastUserMessageId = useMemo(() => {
194
- const msgs = chat.messages;
195
- for (let i = msgs.length - 1; i >= 0; i -= 1) {
196
- if (msgs[i].role === 'user') return msgs[i].id;
197
- }
198
- return null;
199
- }, [chat.messages]);
200
- const handleStartReached = chat.hasMore && !chat.isLoadingMore
201
- ? () => void chat.loadMore()
202
- : undefined;
203
-
204
- const greeting = chat.config.greeting ?? 'How can I help?';
205
- const description = chat.config.description;
206
- const suggestions = chat.config.suggestions;
207
-
208
- const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;
209
-
210
- const emptyNode = slots.empty
211
- ?? (slots.renderEmpty
212
- ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus })
213
- : (
214
- <EmptyState
215
- greeting={greeting}
216
- description={description}
217
- suggestions={suggestions}
218
- onPickSuggestion={(prompt) => {
219
- composer.setValue(prompt);
220
- composer.focus();
221
- }}
222
- />
223
- ));
224
-
225
- const renderItem = slots.renderMessage
226
- ?? ((m: ChatMessage) => (
227
- <MessageBubble
228
- key={m.id}
229
- message={m}
230
- toolCallsProps={slots.toolCallsProps}
231
- attachmentRenderers={slots.attachmentRenderers}
232
- onAttachmentOpen={slots.onAttachmentOpen}
233
- renderAfterMessage={slots.renderAfterMessage}
234
- onCopy={() => copy(m.content)}
235
- onRegenerate={() => void chat.regenerate(m.id)}
236
- onDelete={() => chat.deleteMessage(m.id)}
237
- />
238
- ));
239
-
240
- return (
241
- <div className={cn('relative flex h-full min-h-0 flex-col overflow-hidden', className)}>
242
- {slots.banner ?? null}
243
- {headerNode ?? null}
244
- <div className="relative flex min-h-0 flex-1 flex-col" onMouseUp={onMessagesMouseUp}>
245
- <ErrorBanner
246
- error={chat.error}
247
- onDismiss={chat.error ? () => chat.clearMessages() : undefined}
248
- onRetry={chat.error ? () => void chat.regenerate() : undefined}
249
- />
250
- <MessageList
251
- ref={listRef}
252
- renderItem={renderItem}
253
- renderEmpty={() => <>{emptyNode}</>}
254
- className={listClassName}
255
- onStartReached={handleStartReached}
256
- onAtBottomChange={setIsAtBottom}
257
- scrollAnchorId={lastUserMessageId}
258
- />
259
- <div className="pointer-events-none absolute inset-x-0 bottom-2 flex justify-center">
260
- {slots.jumpToLatest ?? (
261
- <JumpToLatest
262
- visible={!isAtBottom}
263
- onClick={() => listRef.current?.scrollToBottom(true)}
264
- />
265
- )}
266
- </div>
267
- </div>
268
- {!slots.hideComposer && (
269
- slots.renderComposer
270
- ? slots.renderComposer({
271
- composer,
272
- placeholder: chat.config.placeholder,
273
- size: slots.composerSize,
274
- showAttachmentButton: slots.showAttachmentButton,
275
- onPickFiles: slots.onPickFiles,
276
- toolbarStart: slots.composerToolbarStart,
277
- toolbarEnd: slots.composerToolbarEnd,
278
- attachmentTray: slots.composerAttachmentTray,
279
- })
280
- : (
281
- <Composer
282
- composer={composer}
283
- placeholder={chat.config.placeholder}
284
- showAttachmentButton={slots.showAttachmentButton}
285
- onPickFiles={slots.onPickFiles}
286
- toolbarStart={slots.composerToolbarStart}
287
- toolbarEnd={slots.composerToolbarEnd}
288
- attachmentTray={slots.composerAttachmentTray}
289
- size={slots.composerSize}
290
- />
291
- )
292
- )}
293
- {slots.footer ?? null}
294
- </div>
295
- );
296
- }
297
-
298
- function copy(text: string) {
299
- if (typeof navigator !== 'undefined' && navigator.clipboard) {
300
- void navigator.clipboard.writeText(text);
301
- }
302
- }
303
-
304
- // re-export for convenience: composer hook return is a common slot dependency
305
- export type { UseChatComposerReturn };
@@ -1,216 +0,0 @@
1
- 'use client';
2
-
3
- import { type ReactNode, forwardRef, useEffect, useRef } from 'react';
4
- import { Paperclip, Send, Square } from 'lucide-react';
5
-
6
- import { Button, Textarea } from '@djangocfg/ui-core/components';
7
- import { cn } from '@djangocfg/ui-core/lib';
8
-
9
- import { useChatContextOptional } from '../context';
10
- import type { UseChatComposerReturn } from '../hooks/useChatComposer';
11
- import { Attachments } from './Attachments';
12
-
13
- export type ComposerSize = 'sm' | 'md' | 'lg';
14
-
15
- export interface ComposerProps {
16
- composer: UseChatComposerReturn;
17
- placeholder?: string;
18
- disabled?: boolean;
19
- showAttachmentButton?: boolean;
20
- onPickFiles?: () => void;
21
- toolbarStart?: ReactNode;
22
- toolbarEnd?: ReactNode;
23
- attachmentTray?: ReactNode;
24
- className?: string;
25
- textareaClassName?: string;
26
- /** Visual size — controls textarea height + button slot size.
27
- *
28
- * - ``sm`` — 32px slot, dense compact composer (admin sidebars, etc).
29
- * - ``md`` — 36px slot, default. Same as the legacy fixed size.
30
- * - ``lg`` — 48px slot, generous textarea. Use when the chat is
31
- * the page's primary surface (onboarding, support).
32
- */
33
- size?: ComposerSize;
34
- /** Show "Stop" button instead of "Send" while streaming. */
35
- isStreaming?: boolean;
36
- onCancel?: () => void;
37
- }
38
-
39
- const SIZE_CLASSES: Record<ComposerSize, {
40
- slot: string;
41
- button: string;
42
- iconButton: string;
43
- textarea: string;
44
- text: string;
45
- padding: string;
46
- containerPadding: string;
47
- }> = {
48
- sm: {
49
- slot: '[&>:not(textarea)]:h-8',
50
- button: 'h-8 w-8',
51
- iconButton: 'size-3.5',
52
- textarea: 'min-h-8 max-h-48 px-3 py-1.5',
53
- text: 'text-sm',
54
- padding: 'gap-1.5',
55
- containerPadding: 'px-2 pt-1.5 pb-[max(0.375rem,env(safe-area-inset-bottom))]',
56
- },
57
- md: {
58
- slot: '[&>:not(textarea)]:h-9',
59
- button: 'h-9 w-9',
60
- iconButton: 'size-4',
61
- textarea: 'min-h-9 max-h-60 px-3.5 py-2',
62
- text: 'text-base sm:text-sm',
63
- padding: 'gap-1.5',
64
- containerPadding: 'px-2.5 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]',
65
- },
66
- lg: {
67
- slot: '[&>:not(textarea)]:h-12',
68
- button: 'h-12 w-12',
69
- iconButton: 'size-5',
70
- textarea: 'min-h-12 max-h-72 px-4 py-3',
71
- text: 'text-base',
72
- padding: 'gap-2',
73
- containerPadding: 'px-3.5 pt-3 pb-[max(0.875rem,env(safe-area-inset-bottom))]',
74
- },
75
- };
76
-
77
- export const Composer = forwardRef<HTMLDivElement, ComposerProps>(function Composer(
78
- {
79
- composer,
80
- placeholder = 'Type a message...',
81
- disabled,
82
- showAttachmentButton = false,
83
- onPickFiles,
84
- toolbarStart,
85
- toolbarEnd,
86
- attachmentTray,
87
- className,
88
- textareaClassName,
89
- size = 'md',
90
- isStreaming: isStreamingProp,
91
- onCancel: onCancelProp,
92
- },
93
- ref,
94
- ) {
95
- const ctx = useChatContextOptional();
96
- const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
97
- const onCancel = onCancelProp ?? ctx?.cancelStream;
98
- const isDisabled = disabled ?? isStreaming;
99
- const sz = SIZE_CLASSES[size];
100
-
101
- // Register the composer's focus() with the chat context so other
102
- // parts of the tree (e.g. useAutoFocusOnStreamEnd) can drive it
103
- // imperatively without prop-drilling a ref. No-op when used outside
104
- // a ChatProvider.
105
- const register = ctx?.registerComposer;
106
- const composerFocus = composer.focus;
107
- const composerSetValue = composer.setValue;
108
- const textareaRef = composer.textareaRef;
109
- // `getValue` reads the live ref instead of closing over `composer.value`
110
- // so we don't need to re-register on every keystroke. Same trick for
111
- // `setValue` — handler identity stays stable across renders.
112
- const getValueRef = useRef<() => string>(() => composer.value);
113
- getValueRef.current = () => composer.value;
114
- useEffect(() => {
115
- if (!register) return;
116
- register({
117
- focus: composerFocus,
118
- moveCursorToEnd: () => {
119
- const el = textareaRef.current;
120
- if (!el) return;
121
- const end = el.value.length;
122
- el.setSelectionRange(end, end);
123
- },
124
- getValue: () => getValueRef.current(),
125
- setValue: composerSetValue,
126
- });
127
- return () => register(null);
128
- }, [register, composerFocus, composerSetValue, textareaRef]);
129
-
130
- return (
131
- <div
132
- ref={ref}
133
- className={cn(
134
- 'border-t border-border bg-background/95',
135
- sz.containerPadding,
136
- className,
137
- )}
138
- >
139
- {composer.attachments.length > 0 ? (
140
- <div className="mb-1.5">
141
- {attachmentTray ?? (
142
- <Attachments
143
- attachments={composer.attachments}
144
- onRemove={(a) => composer.removeAttachment(a.id)}
145
- />
146
- )}
147
- </div>
148
- ) : null}
149
-
150
- {/* Size-aware slot row. ``[&>:not(textarea)]:h-{N}`` enforces a
151
- * consistent slot height so toolbar buttons line up with the
152
- * textarea baseline. Toolbar slots that want to opt out can
153
- * pass an explicit class like `!h-auto`. */}
154
- <div className={cn('flex items-end [&>:not(textarea)]:shrink-0', sz.padding, sz.slot)}>
155
- {showAttachmentButton ? (
156
- <Button
157
- type="button"
158
- variant="ghost"
159
- size="icon"
160
- onClick={onPickFiles}
161
- aria-label="Attach files"
162
- disabled={isDisabled}
163
- className={sz.button}
164
- >
165
- <Paperclip aria-hidden className={sz.iconButton} />
166
- </Button>
167
- ) : null}
168
-
169
- {toolbarStart}
170
-
171
- <Textarea
172
- {...composer.textareaProps}
173
- rows={1}
174
- placeholder={placeholder}
175
- aria-label={placeholder}
176
- aria-multiline="true"
177
- disabled={isDisabled}
178
- className={cn(
179
- 'flex-1 resize-none rounded-2xl',
180
- sz.textarea,
181
- sz.text,
182
- textareaClassName,
183
- )}
184
- />
185
-
186
- {toolbarEnd}
187
-
188
- {isStreaming ? (
189
- <Button
190
- type="button"
191
- variant="secondary"
192
- size="icon"
193
- onClick={onCancel}
194
- aria-label="Stop"
195
- aria-keyshortcuts="Escape"
196
- className={sz.button}
197
- >
198
- <Square aria-hidden className={sz.iconButton} />
199
- </Button>
200
- ) : (
201
- <Button
202
- type="button"
203
- size="icon"
204
- onClick={() => void composer.submit()}
205
- disabled={!composer.canSubmit}
206
- aria-label="Send"
207
- aria-keyshortcuts="Enter"
208
- className={sz.button}
209
- >
210
- <Send aria-hidden className={sz.iconButton} />
211
- </Button>
212
- )}
213
- </div>
214
- </div>
215
- );
216
- });
@@ -1,145 +0,0 @@
1
- 'use client';
2
-
3
- import { type RefObject, useCallback, useEffect, useRef, useState } from 'react';
4
-
5
- /**
6
- * @deprecated Plan64. As of ui-tools 2.1.369, `<MessageList>` is
7
- * virtualized via react-virtuoso and owns its own scroll viewport.
8
- * Sticky-bottom + auto-follow on streaming live inside the component;
9
- * use the new `MessageListProps.onAtBottomChange` prop and the
10
- * `MessageListHandle.scrollToBottom()` imperative method instead.
11
- *
12
- * This hook is kept for hosts that render messages outside
13
- * `<MessageList>` (e.g. headless story shells, custom non-virtualized
14
- * scroll containers). It still works against any HTMLElement scroll
15
- * container, but it does NOT integrate with Virtuoso — passing a
16
- * Virtuoso-managed element here will read garbage scroll metrics.
17
- */
18
- export interface UseChatScrollOptions {
19
- containerRef: RefObject<HTMLElement | null>;
20
- bottomRef: RefObject<HTMLElement | null>;
21
- isStreaming?: boolean;
22
- /** Distance from bottom (px) considered "at bottom". */
23
- bottomThresholdPx?: number;
24
- /** Bump key — increment when a new message arrives so the hook re-evaluates auto-scroll. */
25
- messagesCount?: number;
26
- }
27
-
28
- export interface UseChatScrollReturn {
29
- isAtBottom: boolean;
30
- unreadCount: number;
31
- scrollToBottom: (smooth?: boolean) => void;
32
- resetUnread: () => void;
33
- }
34
-
35
- export function useChatScroll(options: UseChatScrollOptions): UseChatScrollReturn {
36
- const {
37
- containerRef,
38
- bottomRef,
39
- isStreaming = false,
40
- bottomThresholdPx = 80,
41
- messagesCount = 0,
42
- } = options;
43
-
44
- const [isAtBottom, setIsAtBottom] = useState(true);
45
- const [unreadCount, setUnreadCount] = useState(0);
46
- const lastCountRef = useRef(messagesCount);
47
- const stickyRef = useRef(true);
48
- const wasStreamingRef = useRef(isStreaming);
49
-
50
- const scrollToBottom = useCallback(
51
- (smooth = false) => {
52
- const el = containerRef.current;
53
- if (!el) return;
54
- el.scrollTo({
55
- top: el.scrollHeight,
56
- behavior: smooth ? 'smooth' : 'auto',
57
- });
58
- stickyRef.current = true;
59
- setIsAtBottom(true);
60
- setUnreadCount(0);
61
- },
62
- [containerRef],
63
- );
64
-
65
- const resetUnread = useCallback(() => setUnreadCount(0), []);
66
-
67
- // Track scroll position relative to bottom.
68
- useEffect(() => {
69
- const el = containerRef.current;
70
- if (!el) return;
71
- const onScroll = () => {
72
- const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
73
- const atBottom = distance <= bottomThresholdPx;
74
- stickyRef.current = atBottom;
75
- setIsAtBottom(atBottom);
76
- if (atBottom) setUnreadCount(0);
77
- };
78
- onScroll();
79
- el.addEventListener('scroll', onScroll, { passive: true });
80
- return () => {
81
- el.removeEventListener('scroll', onScroll);
82
- };
83
- }, [containerRef, bottomThresholdPx]);
84
-
85
- // Stick to bottom while streaming, and one extra rAF after stream ends so
86
- // the final layout (markdown re-render, sources/tool-call panels) doesn't
87
- // push the latest content out of view.
88
- useEffect(() => {
89
- const el = containerRef.current;
90
- if (!el) return;
91
-
92
- if (isStreaming) {
93
- wasStreamingRef.current = true;
94
- if (!stickyRef.current) return;
95
- let raf = 0;
96
- const tick = () => {
97
- if (!stickyRef.current) return;
98
- el.scrollTop = el.scrollHeight;
99
- raf = requestAnimationFrame(tick);
100
- };
101
- raf = requestAnimationFrame(tick);
102
- return () => cancelAnimationFrame(raf);
103
- }
104
-
105
- // Stream just ended — flush one more scroll on the next two frames so
106
- // both markdown swap and any post-stream panel insertions are reflected.
107
- if (wasStreamingRef.current && stickyRef.current) {
108
- wasStreamingRef.current = false;
109
- let raf1 = 0;
110
- let raf2 = 0;
111
- raf1 = requestAnimationFrame(() => {
112
- el.scrollTop = el.scrollHeight;
113
- raf2 = requestAnimationFrame(() => {
114
- el.scrollTop = el.scrollHeight;
115
- });
116
- });
117
- return () => {
118
- cancelAnimationFrame(raf1);
119
- cancelAnimationFrame(raf2);
120
- };
121
- }
122
- wasStreamingRef.current = false;
123
- return;
124
- }, [containerRef, isStreaming]);
125
-
126
- // On message count increase, decide whether to scroll or bump unread.
127
- useEffect(() => {
128
- if (messagesCount > lastCountRef.current) {
129
- if (stickyRef.current) {
130
- const el = containerRef.current;
131
- if (el) el.scrollTop = el.scrollHeight;
132
- } else {
133
- setUnreadCount((n) => n + (messagesCount - lastCountRef.current));
134
- }
135
- }
136
- lastCountRef.current = messagesCount;
137
- }, [containerRef, messagesCount]);
138
-
139
- // Watch bottom sentinel just for symmetry/future hooks.
140
- useEffect(() => {
141
- void bottomRef;
142
- }, [bottomRef]);
143
-
144
- return { isAtBottom, unreadCount, scrollToBottom, resetUnread };
145
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Backwards-compatibility re-export.
3
- *
4
- * Types live in `./types/` (one file per domain). Import directly from
5
- * `@djangocfg/ui-tools/Chat` in new code; this barrel exists so old
6
- * `import … from '.../types'` paths keep working.
7
- */
8
-
9
- export * from './types/index';