@ctrl-ui/react 0.8.7 → 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 (351) hide show
  1. package/dist/action-bar.d.ts +15 -7
  2. package/dist/action-bar.js +40 -20
  3. package/dist/activity.js +8 -16
  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 +39 -39
  8. package/dist/blocks/design-canvas-inspector.js +2 -2
  9. package/dist/blocks/design-canvas.d.ts +5 -1
  10. package/dist/blocks/design-canvas.js +200 -94
  11. package/dist/blocks/discussion.d.ts +28 -0
  12. package/dist/blocks/discussion.js +67 -0
  13. package/dist/blocks/file-explorer-data.d.ts +1 -0
  14. package/dist/blocks/file-explorer-data.js +4 -1
  15. package/dist/blocks/file-explorer.d.ts +9 -1
  16. package/dist/blocks/file-explorer.js +461 -192
  17. package/dist/blocks/settings.d.ts +2 -1
  18. package/dist/blocks/settings.js +102 -80
  19. package/dist/blocks/team-chat.d.ts +12 -0
  20. package/dist/blocks/team-chat.js +26 -0
  21. package/dist/blocks/theme-toggle.js +23 -31
  22. package/dist/chat-composer-attachment.d.ts +4 -1
  23. package/dist/chat-composer-attachment.js +35 -10
  24. package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
  25. package/dist/chat-composer-editor/extensions/mention.js +58 -27
  26. package/dist/chat-composer-editor/ghost.js +2 -4
  27. package/dist/chat-composer-editor/types.d.ts +5 -0
  28. package/dist/chat-composer-editor.d.ts +1 -1
  29. package/dist/chat-composer-editor.js +57 -30
  30. package/dist/chat-composer.d.ts +13 -32
  31. package/dist/chat-composer.js +53 -13
  32. package/dist/chat-layout.d.ts +19 -0
  33. package/dist/chat-layout.js +91 -40
  34. package/dist/chat-message/social.d.ts +26 -0
  35. package/dist/chat-message/social.js +122 -0
  36. package/dist/chat-message.d.ts +28 -29
  37. package/dist/chat-message.js +106 -25
  38. package/dist/context.d.ts +2 -2
  39. package/dist/context.js +39 -24
  40. package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
  41. package/dist/dynamic-notification-canvas-loop.js +113 -0
  42. package/dist/dynamic-notification-glass.js +23 -114
  43. package/dist/dynamic-notification-liquid.js +14 -118
  44. package/dist/dynamic-notification.d.ts +16 -8
  45. package/dist/dynamic-notification.js +86 -23
  46. package/dist/email/email-brand.d.ts +7 -4
  47. package/dist/email/email-brand.js +9 -6
  48. package/dist/email/email-code.d.ts +3 -2
  49. package/dist/email/email-code.js +11 -9
  50. package/dist/email/email-footer.d.ts +5 -3
  51. package/dist/email/email-footer.js +11 -5
  52. package/dist/email/email.d.ts +31 -9
  53. package/dist/email/email.js +50 -26
  54. package/dist/email/templates.d.ts +10 -9
  55. package/dist/email/templates.js +102 -38
  56. package/dist/email/theme.js +1 -1
  57. package/dist/environment-variables.d.ts +4 -2
  58. package/dist/environment-variables.js +52 -32
  59. package/dist/filter-bar/editor.js +20 -8
  60. package/dist/filter-bar/model.d.ts +1 -0
  61. package/dist/filter-bar/model.js +10 -1
  62. package/dist/filter-bar/motion.js +4 -4
  63. package/dist/filter-bar/parts.js +20 -9
  64. package/dist/filter-bar/types.d.ts +1 -0
  65. package/dist/filter-bar.js +20 -14
  66. package/dist/hooks/use-audio-recorder.js +12 -6
  67. package/dist/hooks/use-chat-composer.d.ts +19 -7
  68. package/dist/hooks/use-chat-composer.js +32 -5
  69. package/dist/hooks/use-chat-message.d.ts +28 -2
  70. package/dist/hooks/use-chat-message.js +41 -10
  71. package/dist/hooks/use-chat-thread-scroll.js +43 -7
  72. package/dist/hooks/use-color-area.d.ts +3 -3
  73. package/dist/hooks/use-color-area.js +13 -12
  74. package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
  75. package/dist/hooks/use-copy-to-clipboard.js +20 -20
  76. package/dist/hooks/use-dynamic-notification.d.ts +6 -2
  77. package/dist/hooks/use-dynamic-notification.js +26 -10
  78. package/dist/hooks/use-mobile.d.ts +3 -2
  79. package/dist/hooks/use-mobile.js +8 -4
  80. package/dist/hooks/use-textarea-trigger-menu.js +2 -1
  81. package/dist/hooks/use-trigger-menu.d.ts +54 -0
  82. package/dist/hooks/use-trigger-menu.js +102 -47
  83. package/dist/hooks/use-user-ask.d.ts +2 -0
  84. package/dist/hooks/use-user-ask.js +35 -41
  85. package/dist/inline-attachment.d.ts +6 -2
  86. package/dist/inline-attachment.js +36 -15
  87. package/dist/inline-citation.d.ts +12 -5
  88. package/dist/inline-citation.js +26 -17
  89. package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
  90. package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
  91. package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
  92. package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
  93. package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
  94. package/dist/knob-contracts/avatar-knobs.js +6 -1
  95. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  96. package/dist/knob-contracts/badge-knobs.js +2 -1
  97. package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
  98. package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
  99. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  100. package/dist/knob-contracts/button-knobs.js +1 -0
  101. package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
  102. package/dist/knob-contracts/chat-message-knobs.js +8 -1
  103. package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
  104. package/dist/knob-contracts/code-diff-knobs.js +2 -0
  105. package/dist/knob-contracts/code-knobs.d.ts +1 -1
  106. package/dist/knob-contracts/code-knobs.js +2 -1
  107. package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
  108. package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
  109. package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
  110. package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
  111. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  112. package/dist/knob-contracts/field-knobs.js +5 -1
  113. package/dist/knob-contracts/filter-bar-knobs.d.ts +1 -1
  114. package/dist/knob-contracts/filter-bar-knobs.js +1 -2
  115. package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
  116. package/dist/knob-contracts/markdown-knobs.js +3 -1
  117. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  118. package/dist/knob-contracts/page-layout-knobs.js +2 -1
  119. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  120. package/dist/knob-contracts/popup-knobs.js +27 -0
  121. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  122. package/dist/knob-contracts/range-knobs.js +1 -0
  123. package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
  124. package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
  125. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  126. package/dist/knob-contracts/sidebar-knobs.js +27 -2
  127. package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
  128. package/dist/knob-contracts/spinner-knobs.js +1 -1
  129. package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
  130. package/dist/knob-contracts/stepper-knobs.js +4 -1
  131. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  132. package/dist/knob-contracts/switch-knobs.js +5 -0
  133. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  134. package/dist/knob-contracts/table-of-contents-knobs.js +2 -1
  135. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  136. package/dist/knob-contracts/tabs-knobs.js +1 -0
  137. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  138. package/dist/knob-contracts/tree-knobs.js +2 -1
  139. package/dist/lib/cn.js +0 -4
  140. package/dist/lib/contrast.d.ts +1 -0
  141. package/dist/lib/contrast.js +17 -5
  142. package/dist/lib/dropzone-validation.js +41 -28
  143. package/dist/lib/env-file.d.ts +1 -1
  144. package/dist/lib/env-file.js +5 -5
  145. package/dist/lib/motion.d.ts +4 -0
  146. package/dist/lib/motion.js +15 -0
  147. package/dist/lib/phone-input-format.js +1 -1
  148. package/dist/lib/trigger-detect.js +6 -8
  149. package/dist/markdown-block.js +2 -1
  150. package/dist/popup-parts.d.ts +8 -0
  151. package/dist/popup-parts.js +43 -0
  152. package/dist/source-badge.js +3 -1
  153. package/dist/styles/code.css +0 -2
  154. package/dist/styles/effects.css +6 -32
  155. package/dist/styles/index.css +10 -5
  156. package/dist/styles/recipes/accordion.css +9 -6
  157. package/dist/styles/recipes/action-bar.css +0 -15
  158. package/dist/styles/recipes/activity.css +5 -11
  159. package/dist/styles/recipes/alert.css +4 -4
  160. package/dist/styles/recipes/audio-recorder.css +15 -3
  161. package/dist/styles/recipes/audio-visualizer.css +13 -2
  162. package/dist/styles/recipes/avatar.css +78 -1
  163. package/dist/styles/recipes/badge.css +14 -4
  164. package/dist/styles/recipes/breadcrumb.css +77 -0
  165. package/dist/styles/recipes/button-group.css +5 -7
  166. package/dist/styles/recipes/button-states.css +66 -0
  167. package/dist/styles/recipes/button.css +24 -83
  168. package/dist/styles/recipes/calendar.css +17 -23
  169. package/dist/styles/recipes/card.css +2 -2
  170. package/dist/styles/recipes/chat-composer-attachment.css +9 -3
  171. package/dist/styles/recipes/chat-composer.css +7 -10
  172. package/dist/styles/recipes/chat-layout.css +35 -0
  173. package/dist/styles/recipes/chat-message-social.css +123 -0
  174. package/dist/styles/recipes/chat-message.css +157 -37
  175. package/dist/styles/recipes/choice.css +18 -21
  176. package/dist/styles/recipes/code-diff.css +46 -33
  177. package/dist/styles/recipes/code.css +36 -16
  178. package/dist/styles/recipes/collapsible.css +7 -2
  179. package/dist/styles/recipes/color-picker-details.css +3 -4
  180. package/dist/styles/recipes/color-picker-values.css +9 -8
  181. package/dist/styles/recipes/color-picker.css +11 -2
  182. package/dist/styles/recipes/context.css +3 -3
  183. package/dist/styles/recipes/dockable-panel.css +7 -4
  184. package/dist/styles/recipes/dropzone.css +7 -8
  185. package/dist/styles/recipes/dynamic-notification-input.css +8 -5
  186. package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
  187. package/dist/styles/recipes/dynamic-notification.css +40 -26
  188. package/dist/styles/recipes/emoji-picker.css +132 -0
  189. package/dist/styles/recipes/empty.css +5 -4
  190. package/dist/styles/recipes/environment-variables.css +4 -15
  191. package/dist/styles/recipes/field-parts.css +30 -24
  192. package/dist/styles/recipes/field.css +61 -26
  193. package/dist/styles/recipes/filter-bar.css +18 -31
  194. package/dist/styles/recipes/gradient-editor.css +18 -7
  195. package/dist/styles/recipes/infinite-canvas.css +0 -1
  196. package/dist/styles/recipes/inline-attachment.css +3 -3
  197. package/dist/styles/recipes/inline-citation.css +10 -4
  198. package/dist/styles/recipes/item.css +23 -9
  199. package/dist/styles/recipes/markdown-block.css +5 -4
  200. package/dist/styles/recipes/markdown.css +68 -26
  201. package/dist/styles/recipes/morphing-panel.css +8 -8
  202. package/dist/styles/recipes/page-layout.css +20 -12
  203. package/dist/styles/recipes/pagination.css +4 -5
  204. package/dist/styles/recipes/phone-input.css +12 -8
  205. package/dist/styles/recipes/popup-controls.css +166 -33
  206. package/dist/styles/recipes/popup-drawer.css +3 -14
  207. package/dist/styles/recipes/popup-lists.css +128 -27
  208. package/dist/styles/recipes/popup-motion.css +4 -45
  209. package/dist/styles/recipes/popup-toast.css +55 -26
  210. package/dist/styles/recipes/popup.css +139 -141
  211. package/dist/styles/recipes/range.css +54 -18
  212. package/dist/styles/recipes/resizable.css +2 -7
  213. package/dist/styles/recipes/rich-tooltip.css +24 -34
  214. package/dist/styles/recipes/scroll-area.css +8 -1
  215. package/dist/styles/recipes/sidebar-menu.css +149 -34
  216. package/dist/styles/recipes/sidebar-parts.css +154 -0
  217. package/dist/styles/recipes/sidebar.css +187 -128
  218. package/dist/styles/recipes/source-badge.css +1 -1
  219. package/dist/styles/recipes/spinner.css +8 -1
  220. package/dist/styles/recipes/stepper.css +31 -13
  221. package/dist/styles/recipes/switch.css +68 -23
  222. package/dist/styles/recipes/table-of-contents.css +9 -1
  223. package/dist/styles/recipes/table.css +14 -10
  224. package/dist/styles/recipes/tabs.css +17 -12
  225. package/dist/styles/recipes/task-list.css +7 -14
  226. package/dist/styles/recipes/thread-rail.css +42 -19
  227. package/dist/styles/recipes/timeline.css +2 -0
  228. package/dist/styles/recipes/toolbar.css +27 -25
  229. package/dist/styles/recipes/track-highlight.css +10 -7
  230. package/dist/styles/recipes/tree.css +31 -4
  231. package/dist/styles/recipes/user-ask-details.css +0 -1
  232. package/dist/styles/recipes/user-ask.css +4 -4
  233. package/dist/styles/theme.css +37 -16
  234. package/dist/task-list.d.ts +4 -1
  235. package/dist/task-list.js +24 -8
  236. package/dist/thread-rail.d.ts +1 -1
  237. package/dist/thread-rail.js +61 -19
  238. package/dist/transcript-divider.js +2 -1
  239. package/dist/ui/alert-dialog.js +1 -1
  240. package/dist/ui/alert.d.ts +3 -1
  241. package/dist/ui/alert.js +14 -8
  242. package/dist/ui/autocomplete.js +4 -2
  243. package/dist/ui/avatar.d.ts +8 -0
  244. package/dist/ui/avatar.js +23 -3
  245. package/dist/ui/badge.js +1 -1
  246. package/dist/ui/breadcrumb.d.ts +27 -0
  247. package/dist/ui/breadcrumb.js +88 -0
  248. package/dist/ui/button.d.ts +2 -1
  249. package/dist/ui/button.js +3 -3
  250. package/dist/ui/calendar.js +2 -2
  251. package/dist/ui/card.d.ts +3 -1
  252. package/dist/ui/card.js +12 -7
  253. package/dist/ui/checkbox.d.ts +2 -0
  254. package/dist/ui/code-diff.d.ts +1 -1
  255. package/dist/ui/code-diff.js +72 -28
  256. package/dist/ui/code.d.ts +5 -3
  257. package/dist/ui/code.js +28 -29
  258. package/dist/ui/collapsible.js +1 -1
  259. package/dist/ui/color-picker.d.ts +5 -2
  260. package/dist/ui/color-picker.js +85 -37
  261. package/dist/ui/combobox-disabled-selection.d.ts +1 -2
  262. package/dist/ui/combobox-disabled-selection.js +7 -9
  263. package/dist/ui/combobox.js +14 -6
  264. package/dist/ui/command.d.ts +8 -2
  265. package/dist/ui/command.js +30 -14
  266. package/dist/ui/context-menu.js +4 -1
  267. package/dist/ui/dialog.d.ts +2 -1
  268. package/dist/ui/dialog.js +4 -23
  269. package/dist/ui/dockable-panel-geometry.d.ts +1 -0
  270. package/dist/ui/dockable-panel-geometry.js +7 -1
  271. package/dist/ui/dockable-panel.d.ts +9 -6
  272. package/dist/ui/dockable-panel.js +63 -23
  273. package/dist/ui/drawer.js +1 -1
  274. package/dist/ui/dropdown-menu.js +5 -4
  275. package/dist/ui/dropzone.d.ts +2 -2
  276. package/dist/ui/dropzone.js +75 -16
  277. package/dist/ui/emoji-picker.d.ts +18 -0
  278. package/dist/ui/emoji-picker.js +136 -0
  279. package/dist/ui/empty.d.ts +3 -1
  280. package/dist/ui/empty.js +12 -7
  281. package/dist/ui/gradient-editor.d.ts +1 -1
  282. package/dist/ui/gradient-editor.js +37 -21
  283. package/dist/ui/hover-card.js +1 -1
  284. package/dist/ui/infinite-canvas.d.ts +3 -2
  285. package/dist/ui/infinite-canvas.js +63 -19
  286. package/dist/ui/input-group.d.ts +0 -3
  287. package/dist/ui/input-group.js +4 -14
  288. package/dist/ui/input-otp.d.ts +1 -1
  289. package/dist/ui/input-otp.js +32 -15
  290. package/dist/ui/input.js +4 -4
  291. package/dist/ui/item.d.ts +2 -1
  292. package/dist/ui/item.js +12 -8
  293. package/dist/ui/live-status.d.ts +7 -0
  294. package/dist/ui/live-status.js +17 -0
  295. package/dist/ui/markdown-elements.d.ts +6 -0
  296. package/dist/ui/markdown-elements.js +31 -1
  297. package/dist/ui/menubar.js +3 -3
  298. package/dist/ui/morphing-panel.js +3 -2
  299. package/dist/ui/native-select.d.ts +1 -1
  300. package/dist/ui/native-select.js +24 -11
  301. package/dist/ui/navigation-menu.js +1 -1
  302. package/dist/ui/page-layout.d.ts +2 -2
  303. package/dist/ui/page-layout.js +2 -4
  304. package/dist/ui/pagination.d.ts +7 -5
  305. package/dist/ui/pagination.js +19 -13
  306. package/dist/ui/phone-input.d.ts +1 -0
  307. package/dist/ui/phone-input.js +58 -60
  308. package/dist/ui/popover.js +1 -1
  309. package/dist/ui/radio-group.d.ts +2 -0
  310. package/dist/ui/resizable.d.ts +3 -2
  311. package/dist/ui/resizable.js +5 -3
  312. package/dist/ui/responsive-dialog.d.ts +1 -1
  313. package/dist/ui/responsive-dialog.js +4 -21
  314. package/dist/ui/rich-tooltip.d.ts +10 -4
  315. package/dist/ui/rich-tooltip.js +82 -34
  316. package/dist/ui/scroll-area.d.ts +1 -1
  317. package/dist/ui/scroll-area.js +43 -28
  318. package/dist/ui/select.js +1 -1
  319. package/dist/ui/sheet.js +1 -1
  320. package/dist/ui/sidebar-menu.d.ts +5 -1
  321. package/dist/ui/sidebar-menu.js +32 -34
  322. package/dist/ui/sidebar-provider.d.ts +10 -1
  323. package/dist/ui/sidebar-provider.js +39 -14
  324. package/dist/ui/sidebar-resize-rail.js +8 -4
  325. package/dist/ui/sidebar.d.ts +11 -6
  326. package/dist/ui/sidebar.js +47 -24
  327. package/dist/ui/slider.js +31 -22
  328. package/dist/ui/spinner.d.ts +3 -2
  329. package/dist/ui/spinner.js +3 -2
  330. package/dist/ui/stepper.d.ts +8 -1
  331. package/dist/ui/stepper.js +74 -25
  332. package/dist/ui/switch.d.ts +1 -0
  333. package/dist/ui/table-of-contents.d.ts +2 -2
  334. package/dist/ui/table-of-contents.js +20 -8
  335. package/dist/ui/table.js +4 -2
  336. package/dist/ui/textarea.d.ts +1 -1
  337. package/dist/ui/textarea.js +20 -9
  338. package/dist/ui/timeline.d.ts +3 -1
  339. package/dist/ui/timeline.js +17 -9
  340. package/dist/ui/toast.d.ts +11 -5
  341. package/dist/ui/toast.js +101 -55
  342. package/dist/ui/toolbar.d.ts +3 -2
  343. package/dist/ui/toolbar.js +4 -2
  344. package/dist/ui/tooltip.js +4 -21
  345. package/dist/ui/tree.js +121 -81
  346. package/dist/ui/trigger-menu.js +2 -1
  347. package/dist/user-ask.d.ts +10 -5
  348. package/dist/user-ask.js +32 -12
  349. package/package.json +5 -1
  350. package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
  351. package/dist/hooks/use-keyboard-navigation.js +0 -22
