@djangocfg/ui-tools 2.1.407 → 2.1.409

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 (297) hide show
  1. package/README.md +9 -10
  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 +8 -13
  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/parts/Meta/TimeDisplay.tsx +2 -5
  67. package/src/tools/Chat/README.md +277 -39
  68. package/src/tools/Chat/composer/Composer.tsx +471 -0
  69. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  70. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  71. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  72. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  73. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  74. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  75. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  76. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  77. package/src/tools/Chat/composer/index.ts +45 -0
  78. package/src/tools/Chat/composer/size-context.tsx +26 -0
  79. package/src/tools/Chat/composer/types.ts +143 -0
  80. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  81. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  82. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  83. package/src/tools/Chat/core/index.ts +23 -1
  84. package/src/tools/Chat/core/markdown.ts +1 -1
  85. package/src/tools/Chat/core/metadata.ts +47 -0
  86. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  87. package/src/tools/Chat/core/transport/http.ts +71 -32
  88. package/src/tools/Chat/core/transport/sse.ts +18 -10
  89. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  90. package/src/tools/Chat/highlight/README.md +103 -0
  91. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  92. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  93. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  94. package/src/tools/Chat/highlight/index.ts +21 -0
  95. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  96. package/src/tools/Chat/highlight/types.ts +49 -0
  97. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  98. package/src/tools/Chat/hooks/index.ts +0 -5
  99. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  100. package/src/tools/Chat/hooks/useChat.ts +47 -14
  101. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  102. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  103. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  104. package/src/tools/Chat/index.ts +25 -219
  105. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  106. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  107. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  108. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  109. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  110. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  111. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  112. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  113. package/src/tools/Chat/launcher/header/index.ts +26 -0
  114. package/src/tools/Chat/launcher/index.ts +3 -10
  115. package/src/tools/Chat/lazy.tsx +38 -284
  116. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  117. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  118. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  119. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  120. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  121. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  122. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  123. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  124. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  125. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  126. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  127. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  128. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  130. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  131. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  132. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  133. package/src/tools/Chat/public.ts +212 -0
  134. package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
  135. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  136. package/src/tools/Chat/shell/index.ts +15 -0
  137. package/src/tools/Chat/types/block.ts +120 -0
  138. package/src/tools/Chat/types/config.ts +0 -5
  139. package/src/tools/Chat/types/index.ts +17 -0
  140. package/src/tools/Chat/types/message.ts +3 -0
  141. package/src/tools/Chat/utils/index.ts +4 -0
  142. package/src/tools/CodeEditor/README.md +4 -6
  143. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  144. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  145. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  146. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  147. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  148. package/src/tools/CodeEditor/lazy.tsx +6 -0
  149. package/src/tools/CodeEditor/lib/index.ts +1 -1
  150. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  151. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  152. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  153. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  154. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  155. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  156. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  157. package/src/tools/CronScheduler/index.tsx +1 -1
  158. package/src/tools/CronScheduler/types/index.ts +8 -3
  159. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  160. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  161. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  162. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  163. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  164. package/src/tools/FileIcon/index.ts +4 -0
  165. package/src/tools/FileIcon/loader.ts +17 -35
  166. package/src/tools/FileIcon/specialFolders.ts +18 -0
  167. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  168. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  169. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  170. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  171. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  172. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  173. package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
  174. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  175. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  176. package/src/tools/ImageViewer/utils/index.ts +1 -0
  177. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  178. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  179. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  180. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  181. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  182. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  183. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  184. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  185. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  186. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  187. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  188. package/src/tools/JsonForm/widgets/index.ts +1 -0
  189. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  190. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  191. package/src/tools/LottiePlayer/index.tsx +14 -4
  192. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  193. package/src/tools/LottiePlayer/types.ts +31 -1
  194. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  195. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  196. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  197. package/src/tools/Map/components/MapCluster.tsx +28 -21
  198. package/src/tools/Map/components/MapContainer.tsx +11 -4
  199. package/src/tools/Map/components/MapLegend.tsx +46 -15
  200. package/src/tools/Map/components/MapMarker.tsx +31 -2
  201. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  202. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  203. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  204. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  205. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  206. package/src/tools/MarkdownEditor/styles.css +13 -0
  207. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  208. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  209. package/src/tools/MarkdownMessage/components.tsx +22 -9
  210. package/src/tools/MarkdownMessage/types.ts +24 -1
  211. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  212. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  213. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  214. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  215. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  216. package/src/tools/Mermaid/index.tsx +6 -0
  217. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  218. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  221. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  222. package/src/tools/OpenapiViewer/constants.ts +3 -0
  223. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  224. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  225. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  226. package/src/tools/PrettyCode/lazy.tsx +1 -1
  227. package/src/tools/SpeechRecognition/README.md +1 -1
  228. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  229. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  230. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  231. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  232. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  233. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  234. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  235. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  236. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  237. package/src/tools/Tree/README.md +4 -8
  238. package/src/tools/Tree/TreeRoot.tsx +22 -10
  239. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  240. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  241. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  242. package/src/tools/Tree/data/flatten.ts +10 -4
  243. package/src/tools/Tree/types.ts +4 -0
  244. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  245. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  246. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  247. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  248. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  249. package/src/tools/Uploader/utils/formatters.ts +8 -3
  250. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
  251. package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
  252. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
  253. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
  254. package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
  255. package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
  256. package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
  257. package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
  258. package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
  259. package/src/tools/index.ts +2 -1
  260. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  261. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  262. package/src/tools/Chat/components/Composer.tsx +0 -216
  263. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  264. package/src/tools/Chat/types.ts +0 -9
  265. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  266. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  267. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  268. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  269. package/src/tools/Tour/README.md +0 -373
  270. package/src/tools/Tour/components/Tour.tsx +0 -12
  271. package/src/tools/Tour/components/TourContent.tsx +0 -171
  272. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  273. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  274. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  275. package/src/tools/Tour/components/index.ts +0 -5
  276. package/src/tools/Tour/context/TourContext.ts +0 -19
  277. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  278. package/src/tools/Tour/context/index.ts +0 -2
  279. package/src/tools/Tour/hooks/index.ts +0 -3
  280. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  281. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  282. package/src/tools/Tour/hooks/useTour.ts +0 -42
  283. package/src/tools/Tour/index.ts +0 -38
  284. package/src/tools/Tour/types/index.ts +0 -224
  285. package/src/tools/Tour/utils/dom.ts +0 -98
  286. package/src/tools/Tour/utils/index.ts +0 -3
  287. package/src/tools/Tour/utils/logger.ts +0 -3
  288. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  289. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  290. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  291. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  292. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  293. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  294. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  295. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  296. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  297. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -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';
