@ctrl-ui/react 0.8.6 → 0.8.8

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 (376) hide show
  1. package/dist/action-bar.d.ts +15 -7
  2. package/dist/action-bar.js +39 -19
  3. package/dist/activity.js +7 -15
  4. package/dist/audio-recorder.d.ts +14 -4
  5. package/dist/audio-recorder.js +82 -29
  6. package/dist/audio-visualizer.js +1 -1
  7. package/dist/blocks/coding-agent.js +54 -57
  8. package/dist/blocks/design-canvas-data.d.ts +32 -0
  9. package/dist/blocks/design-canvas-data.js +100 -0
  10. package/dist/blocks/design-canvas-inspector.d.ts +8 -0
  11. package/dist/blocks/design-canvas-inspector.js +238 -0
  12. package/dist/blocks/design-canvas.d.ts +21 -0
  13. package/dist/blocks/design-canvas.js +452 -0
  14. package/dist/blocks/discussion.d.ts +28 -0
  15. package/dist/blocks/discussion.js +67 -0
  16. package/dist/blocks/file-explorer-data.d.ts +1 -0
  17. package/dist/blocks/file-explorer-data.js +4 -1
  18. package/dist/blocks/file-explorer.d.ts +9 -1
  19. package/dist/blocks/file-explorer.js +461 -192
  20. package/dist/blocks/settings.d.ts +2 -1
  21. package/dist/blocks/settings.js +101 -79
  22. package/dist/blocks/team-chat.d.ts +12 -0
  23. package/dist/blocks/team-chat.js +26 -0
  24. package/dist/blocks/theme-toggle.js +23 -31
  25. package/dist/chat-composer-attachment.d.ts +12 -3
  26. package/dist/chat-composer-attachment.js +61 -19
  27. package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
  28. package/dist/chat-composer-editor/extensions/mention.js +58 -27
  29. package/dist/chat-composer-editor/ghost.js +2 -4
  30. package/dist/chat-composer-editor/types.d.ts +5 -0
  31. package/dist/chat-composer-editor.d.ts +1 -1
  32. package/dist/chat-composer-editor.js +71 -28
  33. package/dist/chat-composer.d.ts +13 -32
  34. package/dist/chat-composer.js +54 -13
  35. package/dist/chat-layout.d.ts +19 -0
  36. package/dist/chat-layout.js +96 -32
  37. package/dist/chat-message/social.d.ts +26 -0
  38. package/dist/chat-message/social.js +122 -0
  39. package/dist/chat-message.d.ts +28 -29
  40. package/dist/chat-message.js +106 -25
  41. package/dist/context.d.ts +2 -2
  42. package/dist/context.js +38 -23
  43. package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
  44. package/dist/dynamic-notification-canvas-loop.js +113 -0
  45. package/dist/dynamic-notification-glass.js +23 -114
  46. package/dist/dynamic-notification-liquid.js +14 -118
  47. package/dist/dynamic-notification.d.ts +16 -8
  48. package/dist/dynamic-notification.js +85 -22
  49. package/dist/email/email-brand.d.ts +7 -4
  50. package/dist/email/email-brand.js +9 -6
  51. package/dist/email/email-code.d.ts +3 -2
  52. package/dist/email/email-code.js +11 -9
  53. package/dist/email/email-footer.d.ts +5 -3
  54. package/dist/email/email-footer.js +11 -5
  55. package/dist/email/email.d.ts +31 -9
  56. package/dist/email/email.js +50 -26
  57. package/dist/email/templates.d.ts +10 -9
  58. package/dist/email/templates.js +102 -38
  59. package/dist/email/theme.js +1 -1
  60. package/dist/environment-variables.d.ts +4 -2
  61. package/dist/environment-variables.js +52 -32
  62. package/dist/filter-bar/context.d.ts +32 -0
  63. package/dist/filter-bar/context.js +11 -0
  64. package/dist/filter-bar/editor.d.ts +14 -0
  65. package/dist/filter-bar/editor.js +216 -0
  66. package/dist/filter-bar/model.d.ts +10 -0
  67. package/dist/filter-bar/model.js +57 -0
  68. package/dist/filter-bar/motion.d.ts +10 -0
  69. package/dist/filter-bar/motion.js +84 -0
  70. package/dist/filter-bar/parts.d.ts +19 -0
  71. package/dist/filter-bar/parts.js +244 -0
  72. package/dist/filter-bar/types.d.ts +54 -0
  73. package/dist/filter-bar/types.js +0 -0
  74. package/dist/filter-bar.d.ts +27 -0
  75. package/dist/filter-bar.js +310 -0
  76. package/dist/hooks/use-audio-recorder.js +12 -6
  77. package/dist/hooks/use-chat-composer.d.ts +21 -7
  78. package/dist/hooks/use-chat-composer.js +32 -5
  79. package/dist/hooks/use-chat-message.d.ts +28 -2
  80. package/dist/hooks/use-chat-message.js +41 -10
  81. package/dist/hooks/use-chat-thread-scroll.js +43 -7
  82. package/dist/hooks/use-color-area.d.ts +3 -3
  83. package/dist/hooks/use-color-area.js +13 -12
  84. package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
  85. package/dist/hooks/use-copy-to-clipboard.js +20 -20
  86. package/dist/hooks/use-dropzone.d.ts +7 -4
  87. package/dist/hooks/use-dropzone.js +34 -3
  88. package/dist/hooks/use-dynamic-notification.d.ts +6 -2
  89. package/dist/hooks/use-dynamic-notification.js +26 -10
  90. package/dist/hooks/use-mobile.d.ts +3 -2
  91. package/dist/hooks/use-mobile.js +8 -4
  92. package/dist/hooks/use-textarea-trigger-menu.js +2 -1
  93. package/dist/hooks/use-trigger-menu.d.ts +54 -0
  94. package/dist/hooks/use-trigger-menu.js +102 -47
  95. package/dist/hooks/use-user-ask.d.ts +2 -0
  96. package/dist/hooks/use-user-ask.js +35 -41
  97. package/dist/inline-attachment.d.ts +6 -2
  98. package/dist/inline-attachment.js +44 -29
  99. package/dist/inline-citation.d.ts +12 -5
  100. package/dist/inline-citation.js +25 -16
  101. package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
  102. package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
  103. package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
  104. package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
  105. package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
  106. package/dist/knob-contracts/avatar-knobs.js +6 -1
  107. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  108. package/dist/knob-contracts/badge-knobs.js +2 -1
  109. package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
  110. package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
  111. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  112. package/dist/knob-contracts/button-knobs.js +1 -0
  113. package/dist/knob-contracts/chat-layout-knobs.d.ts +1 -1
  114. package/dist/knob-contracts/chat-layout-knobs.js +2 -1
  115. package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
  116. package/dist/knob-contracts/chat-message-knobs.js +8 -1
  117. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  118. package/dist/knob-contracts/choice-knobs.js +1 -0
  119. package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
  120. package/dist/knob-contracts/code-diff-knobs.js +2 -0
  121. package/dist/knob-contracts/code-knobs.d.ts +1 -1
  122. package/dist/knob-contracts/code-knobs.js +2 -1
  123. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  124. package/dist/knob-contracts/collapsible-knobs.js +1 -1
  125. package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
  126. package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
  127. package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
  128. package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
  129. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  130. package/dist/knob-contracts/field-knobs.js +5 -1
  131. package/dist/knob-contracts/filter-bar-knobs.d.ts +4 -0
  132. package/dist/knob-contracts/filter-bar-knobs.js +15 -0
  133. package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
  134. package/dist/knob-contracts/infinite-canvas-knobs.js +3 -0
  135. package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
  136. package/dist/knob-contracts/markdown-knobs.js +3 -1
  137. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  138. package/dist/knob-contracts/page-layout-knobs.js +4 -1
  139. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  140. package/dist/knob-contracts/popup-knobs.js +28 -0
  141. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  142. package/dist/knob-contracts/range-knobs.js +1 -0
  143. package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
  144. package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
  145. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  146. package/dist/knob-contracts/sidebar-knobs.js +27 -2
  147. package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
  148. package/dist/knob-contracts/spinner-knobs.js +1 -1
  149. package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
  150. package/dist/knob-contracts/stepper-knobs.js +4 -1
  151. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  152. package/dist/knob-contracts/switch-knobs.js +5 -0
  153. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  154. package/dist/knob-contracts/table-of-contents-knobs.js +4 -5
  155. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  156. package/dist/knob-contracts/tabs-knobs.js +1 -0
  157. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  158. package/dist/knob-contracts/tree-knobs.js +2 -1
  159. package/dist/lib/cn.js +0 -4
  160. package/dist/lib/contrast.d.ts +1 -0
  161. package/dist/lib/contrast.js +17 -5
  162. package/dist/lib/dropzone-validation.js +41 -28
  163. package/dist/lib/env-file.d.ts +1 -1
  164. package/dist/lib/env-file.js +5 -5
  165. package/dist/lib/motion.d.ts +4 -0
  166. package/dist/lib/motion.js +15 -0
  167. package/dist/lib/trigger-detect.js +6 -8
  168. package/dist/markdown-block.js +13 -8
  169. package/dist/popup-parts.d.ts +8 -0
  170. package/dist/popup-parts.js +43 -0
  171. package/dist/scripts/contrast-eval.js +5 -4
  172. package/dist/source-badge.js +3 -1
  173. package/dist/styles/code.css +0 -2
  174. package/dist/styles/effects.css +6 -32
  175. package/dist/styles/index.css +14 -8
  176. package/dist/styles/recipes/accordion.css +9 -6
  177. package/dist/styles/recipes/action-bar.css +0 -15
  178. package/dist/styles/recipes/activity.css +5 -11
  179. package/dist/styles/recipes/alert.css +4 -4
  180. package/dist/styles/recipes/audio-recorder.css +15 -3
  181. package/dist/styles/recipes/audio-visualizer.css +13 -2
  182. package/dist/styles/recipes/avatar.css +78 -1
  183. package/dist/styles/recipes/badge.css +14 -4
  184. package/dist/styles/recipes/breadcrumb.css +77 -0
  185. package/dist/styles/recipes/button-group.css +6 -8
  186. package/dist/styles/recipes/button-states.css +66 -0
  187. package/dist/styles/recipes/button.css +26 -85
  188. package/dist/styles/recipes/calendar.css +17 -23
  189. package/dist/styles/recipes/card.css +2 -2
  190. package/dist/styles/recipes/chat-composer-attachment.css +9 -3
  191. package/dist/styles/recipes/chat-composer.css +21 -16
  192. package/dist/styles/recipes/chat-layout.css +64 -2
  193. package/dist/styles/recipes/chat-message-social.css +123 -0
  194. package/dist/styles/recipes/chat-message.css +157 -37
  195. package/dist/styles/recipes/choice.css +25 -21
  196. package/dist/styles/recipes/code-diff.css +51 -26
  197. package/dist/styles/recipes/code.css +44 -18
  198. package/dist/styles/recipes/collapsible.css +43 -1
  199. package/dist/styles/recipes/color-picker-details.css +3 -4
  200. package/dist/styles/recipes/color-picker-values.css +9 -8
  201. package/dist/styles/recipes/color-picker.css +23 -4
  202. package/dist/styles/recipes/context.css +3 -3
  203. package/dist/styles/recipes/dockable-panel.css +18 -7
  204. package/dist/styles/recipes/dropzone.css +7 -8
  205. package/dist/styles/recipes/dynamic-notification-input.css +8 -5
  206. package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
  207. package/dist/styles/recipes/dynamic-notification.css +40 -26
  208. package/dist/styles/recipes/emoji-picker.css +132 -0
  209. package/dist/styles/recipes/empty.css +5 -4
  210. package/dist/styles/recipes/environment-variables.css +4 -15
  211. package/dist/styles/recipes/field-parts.css +89 -21
  212. package/dist/styles/recipes/field.css +83 -26
  213. package/dist/styles/recipes/filter-bar.css +161 -0
  214. package/dist/styles/recipes/gradient-editor.css +18 -7
  215. package/dist/styles/recipes/infinite-canvas.css +51 -4
  216. package/dist/styles/recipes/inline-attachment.css +9 -4
  217. package/dist/styles/recipes/inline-citation.css +10 -4
  218. package/dist/styles/recipes/item.css +23 -9
  219. package/dist/styles/recipes/markdown-block.css +5 -4
  220. package/dist/styles/recipes/markdown.css +68 -26
  221. package/dist/styles/recipes/morphing-panel.css +8 -8
  222. package/dist/styles/recipes/page-layout.css +36 -10
  223. package/dist/styles/recipes/pagination.css +5 -6
  224. package/dist/styles/recipes/phone-input.css +12 -8
  225. package/dist/styles/recipes/popup-controls.css +175 -26
  226. package/dist/styles/recipes/popup-drawer.css +35 -44
  227. package/dist/styles/recipes/popup-lists.css +139 -29
  228. package/dist/styles/recipes/popup-motion.css +4 -45
  229. package/dist/styles/recipes/popup-toast.css +55 -26
  230. package/dist/styles/recipes/popup.css +149 -153
  231. package/dist/styles/recipes/range.css +55 -19
  232. package/dist/styles/recipes/resizable.css +36 -7
  233. package/dist/styles/recipes/rich-tooltip.css +24 -34
  234. package/dist/styles/recipes/scroll-area.css +8 -1
  235. package/dist/styles/recipes/sidebar-menu.css +149 -34
  236. package/dist/styles/recipes/sidebar-parts.css +154 -0
  237. package/dist/styles/recipes/sidebar.css +187 -128
  238. package/dist/styles/recipes/source-badge.css +1 -1
  239. package/dist/styles/recipes/spinner.css +12 -1
  240. package/dist/styles/recipes/stepper.css +31 -13
  241. package/dist/styles/recipes/switch.css +68 -23
  242. package/dist/styles/recipes/table-of-contents.css +49 -41
  243. package/dist/styles/recipes/table.css +14 -10
  244. package/dist/styles/recipes/tabs.css +38 -12
  245. package/dist/styles/recipes/task-list.css +7 -14
  246. package/dist/styles/recipes/thread-rail.css +42 -19
  247. package/dist/styles/recipes/timeline.css +2 -0
  248. package/dist/styles/recipes/toolbar.css +27 -25
  249. package/dist/styles/recipes/track-highlight.css +12 -8
  250. package/dist/styles/recipes/transcript-divider.css +2 -2
  251. package/dist/styles/recipes/tree.css +31 -4
  252. package/dist/styles/recipes/user-ask-details.css +0 -1
  253. package/dist/styles/recipes/user-ask.css +4 -4
  254. package/dist/styles/theme.css +287 -107
  255. package/dist/task-list.d.ts +4 -1
  256. package/dist/task-list.js +24 -8
  257. package/dist/thread-rail.d.ts +1 -1
  258. package/dist/thread-rail.js +61 -19
  259. package/dist/transcript-divider.js +2 -1
  260. package/dist/ui/alert.d.ts +3 -1
  261. package/dist/ui/alert.js +14 -8
  262. package/dist/ui/autocomplete.js +7 -1
  263. package/dist/ui/avatar.d.ts +8 -0
  264. package/dist/ui/avatar.js +23 -3
  265. package/dist/ui/badge.js +1 -1
  266. package/dist/ui/breadcrumb.d.ts +27 -0
  267. package/dist/ui/breadcrumb.js +88 -0
  268. package/dist/ui/button.d.ts +2 -1
  269. package/dist/ui/button.js +1 -1
  270. package/dist/ui/calendar.js +2 -2
  271. package/dist/ui/card.d.ts +3 -1
  272. package/dist/ui/card.js +12 -7
  273. package/dist/ui/checkbox-group.js +1 -1
  274. package/dist/ui/checkbox.d.ts +2 -0
  275. package/dist/ui/code-diff.d.ts +1 -1
  276. package/dist/ui/code-diff.js +71 -27
  277. package/dist/ui/code.d.ts +5 -3
  278. package/dist/ui/code.js +27 -28
  279. package/dist/ui/color-picker.d.ts +5 -2
  280. package/dist/ui/color-picker.js +84 -36
  281. package/dist/ui/combobox-disabled-selection.d.ts +1 -2
  282. package/dist/ui/combobox-disabled-selection.js +7 -8
  283. package/dist/ui/combobox.d.ts +5 -23
  284. package/dist/ui/combobox.js +36 -16
  285. package/dist/ui/command.d.ts +8 -2
  286. package/dist/ui/command.js +39 -15
  287. package/dist/ui/context-menu.js +3 -0
  288. package/dist/ui/dialog.d.ts +2 -1
  289. package/dist/ui/dialog.js +3 -22
  290. package/dist/ui/dockable-panel-geometry.d.ts +1 -0
  291. package/dist/ui/dockable-panel-geometry.js +7 -1
  292. package/dist/ui/dockable-panel.d.ts +9 -6
  293. package/dist/ui/dockable-panel.js +81 -33
  294. package/dist/ui/drawer.d.ts +7 -4
  295. package/dist/ui/drawer.js +35 -22
  296. package/dist/ui/dropdown-menu.js +4 -3
  297. package/dist/ui/dropzone.d.ts +5 -3
  298. package/dist/ui/dropzone.js +78 -17
  299. package/dist/ui/emoji-picker.d.ts +18 -0
  300. package/dist/ui/emoji-picker.js +136 -0
  301. package/dist/ui/empty.d.ts +3 -1
  302. package/dist/ui/empty.js +12 -7
  303. package/dist/ui/field.js +1 -1
  304. package/dist/ui/gradient-editor.d.ts +1 -1
  305. package/dist/ui/gradient-editor.js +37 -21
  306. package/dist/ui/infinite-canvas.d.ts +13 -1
  307. package/dist/ui/infinite-canvas.js +139 -20
  308. package/dist/ui/input-group.d.ts +2 -4
  309. package/dist/ui/input-group.js +13 -14
  310. package/dist/ui/input-otp.d.ts +1 -1
  311. package/dist/ui/input-otp.js +32 -15
  312. package/dist/ui/input.js +11 -2
  313. package/dist/ui/item.d.ts +2 -1
  314. package/dist/ui/item.js +11 -7
  315. package/dist/ui/live-status.d.ts +7 -0
  316. package/dist/ui/live-status.js +17 -0
  317. package/dist/ui/markdown-elements.d.ts +6 -0
  318. package/dist/ui/markdown-elements.js +42 -9
  319. package/dist/ui/menubar.js +2 -2
  320. package/dist/ui/morphing-panel.js +2 -1
  321. package/dist/ui/native-select.d.ts +1 -1
  322. package/dist/ui/native-select.js +24 -11
  323. package/dist/ui/number-field.d.ts +1 -1
  324. package/dist/ui/number-field.js +13 -2
  325. package/dist/ui/page-layout.d.ts +2 -2
  326. package/dist/ui/page-layout.js +2 -4
  327. package/dist/ui/pagination.d.ts +7 -5
  328. package/dist/ui/pagination.js +19 -13
  329. package/dist/ui/phone-input.d.ts +1 -0
  330. package/dist/ui/phone-input.js +56 -58
  331. package/dist/ui/radio-group.d.ts +3 -0
  332. package/dist/ui/radio-group.js +11 -1
  333. package/dist/ui/resizable.d.ts +20 -2
  334. package/dist/ui/resizable.js +130 -8
  335. package/dist/ui/responsive-dialog.d.ts +1 -1
  336. package/dist/ui/responsive-dialog.js +9 -23
  337. package/dist/ui/rich-tooltip.d.ts +10 -4
  338. package/dist/ui/rich-tooltip.js +81 -33
  339. package/dist/ui/scroll-area.d.ts +4 -1
  340. package/dist/ui/scroll-area.js +44 -28
  341. package/dist/ui/select.d.ts +2 -1
  342. package/dist/ui/select.js +57 -32
  343. package/dist/ui/sidebar-menu.d.ts +5 -1
  344. package/dist/ui/sidebar-menu.js +31 -33
  345. package/dist/ui/sidebar-provider.d.ts +10 -1
  346. package/dist/ui/sidebar-provider.js +38 -13
  347. package/dist/ui/sidebar-resize-rail.js +7 -3
  348. package/dist/ui/sidebar.d.ts +11 -6
  349. package/dist/ui/sidebar.js +46 -23
  350. package/dist/ui/slider.js +31 -22
  351. package/dist/ui/spinner.d.ts +3 -2
  352. package/dist/ui/spinner.js +3 -2
  353. package/dist/ui/stepper.d.ts +8 -1
  354. package/dist/ui/stepper.js +74 -25
  355. package/dist/ui/switch.d.ts +1 -0
  356. package/dist/ui/table-of-contents.d.ts +5 -4
  357. package/dist/ui/table-of-contents.js +143 -52
  358. package/dist/ui/table.js +15 -9
  359. package/dist/ui/textarea.d.ts +1 -1
  360. package/dist/ui/textarea.js +20 -9
  361. package/dist/ui/timeline.d.ts +3 -1
  362. package/dist/ui/timeline.js +17 -9
  363. package/dist/ui/toast.d.ts +11 -5
  364. package/dist/ui/toast.js +100 -54
  365. package/dist/ui/toggle.d.ts +1 -1
  366. package/dist/ui/toggle.js +2 -1
  367. package/dist/ui/toolbar.d.ts +3 -2
  368. package/dist/ui/toolbar.js +4 -2
  369. package/dist/ui/tooltip.js +3 -20
  370. package/dist/ui/tree.js +120 -80
  371. package/dist/ui/trigger-menu.js +7 -2
  372. package/dist/user-ask.d.ts +10 -5
  373. package/dist/user-ask.js +31 -11
  374. package/package.json +5 -1
  375. package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
  376. package/dist/hooks/use-keyboard-navigation.js +0 -22
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import { cn } from "./lib/cn.js";
3
3
  import { Button } from "./ui/button.js";