@@ -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,7 +12,6 @@ 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
15
  function releaseFileDrag(_view, event) {
17
16
  return event.dataTransfer ? Array.from(event.dataTransfer.types).includes("Files") : false;
18
17
  }
@@ -26,7 +25,7 @@ const fileTransferPassthrough = {
26
25
  return types.includes("Files") && !types.includes("text/plain");
27
26
  }
28
27
  };
29
- function ChatComposerEditor({ className, placeholder, style, extensions = [] }) {
28
+ function ChatComposerEditor({ className, placeholder, style, extensions = [], "aria-label": ariaLabel = "Message", "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }) {
30
29
  const input = useChatComposerContext();
31
30
  const mountRef = useRef(null);
32
31
  const viewRef = useRef(null);
@@ -36,6 +35,30 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
36
35
  }, [input]);
37
36
  const lastSerialized = useRef(input.value);
38
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);
39
62
  const listenersRef = useRef(/* @__PURE__ */ new Set());
40
63
  const keyHandlersRef = useRef(/* @__PURE__ */ new Set());
41
64
  const [api] = useState(() => ({
@@ -51,7 +74,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
51
74
  return () => {
52
75
  keyHandlersRef.current.delete(handler);
53
76
  };
54
- }
77
+ },
78
+ setHostAria
55
79
  }));