@@ -1,95 +0,0 @@
1
- 'use client';
2
-
3
- import { ChevronDown, ChevronUp, Download } from 'lucide-react';
4
- import React, { memo } from 'react';
5
-
6
- import { Button, CopyButton } from '@djangocfg/ui-core/components';
7
-
8
- interface JsonToolbarProps {
9
- /** Viewport right coordinate (distance from right edge) for fixed positioning */
10
- right: number;
11
- /** Bottom edge of the container in viewport coords */
12
- bottom: number;
13
- /** Top edge — used only to detect if block is fully off-screen */
14
- top: number;
15
- isExpanded: boolean;
16
- onToggleExpand: () => void;
17
- jsonString: string;
18
- onDownload: () => void;
19
- showExpandControls: boolean;
20
- showActionButtons: boolean;
21
- }
22
-
23
- const BUTTON_CLASS = 'h-6 w-6 rounded-sm bg-muted/80 hover:bg-muted border border-border/50 backdrop-blur-sm';
24
- const TOOLBAR_H = 24 + 8 + 8;
25
- const OFFSET = 8;
26
-
27
- /**
28
- * JsonToolbar — floating toolbar for JsonTree.
29
- *
30
- * Memoised: re-renders only when any prop changes. Position props
31
- * (`top`, `right`, `bottom`) are primitives, so default shallow
32
- * comparison is sufficient.
33
- */
34
- const JsonToolbar = memo(({
35
- top,
36
- right,
37
- bottom,
38
- isExpanded,
39
- onToggleExpand,
40
- jsonString,
41
- onDownload,
42
- showExpandControls,
43
- showActionButtons,
44
- }: JsonToolbarProps) => {
45
- const viewportHeight = window.visualViewport?.height ?? document.documentElement.clientHeight;
46
-
47
- // Hide when block is fully above or below viewport
48
- if (bottom <= 0 || top >= viewportHeight) return null;
49
- // Hide when block is too small to fit toolbar
50
- if (bottom - top < TOOLBAR_H) return null;
51
-
52
- // Anchor to bottom-right of block, clamped so toolbar stays in viewport
53
- const toolbarBottom = Math.min(bottom - OFFSET, viewportHeight - OFFSET);
54
-
55
- return (
56
- <div
57
- className="flex items-center gap-1"
58
- style={{ position: 'fixed', bottom: viewportHeight - toolbarBottom, right: right + OFFSET, zIndex: 30 }}
59
- >
60
- {showExpandControls && (
61
- <Button
62
- variant="ghost"
63
- size="icon"
64
- onClick={onToggleExpand}
65
- className={BUTTON_CLASS}
66
- title={isExpanded ? 'Collapse All' : 'Expand All'}
67
- >
68
- {isExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
69
- </Button>
70
- )}
71
-
72
- {showActionButtons && (
73
- <>
74
- <CopyButton
75
- value={jsonString}
76
- variant="ghost"
77
- size="icon"
78
- className={BUTTON_CLASS}
79
- iconClassName="h-3 w-3"
80
- title="Copy JSON"
81
- />
82
- <Button
83
- variant="ghost"
84
- size="icon"
85
- onClick={onDownload}
86
- className={BUTTON_CLASS}
87
- title="Download JSON"
88
- >
89
- <Download className="h-3 w-3" />
90
- </Button>
91
- </>
92
- )}
93
- </div>
94
- );
95
- });
@@ -1,84 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useRef, useState } from 'react';
4
-
5
- interface Corner {
6
- top: number;
7
- right: number;
8
- bottom: number;
9
- }
10
-
11
- /**
12
- * Tracks the top-right corner of a referenced element in viewport coordinates.
13
- * Returns { top, right } in px — ready for `position: fixed` toolbar placement.
14
- *
15
- * Uses `visualViewport` for accurate viewport width — correctly handles:
16
- * - DevTools panel open (docked left/right/bottom)
17
- * - Browser zoom
18
- * - Mobile virtual keyboard / pinch-zoom
19
- * - Scrollbars
20
- *
21
- * Falls back to `document.documentElement.clientWidth` (excludes scrollbars,
22
- * matches `position: fixed` coordinate space) when visualViewport is unavailable.
23
- *
24
- * Updates on:
25
- * - Any ancestor scroll (capture phase)
26
- * - visualViewport resize/scroll
27
- * - ResizeObserver on the element itself
28
- */
29
- export function useElementCorner(ref: React.RefObject<HTMLElement | null>) {
30
- const [corner, setCorner] = useState<Corner | null>(null);
31
- const updateRef = useRef<() => void>(() => {});
32
-
33
- updateRef.current = () => {
34
- if (!ref.current) return;
35
- const rect = ref.current.getBoundingClientRect();
36
-
37
- // `position: fixed` is relative to the layout viewport (document.documentElement.clientWidth),
38
- // NOT window.innerWidth (which includes scrollbar gutter).
39
- // visualViewport.width is the visible area — same as layout viewport when no zoom/keyboard.
40
- const viewportWidth =
41
- window.visualViewport?.width ?? document.documentElement.clientWidth;
42
-
43
- setCorner({
44
- top: rect.top,
45
- right: viewportWidth - rect.right,
46
- bottom: rect.bottom,
47
- });
48
- };
49
-
50
- useEffect(() => {
51
- const handler = () => updateRef.current();
52
-
53
- handler();
54
-
55
- // Element size changes
56
- const ro = new ResizeObserver(handler);
57
- if (ref.current) ro.observe(ref.current);
58
-
59
- // Any ancestor scroll (capture catches all, including overflow:auto containers)
60
- window.addEventListener('scroll', handler, { capture: true, passive: true });
61
-
62
- // visualViewport handles: DevTools resize, browser zoom, mobile keyboard
63
- const vv = window.visualViewport;
64
- if (vv) {
65
- vv.addEventListener('resize', handler);
66
- vv.addEventListener('scroll', handler);
67
- } else {
68
- window.addEventListener('resize', handler, { passive: true });
69
- }
70
-
71
- return () => {
72
- ro.disconnect();
73
- window.removeEventListener('scroll', handler, { capture: true });
74
- if (vv) {
75
- vv.removeEventListener('resize', handler);
76
- vv.removeEventListener('scroll', handler);
77
- } else {
78
- window.removeEventListener('resize', handler);
79
- }
80
- };
81
- }, [ref]);
82
-
83
- return corner;
84
- }
@@ -1,83 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useRef, useState } from 'react';
4
-
5
- /**
6
- * Detects the height of any element covering the top of the viewport.
7
- *
8
- * Strategy: uses elementFromPoint() along the top of the viewport to find
9
- * whatever is actually rendered there — fixed, sticky, or in-flow elements.
10
- * Walks up the DOM from the hit element to find the topmost covering block.
11
- *
12
- * Performance:
13
- * - Samples a few x-points across the viewport top (not the whole width)
14
- * - Throttled with requestAnimationFrame
15
- * - Only re-runs on resize (navbars rarely change height on scroll)
16
- */
17
-
18
- const SAMPLE_POINTS = 5; // how many x positions to probe across viewport
19
-
20
- function measureNavbarHeight(): number {
21
- const vv = window.visualViewport;
22
- const viewportWidth = vv?.width ?? document.documentElement.clientWidth;
23
-
24
- let maxBottom = 0;
25
-
26
- for (let i = 0; i < SAMPLE_POINTS; i++) {
27
- const x = (viewportWidth / (SAMPLE_POINTS + 1)) * (i + 1);
28
-
29
- // Probe just below the very top — y=1 to avoid hitting browser chrome
30
- const el = document.elementFromPoint(x, 1);
31
- if (!el || el === document.documentElement || el === document.body) continue;
32
-
33
- const rect = el.getBoundingClientRect();
34
- // Only count elements that are anchored at the very top (top ≤ 2px)
35
- if (rect.top <= 2 && rect.bottom > maxBottom) {
36
- maxBottom = rect.bottom;
37
- }
38
- }
39
-
40
- return maxBottom;
41
- }
42
-
43
- export function useNavbarHeight(): number {
44
- const [height, setHeight] = useState(0);
45
- const rafRef = useRef<number | null>(null);
46
-
47
- const scheduleUpdate = () => {
48
- if (rafRef.current !== null) return;
49
- rafRef.current = requestAnimationFrame(() => {
50
- rafRef.current = null;
51
- setHeight(measureNavbarHeight());
52
- });
53
- };
54
-
55
- useEffect(() => {
56
- // Initial measure after first paint
57
- scheduleUpdate();
58
-
59
- // Navbars can change height on resize (mobile keyboard, orientation)
60
- const vv = window.visualViewport;
61
- if (vv) {
62
- vv.addEventListener('resize', scheduleUpdate);
63
- } else {
64
- window.addEventListener('resize', scheduleUpdate, { passive: true });
65
- }
66
-
67
- // Watch for navbar being added/removed from DOM
68
- const mo = new MutationObserver(scheduleUpdate);
69
- mo.observe(document.body, { childList: true, subtree: false });
70
-
71
- return () => {
72
- if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
73
- if (vv) {
74
- vv.removeEventListener('resize', scheduleUpdate);
75
- } else {
76
- window.removeEventListener('resize', scheduleUpdate);
77
- }
78
- mo.disconnect();
79
- };
80
- }, []);
81
-
82
- return height;
83
- }