4
+ import { LiveStatus } from "./ui/live-status.js";
4
5
  import { ScrollArea } from "./ui/scroll-area.js";
5
6
  import { Spinner } from "./ui/spinner.js";
6
- import { createContext, useContext } from "react";
7
+ import { createContext, useContext, useLayoutEffect, useState } from "react";
7
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
9
  import { XIcon } from "lucide-react";
9
10
  //#region src/chat-composer-attachment.tsx
@@ -67,6 +68,24 @@ function kindFromAttachment(type, name) {
67
68
  ].includes(extension)) return "document";
68
69
  return "file";
69
70
  }
71
+ function resolveVariant(variant, kind, status, previewUrl) {
72
+ if (variant !== "auto") return variant;
73
+ if (status === "error") return "file";
74
+ return previewUrl || kind === "image" ? "preview" : "file";
75
+ }
76
+ function useObjectUrl(blob) {
77
+ const [objectUrl, setObjectUrl] = useState();
78
+ useLayoutEffect(() => {
79
+ if (!blob) return;
80
+ const url = URL.createObjectURL(blob);
81
+ setObjectUrl({
82
+ blob,
83
+ url
84
+ });
85
+ return () => URL.revokeObjectURL(url);
86
+ }, [blob]);
87
+ return blob && objectUrl?.blob === blob ? objectUrl.url : void 0;
88
+ }
70
89
  function progressValue(progress) {
71
90
  if (typeof progress !== "number" || Number.isNaN(progress)) return void 0;
72
91
  return Math.min(100, Math.max(0, progress));
@@ -88,9 +107,27 @@ function defaultDescription(context) {
88
107
  const { progress } = context;
89
108
  if (context.description) return context.description;
90
109
  if (context.status === "uploading") return progress === void 0 ? "Uploading…" : `Uploading ${Math.round(progress)}%`;
91
- if (context.status === "error") return "Upload failed";
110
+ if (context.status === "error") return context.errorLabel;
92
111
  return fallbackLabel(context.kind, context.extension);
93
112
  }
113
+ function defaultUploadedLabel(name) {
114
+ return `${name} uploaded`;
115
+ }
116
+ function defaultUploadFailedLabel(name) {
117
+ return `${name} couldn't upload`;
118
+ }
119
+ function useUploadAnnouncement(status, name, uploadedLabel, uploadFailedLabel) {
120
+ const [previousStatus, setPreviousStatus] = useState(status);
121
+ const [announcement, setAnnouncement] = useState("");
122
+ if (status !== previousStatus) {
123
+ setPreviousStatus(status);
124
+ setAnnouncement({
125
+ uploaded: uploadedLabel,
126
+ error: uploadFailedLabel
127
+ }[status]?.(name) ?? "");
128
+ }
129
+ return announcement;
130
+ }
94
131
  function ChatComposerAttachments({ label = "Attachments", className, viewportClassName, children, ...props }) {
95
132
  return /* @__PURE__ */ jsx("div", {
96
133
  "data-control-ui": "chat-composer-attachments",
@@ -101,13 +138,12 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
101
138
  children: /* @__PURE__ */ jsx(ScrollArea, {
102
139
  "data-control-ui": "chat-composer-attachments",
103
140
  "data-slot": "scroll",
104
- "aria-label": label,
105
141
  lockAxis: "y",
106
142
  mask: true,
107
- scrollbarVisibility: "hover",
108
143
  className: "w-full",
109
144
  viewportClassName,
110
145
  children: /* @__PURE__ */ jsx("ul", {
146
+ "aria-label": label,
111
147
  "data-control-ui": "chat-composer-attachments",
112
148
  "data-control-family": "chat-composer-attachment",
113
149
  "data-slot": "list",
@@ -117,13 +153,16 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
117
153
  })
118
154
  });
119
155
  }
120
- function ChatComposerAttachment({ name, type, description, status = "idle", progress: progressInput, previewUrl, kind, variant = "auto", onRemove, removeLabel, className, children, role, "aria-label": ariaLabel, ...props }) {
156
+ function ChatComposerAttachment({ file, name: nameInput, type: typeInput, description, status = "idle", progress: progressInput, previewUrl: previewUrlInput, kind, variant = "auto", onRemove, removeLabel, errorLabel = "Couldn't upload. Remove and try again.", uploadedLabel = defaultUploadedLabel, uploadFailedLabel = defaultUploadFailedLabel, className, children, "aria-label": ariaLabel, ...props }) {
157
+ const name = nameInput ?? file?.name ?? "";
158
+ const type = typeInput ?? file?.type;
121
159
  const resolvedKind = kind ?? kindFromAttachment(type, name);
122
- let resolvedVariant;
123
- if (variant === "auto") resolvedVariant = previewUrl || resolvedKind === "image" ? "preview" : "file";
124
- else resolvedVariant = variant;
160
+ const resolvedVariant = resolveVariant(variant, resolvedKind, status, previewUrlInput);
161
+ const filePreviewUrl = useObjectUrl(resolvedVariant === "preview" && !previewUrlInput ? file : void 0);
162
+ const previewUrl = previewUrlInput ?? filePreviewUrl;
125
163
  const extension = extensionFromName(name);
126
164
  const progress = progressValue(progressInput);
165
+ const announcement = useUploadAnnouncement(status, name, uploadedLabel, uploadFailedLabel);
127
166
  return /* @__PURE__ */ jsx(ChatComposerAttachmentContext.Provider, {
128
167
  value: {
129
168
  name,
@@ -136,10 +175,10 @@ function ChatComposerAttachment({ name, type, description, status = "idle", prog
136
175
  variant: resolvedVariant,
137
176
  extension,
138
177
  onRemove,
139
- removeLabel
178
+ removeLabel,
179
+ errorLabel
140
180
  },
141
- children: /* @__PURE__ */ jsx("li", {
142
- role,
181
+ children: /* @__PURE__ */ jsxs("li", {
143
182
  "aria-label": ariaLabel ?? name,
144
183
  "data-control-ui": "chat-composer-attachment",
145
184
  "data-control-family": "chat-composer-attachment",
@@ -150,12 +189,12 @@ function ChatComposerAttachment({ name, type, description, status = "idle", prog
150
189
  "data-variant": resolvedVariant,
151
190
  className: cn("relative isolate flex shrink-0 items-center overflow-hidden", className),
152
191
  ...props,
153
- children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
192
+ children: [children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
154
193
  /* @__PURE__ */ jsx(ChatComposerAttachmentPreview, {}),
155
194
  resolvedVariant === "file" && /* @__PURE__ */ jsxs(ChatComposerAttachmentContent, { children: [/* @__PURE__ */ jsx(ChatComposerAttachmentTitle, {}), /* @__PURE__ */ jsx(ChatComposerAttachmentDescription, {})] }),
156
195
  /* @__PURE__ */ jsx(ChatComposerAttachmentRemove, {}),
157
196
  /* @__PURE__ */ jsx(ChatComposerAttachmentProgress, {})
158
- ] })
197
+ ] }), /* @__PURE__ */ jsx(LiveStatus, { message: announcement })]
159
198
  })
160
199
  });
161
200
  }
@@ -166,7 +205,7 @@ function ChatComposerAttachmentPreview({ className, children, ...props }) {
166
205
  "data-control-ui": "chat-composer-attachment",
167
206
  "data-control-family": "chat-composer-attachment",
168
207
  "data-slot": "preview",
169
- className: cn("relative grid shrink-0 place-items-center overflow-hidden", className),
208
+ className: cn("relative flex shrink-0 items-center justify-center overflow-hidden", className),
170
209
  ...props,
171
210
  children: children ?? (context.previewUrl ? /* @__PURE__ */ jsx("img", {
172
211
  src: context.previewUrl,
@@ -194,25 +233,29 @@ function ChatComposerAttachmentContent({ className, ...props }) {
194
233
  }
195
234
  function ChatComposerAttachmentTitle({ className, children, ...props }) {
196
235
  const { name } = useChatComposerAttachmentContext();
236
+ const content = children ?? name;
197
237
  return /* @__PURE__ */ jsx("div", {
198
238
  "data-control-ui": "chat-composer-attachment",
199
239
  "data-control-family": "chat-composer-attachment",
200
240
  "data-slot": "title",
241
+ title: typeof content === "string" ? content : void 0,
201
242
  className: cn("truncate", className),
202
243
  ...props,
203
- children: children ?? name
244
+ children: content
204
245
  });
205
246
  }
206
247
  function ChatComposerAttachmentDescription({ className, children, ...props }) {
207
248
  const context = useChatComposerAttachmentContext();
249
+ const content = children ?? defaultDescription(context);
208
250
  return /* @__PURE__ */ jsx("div", {
209
251
  "data-control-ui": "chat-composer-attachment",
210
252
  "data-control-family": "chat-composer-attachment",
211
253
  "data-slot": "description",
254
+ title: typeof content === "string" ? content : void 0,
212
255
  className: cn("truncate", className),
213
256
  "data-state": context.status,
214
257
  ...props,
215
- children: children ?? defaultDescription(context)
258
+ children: content
216
259
  });
217
260
  }
218
261
  function ChatComposerAttachmentRemove({ className, onClick, children, "aria-label": ariaLabel, ...props }) {
@@ -230,7 +273,7 @@ function ChatComposerAttachmentRemove({ className, onClick, children, "aria-labe
230
273
  "aria-label": ariaLabel ?? removeLabel ?? `Remove ${name}`,
231
274
  size: "xs",
232
275
  variant: "solid",
233
- className: cn("absolute right-1 top-1 z-10", className),
276
+ className: cn("absolute end-1 top-1 z-10", className),
234
277
  onClick: handleClick,
235
278
  ...props,
236
279
  children: children ?? /* @__PURE__ */ jsx(XIcon, {
@@ -239,7 +282,7 @@ function ChatComposerAttachmentRemove({ className, onClick, children, "aria-labe
239
282
  })
240
283
  });
241
284
  }
242
- function ChatComposerAttachmentProgress({ className, children, style, ...props }) {
285
+ function ChatComposerAttachmentProgress({ className, children, ...props }) {
243
286
  const { name, status, progress } = useChatComposerAttachmentContext();
244
287
  if (status !== "uploading") return null;
245
288
  return /* @__PURE__ */ jsx("div", {
@@ -252,7 +295,6 @@ function ChatComposerAttachmentProgress({ className, children, style, ...props }
252
295
  "data-control-family": "chat-composer-attachment",
253
296
  "data-slot": "progress",
254
297
  className: cn("pointer-events-none absolute inset-0 z-1 grid place-items-center", className),
255
- style,
256
298
  ...props,
257
299
  children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
258
300
  "data-control-ui": "chat-composer-attachment",
@@ -8,6 +8,10 @@ export type MentionExtensionConfig<Item extends TriggerMenuItemData = TriggerMen
8
8
  triggers: readonly TriggerConfig<Item>[];
9
9
  side?: "top" | "bottom";
10
10
  align?: "start" | "center" | "end";
11
+ /** Shown and announced when a trigger's query matches nothing. */
12
+ emptyLabel?: string;
13
+ /** Announced while the menu is open, so opening it and narrowing it are heard from the editor. */
14
+ resultsLabel?: (count: number) => string;
11
15
  };
12
- export declare function mentionExtension<Item extends TriggerMenuItemData = TriggerMenuItemData>({ triggers, side, align }: MentionExtensionConfig<Item>): ChatComposerEditorExtension;
16
+ export declare function mentionExtension<Item extends TriggerMenuItemData = TriggerMenuItemData>({ triggers, side, align, emptyLabel, resultsLabel }: MentionExtensionConfig<Item>): ChatComposerEditorExtension;
13
17
  //#endregion
@@ -1,10 +1,11 @@
1
1
  "use client";
2
+ import { LiveStatus } from "../../ui/live-status.js";
3
+ import { isComposingKey, useTriggerMenu } from "../../hooks/use-trigger-menu.js";
2
4
  import { spawnExitGhost } from "../ghost.js";
3
5
  import { detectTrigger } from "../../lib/trigger-detect.js";
4
- import { useTriggerMenu } from "../../hooks/use-trigger-menu.js";
5
6
  import { TriggerMenu, TriggerMenuEmpty, TriggerMenuIcon, TriggerMenuItem, TriggerMenuList } from "../../ui/trigger-menu.js";
6
7
  import { useEffect } from "react";
7
- import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
9
  //#region src/chat-composer-editor/extensions/mention.tsx
9
10
  function attrString(value, fallback) {
10
11
  return typeof value === "string" ? value : fallback;
@@ -181,7 +182,7 @@ function collectMentions(doc) {
181
182
  });
182
183
  return mentions;
183
184
  }
184
- function MentionOverlay({ editor, triggers, side, align }) {
185
+ function MentionOverlay({ editor, triggers, side, align, emptyLabel, resultsLabel }) {
185
186
  const chars = triggers.map((trigger) => trigger.char);
186
187
  const controller = useTriggerMenu({
187
188
  triggers,
@@ -220,8 +221,23 @@ function MentionOverlay({ editor, triggers, side, align }) {
220
221
  controller,
221
222
  chars
222
223
  ]);
224
+ const { "aria-controls": controls, "aria-activedescendant": activeDescendant } = controller.inputAria;
225
+ useEffect(() => {
226
+ editor.setHostAria({
227
+ "aria-autocomplete": "list",
228
+ "aria-haspopup": "listbox",
229
+ ...controls ? { "aria-controls": controls } : {},
230
+ ...activeDescendant ? { "aria-activedescendant": activeDescendant } : {}
231
+ });
232
+ }, [
233
+ editor,
234
+ controls,
235
+ activeDescendant
236
+ ]);
237
+ useEffect(() => () => editor.setHostAria({}), [editor]);
223
238
  useEffect(() => {
224
239
  return editor.registerKeyHandler((event) => {
240
+ if (isComposingKey(event)) return false;
225
241
  const view = editor.getView();
226
242
  if (!view) return false;
227
243
  if (!readEditorTrigger(view, chars).active) return false;
@@ -233,35 +249,48 @@ function MentionOverlay({ editor, triggers, side, align }) {
233
249
  chars
234
250
  ]);
235
251
  if (triggers.length === 0) return null;
236
- return /* @__PURE__ */ jsx(TriggerMenu, {
252
+ const isEmpty = controller.open && controller.items.length === 0;
253
+ let status = "";
254
+ if (isEmpty) status = emptyLabel;
255
+ else if (controller.open) status = resultsLabel(controller.items.length);
256
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LiveStatus, { message: status }), /* @__PURE__ */ jsxs(TriggerMenu, {
237
257
  open: controller.open,
238
258
  onOpenChange: controller.setOpen,
239
259
  anchorRect: controller.anchorRect,
240
260
  side,
241
261
  align,
242
- children: /* @__PURE__ */ jsx(TriggerMenuList, { children: controller.items.length === 0 ? /* @__PURE__ */ jsx(TriggerMenuEmpty, { children: "No results" }) : controller.items.map((item, index) => /* @__PURE__ */ jsxs(TriggerMenuItem, {
243
- active: index === controller.activeIndex,
244
- disabled: item.disabled,
245
- onPointerMove: () => controller.setActiveIndex(index),
246
- onClick: () => controller.select(item),
247
- children: [
248
- item.icon ? /* @__PURE__ */ jsx(TriggerMenuIcon, { children: item.icon }) : null,
249
- /* @__PURE__ */ jsx("span", {
250
- className: "flex-1 truncate",
251
- children: item.label
252
- }),
253
- item.description ? /* @__PURE__ */ jsx("span", {
254
- "data-control-ui": "chat-composer",
255
- "data-control-family": "chat-composer",
256
- "data-slot": "mention-description",
257
- className: "truncate",
258
- children: item.description
259
- }) : null
260
- ]
261
- }, item.id)) })
262
- });
262
+ children: [isEmpty ? /* @__PURE__ */ jsx(TriggerMenuEmpty, {
263
+ "aria-hidden": "true",
264
+ children: emptyLabel
265
+ }) : null, /* @__PURE__ */ jsx(TriggerMenuList, {
266
+ id: controller.listId,
267
+ children: controller.items.map((item, index) => /* @__PURE__ */ jsxs(TriggerMenuItem, {
268
+ id: controller.optionId(index),
269
+ active: index === controller.activeIndex,
270
+ disabled: item.disabled,
271
+ onPointerMove: () => controller.setActiveIndex(index),
272
+ onClick: () => controller.select(item),
273
+ children: [
274
+ item.icon ? /* @__PURE__ */ jsx(TriggerMenuIcon, { children: item.icon }) : null,
275
+ /* @__PURE__ */ jsx("span", {
276
+ className: "flex-1 truncate",
277
+ title: item.label,
278
+ children: item.label
279
+ }),
280
+ item.description ? /* @__PURE__ */ jsx("span", {
281
+ "data-control-ui": "chat-composer",
282
+ "data-control-family": "chat-composer",
283
+ "data-slot": "mention-description",
284
+ className: "truncate",
285
+ title: item.description,
286
+ children: item.description
287
+ }) : null
288
+ ]
289
+ }, item.id))
290
+ })]
291
+ })] });
263
292
  }
264
- function mentionExtension({ triggers, side = "top", align = "start" }) {
293
+ function mentionExtension({ triggers, side = "top", align = "start", emptyLabel = "No matches", resultsLabel = (count) => count === 1 ? "1 suggestion" : `${count} suggestions` }) {
265
294
  return {
266
295
  name: "mention",
267
296
  nodes: { mention: mentionNode },
@@ -274,7 +303,9 @@ function mentionExtension({ triggers, side = "top", align = "start" }) {
274
303
  editor,
275
304
  triggers,
276
305
  side,
277
- align
306
+ align,
307
+ emptyLabel,
308
+ resultsLabel
278
309
  })
279
310
  };
280
311
  }
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { prefersReducedMotion } from "../lib/motion.js";
2
3
  //#region src/chat-composer-editor/ghost.ts
3
4
  const MESSAGE_INHERIT = [
4
5
  "font-family",
@@ -14,12 +15,9 @@ const MESSAGE_INHERIT = [
14
15
  "word-break",
15
16
  "text-align"
16
17
  ];
17
- function prefersReducedMotion() {
18
- return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
19
- }
20
18
  function spawnExitGhost(source, inherit = []) {
21
19
  if (typeof document === "undefined") return;
22
- if (prefersReducedMotion()) return;
20
+ if (prefersReducedMotion(source)) return;
23
21
  const rect = source.getBoundingClientRect();
24
22
  if (rect.width === 0 && rect.height === 0) return;
25
23
  const ghost = document.createElement(source.tagName.toLowerCase());
@@ -8,6 +8,8 @@ export type ChatComposerEditorApi = {
8
8
  getView: () => EditorView | null;
9
9
  subscribe: (listener: () => void) => () => void;
10
10
  registerKeyHandler: (handler: (event: KeyboardEvent) => boolean) => () => void;
11
+ /** Merges extra ARIA onto the editable host (e.g. combobox state while a trigger menu is open). */
12
+ setHostAria: (attributes: Readonly<Record<string, string>>) => void;
11
13
  };
12
14
  export type ChatComposerEditorOverlayProps = {
13
15
  editor: ChatComposerEditorApi;
@@ -24,5 +26,8 @@ export type ChatComposerEditorProps = {
24
26
  className?: string;
25
27
  placeholder?: string;
26
28
  extensions?: readonly ChatComposerEditorExtension[];
29
+ "aria-label"?: string;
30
+ "aria-labelledby"?: string;
31
+ "aria-describedby"?: string;
27
32
  };
28
33
  //#endregion
@@ -2,7 +2,7 @@ import { ChatComposerKnobStyle } from "./knob-contracts/chat-composer-knobs.js";
2
2
  import { ChatComposerEditorProps } from "./chat-composer-editor/types.js";
3
3
  import { CSSProperties } from "react";
4
4
  //#region src/chat-composer-editor.d.ts
5
- export declare function ChatComposerEditor({ className, placeholder, style, extensions }: ChatComposerEditorProps & {
5
+ export declare function ChatComposerEditor({ className, placeholder, style, extensions, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }: ChatComposerEditorProps & {
6
6
  style?: CSSProperties & ChatComposerKnobStyle;
7
7
  }): import("react").JSX.Element;
8
8
  //#endregion
@@ -4,7 +4,7 @@ import { useChatComposerContext } from "./chat-composer.js";
4
4
  import { MESSAGE_GHOST_INHERIT, spawnExitGhost } from "./chat-composer-editor/ghost.js";
5
5
  import { createEditorSchema } from "./chat-composer-editor/schema.js";
6
6
  import { docFromText, serializeDoc } from "./chat-composer-editor/serialize.js";
7
- import { useEffect, useRef, useState } from "react";
7
+ import { useEffect, useMemo, useRef, useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  import { baseKeymap, splitBlock } from "prosemirror-commands";
10
10
  import { history, redo, undo } from "prosemirror-history";
@@ -12,8 +12,20 @@ import { keymap } from "prosemirror-keymap";
12
12
  import { EditorState, Plugin } from "prosemirror-state";
13
13
  import { EditorView } from "prosemirror-view";
14
14
  //#region src/chat-composer-editor.tsx
15
- const SUBMIT_KEY = "Enter";
16
- function ChatComposerEditor({ className, placeholder, style, extensions = [] }) {
15
+ function releaseFileDrag(_view, event) {
16
+ return event.dataTransfer ? Array.from(event.dataTransfer.types).includes("Files") : false;
17
+ }
18
+ /** ProseMirror claims every drag and paste over the editor. A transfer it cannot insert is not editor input, so it has to reach the surrounding drop zone with its default intact. */
19
+ const fileTransferPassthrough = {
20
+ dragenter: releaseFileDrag,
21
+ dragover: releaseFileDrag,
22
+ drop: releaseFileDrag,
23
+ paste: (_view, event) => {
24
+ const types = event.clipboardData ? Array.from(event.clipboardData.types) : [];
25
+ return types.includes("Files") && !types.includes("text/plain");
26
+ }
27
+ };
28
+ function ChatComposerEditor({ className, placeholder, style, extensions = [], "aria-label": ariaLabel = "Message", "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }) {
17
29
  const input = useChatComposerContext();
18
30
  const mountRef = useRef(null);
19
31
  const viewRef = useRef(null);
@@ -23,6 +35,30 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
23
35
  }, [input]);
24
36
  const lastSerialized = useRef(input.value);
25
37
  const [mounted, setMounted] = useState(false);
38
+ const [hostAria, setHostAria] = useState({});
39
+ const { isDisabled, isLocked } = input;
40
+ const hostAttributes = useMemo(() => ({
41
+ role: "textbox",
42
+ "aria-multiline": "true",
43
+ ...ariaLabelledby ? { "aria-labelledby": ariaLabelledby } : { "aria-label": ariaLabel },
44
+ ...ariaDescribedby ? { "aria-describedby": ariaDescribedby } : {},
45
+ ...placeholder ? { "aria-placeholder": placeholder } : {},
46
+ ...isDisabled ? { "aria-disabled": "true" } : {},
47
+ ...isLocked ? {
48
+ "aria-readonly": "true",
49
+ "aria-busy": "true"
50
+ } : {},
51
+ ...hostAria
52
+ }), [
53
+ ariaLabel,
54
+ ariaLabelledby,
55
+ ariaDescribedby,
56
+ placeholder,
57
+ isDisabled,
58
+ isLocked,
59
+ hostAria
60
+ ]);
61
+ const hostAttributesRef = useRef(hostAttributes);
26
62
  const listenersRef = useRef(/* @__PURE__ */ new Set());
27
63
  const keyHandlersRef = useRef(/* @__PURE__ */ new Set());
28
64
  const [api] = useState(() => ({
@@ -38,7 +74,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
38
74
  return () => {
39
75
  keyHandlersRef.current.delete(handler);
40
76
  };
41
- }
77
+ },
78
+ setHostAria
42
79
  }));
43
80
  const [initialExtensions] = useState(extensions);
44
81
  useEffect(() => {
@@ -55,14 +92,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
55
92
  inputRef.current.submit(extra);
56
93
  return true;
57
94
  };
95
+ const submitOnEnter = (editorState) => inputRef.current.submitKey === "enter" && submitMessage(editorState);
58
96
  const state = EditorState.create({
59
97
  schema,
60
98
  doc: docFromText(schema, inputRef.current.value),
61
99
  plugins: [
62
- new Plugin({ props: { handleKeyDown: (_view, event) => {
63
- for (const handler of keyHandlersRef.current) if (handler(event)) return true;
64
- return false;
65
- } } }),
100
+ new Plugin({
101
+ props: {
102
+ handleKeyDown: (_view, event) => {
103
+ for (const handler of keyHandlersRef.current) if (handler(event)) return true;
104
+ return false;
105
+ },
106
+ handleDOMEvents: fileTransferPassthrough
107
+ },
108
+ filterTransaction: (transaction) => !transaction.docChanged || !inputRef.current.isLocked
109
+ }),
66
110
  ...extensionPlugins,
67
111
  history(),
68
112
  keymap(extensionKeymap),
@@ -71,7 +115,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
71
115
  "Mod-y": redo,
72
116
  "Shift-Mod-z": redo,
73
117
  "Shift-Enter": splitBlock,
74
- [SUBMIT_KEY]: submitMessage
118
+ Enter: submitOnEnter,
119
+ "Mod-Enter": submitMessage
75
120
  }),
76
121
  keymap(baseKeymap)
77
122
  ]
@@ -79,11 +124,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
79
124
  const view = new EditorView(mount, {
80
125
  state,
81
126
  editable: () => !inputRef.current.isDisabled,
82
- attributes: {
83
- "aria-label": "Message",
84
- "aria-multiline": "true",
85
- role: "textbox"
86
- },
127
+ attributes: () => hostAttributesRef.current,
87
128
  dispatchTransaction(transaction) {
88
129
  const next = view.state.apply(transaction);
89
130
  view.updateState(next);
@@ -102,6 +143,13 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
102
143
  viewRef.current = null;
103
144
  };
104
145
  }, [api, initialExtensions]);
146
+ const { registerInsertionTarget } = input;
147
+ useEffect(() => registerInsertionTarget((text) => {
148
+ const view = viewRef.current;
149
+ if (!view) return;
150
+ view.dispatch(view.state.tr.insertText(text));
151
+ view.focus();
152
+ }), [registerInsertionTarget]);
105
153
  useEffect(() => {
106
154
  const view = viewRef.current;
107
155
  if (!view) return;
@@ -116,16 +164,9 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
116
164
  }));
117
165
  }, [input.value]);
118
166
  useEffect(() => {
119
- viewRef.current?.setProps({
120
- editable: () => !input.isDisabled,
121
- attributes: {
122
- "aria-label": "Message",
123
- "aria-multiline": "true",
124
- "aria-disabled": String(input.isDisabled),
125
- role: "textbox"
126
- }
127
- });
128
- }, [input.isDisabled]);
167
+ hostAttributesRef.current = hostAttributes;
168
+ viewRef.current?.setProps({});
169
+ }, [hostAttributes]);
129
170
  return /* @__PURE__ */ jsxs("div", {
130
171
  "data-control-ui": "chat-composer-editor",
131
172
  "data-control-family": "chat-composer",
@@ -138,7 +179,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
138
179
  "data-control-ui": "chat-composer-editor",
139
180
  "data-control-family": "chat-composer",
140
181
  "data-slot": "placeholder",
141
- className: "pointer-events-none absolute left-[var(--padding-x)] top-[var(--padding-y)]",
182
+ className: "pointer-events-none absolute start-[var(--padding-x)] top-[var(--padding-y)]",
142
183
  children: placeholder
143
184
  }) : null,
144
185
  /* @__PURE__ */ jsx("div", {
@@ -146,19 +187,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
146
187
  "data-control-ui": "chat-composer-editor",
147
188
  "data-control-family": "chat-composer",
148
189
  "data-slot": "editor",
149
- className: cn("[&_.ProseMirror]:max-h-[40dvh] [&_.ProseMirror]:min-h-16 [&_.ProseMirror]:w-full [&_.ProseMirror]:overflow-y-auto [&_.ProseMirror]:outline-none [&_.ProseMirror]:whitespace-pre-wrap [&_.ProseMirror]:break-words [&_.ProseMirror]:px-[var(--padding-x)] [&_.ProseMirror]:py-[var(--padding-y)]", mounted ? "" : "hidden")
190
+ className: cn("[&_.ProseMirror]:max-h-[40dvh] [&_.ProseMirror]:min-h-16 [&_.ProseMirror]:w-full [&_.ProseMirror]:overflow-y-auto [&_.ProseMirror]:whitespace-pre-wrap [&_.ProseMirror]:break-words [&_.ProseMirror]:px-[var(--padding-x)] [&_.ProseMirror]:py-[var(--padding-y)]", mounted ? "" : "hidden")
150
191
  }),
151
192
  mounted ? null : /* @__PURE__ */ jsx("textarea", {
152
193
  "data-control-ui": "chat-composer-editor",
153
194
  "data-control-family": "chat-composer",
154
195
  "data-slot": "fallback",
155
- "aria-label": "Message",
196
+ "aria-label": ariaLabelledby ? void 0 : ariaLabel,
197
+ "aria-labelledby": ariaLabelledby,
198
+ "aria-describedby": ariaDescribedby,
156
199
  defaultValue: input.value,
157
200
  readOnly: true,
158
201
  disabled: input.isDisabled,
159
202
  rows: 2,
160
203
  placeholder,
161
- className: "min-h-16 w-full resize-none outline-none px-[var(--padding-x)] py-[var(--padding-y)]"
204
+ className: "min-h-16 w-full resize-none px-[var(--padding-x)] py-[var(--padding-y)]"
162
205
  }),
163
206
  mounted ? initialExtensions.map((extension) => extension.Overlay ? /* @__PURE__ */ jsx(extension.Overlay, { editor: api }, extension.name) : null) : null
164
207
  ]
@@ -1,35 +1,10 @@
1
- import { FormSubmitEvent } from "./control-props.js";
2
1
  import { Button } from "./ui/button.js";
3
2
  import { ChatComposerKnobStyle } from "./knob-contracts/chat-composer-knobs.js";
4
- import { ChatDensity } from "./hooks/use-chat-message.js";
5
- import { ChatComposerProps, ChatComposerSubmitPayload } from "./hooks/use-chat-composer.js";
6
- import { CSSProperties, ComponentProps } from "react";
3
+ import { ChatComposerController, ChatComposerProps } from "./hooks/use-chat-composer.js";
4
+ import { CSSProperties, ComponentProps, ReactNode } from "react";
7
5
  //#region src/chat-composer.d.ts
8
- export declare function useChatComposerContext(): {
9
- value: string;
10
- setValue: (nextValue: string) => void;
11
- normalizedValue: string;
12
- state: "disabled" | "idle" | "submitting";
13
- density: ChatDensity;
14
- isCompact: boolean;
15
- isDisabled: boolean;
16
- canSubmit: boolean;
17
- rows: number;
18
- sendCount: number;
19
- clear: () => void;
20
- submit: (extra?: Partial<ChatComposerSubmitPayload>) => void;
21
- handleSubmit: (event: FormSubmitEvent) => void;
22
- handleKeyDown: (event: {
23
- key: string;
24
- shiftKey: boolean;
25
- defaultPrevented: boolean;
26
- nativeEvent: {
27
- isComposing: boolean;
28
- };
29
- preventDefault: () => void;
30
- }) => void;
31
- };
32
- export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
6
+ export declare function useChatComposerContext(): ChatComposerController;
7
+ export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, allowEmptySubmit, submitKey, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
33
8
  export type ChatComposerShellProps = Omit<ComponentProps<"div">, "style"> & {
34
9
  style?: CSSProperties & ChatComposerKnobStyle;
35
10
  };
@@ -41,7 +16,7 @@ export declare function ChatComposerAccent({ className, ...props }: ChatComposer
41
16
  export type ChatComposerTextareaProps = Omit<ComponentProps<"textarea">, "style"> & {
42
17
  style?: CSSProperties & ChatComposerKnobStyle;
43
18
  };
44
- export declare function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown, ...props }: ChatComposerTextareaProps): import("react").JSX.Element;
19
+ export declare function ChatComposerTextarea({ className, rows, disabled, readOnly, onChange, onKeyDown, ref, ...props }: ChatComposerTextareaProps): import("react").JSX.Element;
45
20
  export type ChatComposerToolbarProps = ComponentProps<"div"> & {
46
21
  style?: CSSProperties & ChatComposerKnobStyle;
47
22
  };
@@ -54,6 +29,12 @@ export type ChatComposerFooterProps = Omit<ComponentProps<"div">, "style"> & {
54
29
  style?: CSSProperties & ChatComposerKnobStyle;
55
30
  };
56
31
  export declare function ChatComposerFooter({ className, ...props }: ChatComposerFooterProps): import("react").JSX.Element;
57
- export type ChatComposerSubmitProps = ComponentProps<typeof Button>;
58
- export declare function ChatComposerSubmit({ className, disabled, children, ...props }: ChatComposerSubmitProps): import("react").JSX.Element;
32
+ export type ChatComposerSubmitProps = ComponentProps<typeof Button> & {
33
+ /** While the composer is submitting, turns this same button into Stop so focus stays put. */
34
+ onStop?: () => void;
35
+ stopLabel?: string;
36
+ /** Content of an `iconOnly` button in its Stop state; `stopLabel` becomes its name. */
37
+ stopIcon?: ReactNode;
38
+ };
39
+ export declare function ChatComposerSubmit({ className, disabled, iconOnly, children, onStop, stopLabel, stopIcon, onClick, "aria-label": ariaLabel, ...props }: ChatComposerSubmitProps): import("react").JSX.Element;
59
40
  //#endregion