56
80
  const [initialExtensions] = useState(extensions);
57
81
  useEffect(() => {
@@ -68,17 +92,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
68
92
  inputRef.current.submit(extra);
69
93
  return true;
70
94
  };
95
+ const submitOnEnter = (editorState) => inputRef.current.submitKey === "enter" && submitMessage(editorState);
71
96
  const state = EditorState.create({
72
97
  schema,
73
98
  doc: docFromText(schema, inputRef.current.value),
74
99
  plugins: [
75
- new Plugin({ props: {
76
- handleKeyDown: (_view, event) => {
77
- for (const handler of keyHandlersRef.current) if (handler(event)) return true;
78
- return false;
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
79
107
  },
80
- handleDOMEvents: fileTransferPassthrough
81
- } }),
108
+ filterTransaction: (transaction) => !transaction.docChanged || !inputRef.current.isLocked
109
+ }),
82
110
  ...extensionPlugins,
83
111
  history(),
84
112
  keymap(extensionKeymap),
@@ -87,7 +115,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
87
115
  "Mod-y": redo,
88
116
  "Shift-Mod-z": redo,
89
117
  "Shift-Enter": splitBlock,
90
- [SUBMIT_KEY]: submitMessage
118
+ Enter: submitOnEnter,
119
+ "Mod-Enter": submitMessage
91
120
  }),
92
121
  keymap(baseKeymap)
93
122
  ]
@@ -95,11 +124,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
95
124
  const view = new EditorView(mount, {
96
125
  state,
97
126
  editable: () => !inputRef.current.isDisabled,
98
- attributes: {
99
- "aria-label": "Message",
100
- "aria-multiline": "true",
101
- role: "textbox"
102
- },
127
+ attributes: () => hostAttributesRef.current,
103
128
  dispatchTransaction(transaction) {
104
129
  const next = view.state.apply(transaction);
105
130
  view.updateState(next);
@@ -118,6 +143,13 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
118
143
  viewRef.current = null;
119
144
  };
120
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]);
121
153
  useEffect(() => {
122
154
  const view = viewRef.current;
123
155
  if (!view) return;
@@ -132,16 +164,9 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
132
164
  }));
133
165
  }, [input.value]);
134
166
  useEffect(() => {
135
- viewRef.current?.setProps({
136
- editable: () => !input.isDisabled,
137
- attributes: {
138
- "aria-label": "Message",
139
- "aria-multiline": "true",
140
- "aria-disabled": String(input.isDisabled),
141
- role: "textbox"
142
- }
143
- });
144
- }, [input.isDisabled]);
167
+ hostAttributesRef.current = hostAttributes;
168
+ viewRef.current?.setProps({});
169
+ }, [hostAttributes]);
145
170
  return /* @__PURE__ */ jsxs("div", {
146
171
  "data-control-ui": "chat-composer-editor",
147
172
  "data-control-family": "chat-composer",
@@ -154,7 +179,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
154
179
  "data-control-ui": "chat-composer-editor",
155
180
  "data-control-family": "chat-composer",
156
181
  "data-slot": "placeholder",
157
- 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)]",
158
183
  children: placeholder
159
184
  }) : null,
160
185
  /* @__PURE__ */ jsx("div", {
@@ -162,19 +187,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
162
187
  "data-control-ui": "chat-composer-editor",
163
188
  "data-control-family": "chat-composer",
164
189
  "data-slot": "editor",
165
- 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")
166
191
  }),
167
192
  mounted ? null : /* @__PURE__ */ jsx("textarea", {
168
193
  "data-control-ui": "chat-composer-editor",
169
194
  "data-control-family": "chat-composer",
170
195
  "data-slot": "fallback",
171
- "aria-label": "Message",
196
+ "aria-label": ariaLabelledby ? void 0 : ariaLabel,
197
+ "aria-labelledby": ariaLabelledby,
198
+ "aria-describedby": ariaDescribedby,
172
199
  defaultValue: input.value,
173
200
  readOnly: true,
174
201
  disabled: input.isDisabled,
175
202
  rows: 2,
176
203
  placeholder,
177
- 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)]"
178
205
  }),
179
206
  mounted ? initialExtensions.map((extension) => extension.Overlay ? /* @__PURE__ */ jsx(extension.Overlay, { editor: api }, extension.name) : null) : null
180
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, allowEmptySubmit, 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
@@ -1,12 +1,13 @@
1
1
  "use client";
2
- import { hasSkinAdornment, skinAdornment } from "./skin.js";
3
2
  import { cn } from "./lib/cn.js";
3
+ import { hasSkinAdornment, skinAdornment } from "./skin.js";
4
4
  import { useSkin } from "./skin-provider.js";
5
5
  import { Button } from "./ui/button.js";
6
+ import { Spinner } from "./ui/spinner.js";
6
7
  import { useChatComposer } from "./hooks/use-chat-composer.js";
7
- import { useKeyboardNavigation } from "./hooks/use-keyboard-navigation.js";
8
- import { createContext, useContext } from "react";
9
- import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { createContext, useContext, useEffect, useRef } from "react";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
+ import { Square } from "lucide-react";
10
11
  //#region src/chat-composer.tsx
11
12
  const ChatComposerContext = createContext(null);
12
13
  function useChatComposerContext() {
@@ -14,7 +15,7 @@ function useChatComposerContext() {
14
15
  if (!context) throw new Error("ChatComposer compound components must be rendered inside <ChatComposer>.");
15
16
  return context;
16
17
  }
17
- function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, className, children, ...props }) {
18
+ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, submitKey = "enter", className, children, ...props }) {
18
19
  const skin = useSkin();
19
20
  const input = useChatComposer({
20
21
  value,
@@ -25,6 +26,7 @@ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "i
25
26
  density,
26
27
  disabled,
27
28
  allowEmptySubmit,
29
+ submitKey,
28
30
  trackSends: hasSkinAdornment(skin, "chat-composer", "send-layer")
29
31
  });
30
32
  const sendLayer = skinAdornment(skin, "chat-composer", "send-layer", { sendCount: input.sendCount });
@@ -49,9 +51,7 @@ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "i
49
51
  }
50
52
  function ChatComposerShell({ className, ...props }) {
51
53
  const input = useChatComposerContext();
52
- const keyboardNavigation = useKeyboardNavigation();
53
54
  return /* @__PURE__ */ jsx("div", {
54
- "data-keyboard-navigation": keyboardNavigation ? "" : void 0,
55
55
  "data-control-ui": "chat-composer",
56
56
  "data-control-family": "chat-composer",
57
57
  "data-slot": "shell",
@@ -71,8 +71,17 @@ function ChatComposerAccent({ className, ...props }) {
71
71
  ...props
72
72
  });
73
73
  }
74
- function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown, ...props }) {
74
+ function ChatComposerTextarea({ className, rows, disabled, readOnly, onChange, onKeyDown, ref, ...props }) {
75
75
  const input = useChatComposerContext();
76
+ const textareaRef = useRef(null);
77
+ const { registerInsertionTarget } = input;
78
+ useEffect(() => registerInsertionTarget((text) => {
79
+ const textarea = textareaRef.current;
80
+ if (!textarea) return;
81
+ textarea.focus();
82
+ textarea.setRangeText(text, textarea.selectionStart, textarea.selectionEnd, "end");
83
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
84
+ }), [registerInsertionTarget]);
76
85
  function handleChange(event) {
77
86
  onChange?.(event);
78
87
  if (!event.defaultPrevented) input.setValue(event.currentTarget.value);
@@ -81,7 +90,12 @@ function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown,
81
90
  onKeyDown?.(event);
82
91
  input.handleKeyDown(event);
83
92
  }
93
+ function attachTextarea(node) {
94
+ textareaRef.current = node;
95
+ assignRef(ref, node);
96
+ }
84
97
  return /* @__PURE__ */ jsx("textarea", {
98
+ ref: attachTextarea,
85
99
  "data-control-ui": "chat-composer",
86
100
  "data-control-family": "chat-composer",
87
101
  "data-slot": "textarea",
@@ -91,8 +105,10 @@ function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown,
91
105
  onChange: handleChange,
92
106
  onKeyDown: handleKeyDown,
93
107
  disabled: disabled || input.isDisabled,
108
+ readOnly: readOnly || input.isLocked,
109
+ "aria-busy": input.isLocked || void 0,
94
110
  rows: rows ?? input.rows,
95
- className: cn("field-sizing-content min-h-16 max-h-[40dvh] w-full resize-none outline-none px-[var(--padding-x)] py-[var(--padding-y)] disabled:cursor-not-allowed", className)
111
+ className: cn("field-sizing-content min-h-16 max-h-[40dvh] w-full resize-none px-[var(--padding-x)] py-[var(--padding-y)] disabled:cursor-not-allowed", className)
96
112
  });
97
113
  }
98
114
  function ChatComposerToolbar({ className, ...props }) {
@@ -122,20 +138,44 @@ function ChatComposerFooter({ className, ...props }) {
122
138
  ...props
123
139
  });
124
140
  }
125
- function ChatComposerSubmit({ className, disabled, children = "Send", ...props }) {
141
+ function ChatComposerSubmit({ className, disabled, iconOnly = false, children = "Send", onStop, stopLabel = "Stop response", stopIcon = /* @__PURE__ */ jsx(Square, {
142
+ "aria-hidden": "true",
143
+ className: "fill-current"
144
+ }), onClick, "aria-label": ariaLabel, ...props }) {
126
145
  const input = useChatComposerContext();
146
+ const isStop = input.isLocked && onStop !== void 0;
147
+ const isBusy = input.isLocked && !isStop;
148
+ function handleClick(event) {
149
+ onClick?.(event);
150
+ if (isStop && !event.defaultPrevented) onStop();
151
+ }
152
+ function content() {
153
+ if (isStop) return iconOnly ? stopIcon : stopLabel;
154
+ if (!isBusy) return children;
155
+ if (iconOnly) return /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true" });
156
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true" }), children] });
157
+ }
127
158
  return /* @__PURE__ */ jsx(Button, {
128
159
  "data-control-ui": "chat-composer",
129
160
  "data-slot": "submit",
130
- type: "submit",
161
+ "data-stop": isStop ? "true" : void 0,
162
+ type: isStop ? "button" : "submit",
131
163
  variant: "solid",
132
164
  tone: "primary",
133
165
  size: "xs",
134
- disabled: disabled || !input.canSubmit,
166
+ iconOnly,
167
+ disabled: isStop ? disabled : disabled || !input.canSubmit,
168
+ "aria-busy": isBusy || void 0,
169
+ "aria-label": isStop && iconOnly ? stopLabel : ariaLabel,
170
+ onClick: handleClick,
135
171
  className,
136
172
  ...props,
137
- children
173
+ children: content()
138
174
  });
139
175
  }
176
+ function assignRef(ref, node) {
177
+ if (typeof ref === "function") ref(node);
178
+ else if (ref) ref.current = node;
179
+ }
140
180
  //#endregion
141
181
  export { ChatComposer, ChatComposerAccent, ChatComposerFooter, ChatComposerShell, ChatComposerSubmit, ChatComposerTextarea, ChatComposerToolbar, ChatComposerTools, useChatComposerContext };
@@ -1,3 +1,4 @@
1
+ import { RenderProp } from "./control-props.js";
1
2
  import { Button } from "./ui/button.js";
2
3
  import { ChatLayoutKnobStyle } from "./knob-contracts/chat-layout-knobs.js";
3
4
  import { CSSProperties, ComponentProps, ReactNode } from "react";
@@ -8,6 +9,24 @@ export type ChatLayoutProps = Omit<ComponentProps<"section">, "style"> & {
8
9
  style?: CSSProperties & ChatLayoutKnobStyle;
9
10
  };
10
11
  export declare function ChatLayout({ children, chrome, className, ...props }: ChatLayoutProps): import("react").JSX.Element;
12
+ export type ChatLayoutHeaderProps = Omit<ComponentProps<"header">, "style"> & {
13
+ style?: CSSProperties & ChatLayoutKnobStyle;
14
+ };
15
+ export declare function ChatLayoutHeader({ className, ...props }: ChatLayoutHeaderProps): import("react").JSX.Element;
16
+ export type ChatLayoutTitleProps = Omit<ComponentProps<"div">, "style"> & {
17
+ render?: RenderProp<ComponentProps<"div">>;
18
+ style?: CSSProperties & ChatLayoutKnobStyle;
19
+ };
20
+ /** Renders a `div`; pass `render={(props) => <h2 {...props} />}` for the heading level the page needs. */
21
+ export declare function ChatLayoutTitle({ render, className, ...props }: ChatLayoutTitleProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
22
+ export type ChatLayoutDescriptionProps = Omit<ComponentProps<"p">, "style"> & {
23
+ style?: CSSProperties & ChatLayoutKnobStyle;
24
+ };
25
+ export declare function ChatLayoutDescription({ className, ...props }: ChatLayoutDescriptionProps): import("react").JSX.Element;
26
+ export type ChatLayoutActionsProps = Omit<ComponentProps<"div">, "style"> & {
27
+ style?: CSSProperties & ChatLayoutKnobStyle;
28
+ };
29
+ export declare function ChatLayoutActions({ className, ...props }: ChatLayoutActionsProps): import("react").JSX.Element;
11
30
  export type ChatThreadProps = ComponentProps<"div"> & {
12
31
  composer?: ReactNode;
13
32
  };