@k2b/ui 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +1 -1
  2. package/dist/base.css +147 -2
  3. package/dist/browser/actions/ContextMenu.js +35 -4
  4. package/dist/browser/actions/ContextMenu.js.map +1 -1
  5. package/dist/browser/actions/DragAndDrop.js +89 -0
  6. package/dist/browser/actions/DragAndDrop.js.map +1 -0
  7. package/dist/browser/actions/Dropdown.js +184 -158
  8. package/dist/browser/actions/Dropdown.js.map +1 -1
  9. package/dist/browser/actions/GestureMenu.js +95 -139
  10. package/dist/browser/actions/GestureMenu.js.map +1 -1
  11. package/dist/browser/actions/Toolbar.js +379 -48
  12. package/dist/browser/actions/Toolbar.js.map +1 -1
  13. package/dist/browser/actions/drag-and-drop-messages.js +75 -0
  14. package/dist/browser/actions/drag-and-drop-messages.js.map +1 -0
  15. package/dist/browser/actions/drag-and-drop.js +692 -0
  16. package/dist/browser/actions/drag-and-drop.js.map +1 -0
  17. package/dist/browser/actions/index.js +1 -0
  18. package/dist/browser/actions/index.js.map +1 -1
  19. package/dist/browser/actions/menu-rows.js +15 -0
  20. package/dist/browser/actions/menu-rows.js.map +1 -0
  21. package/dist/browser/actions/menu-sheet.js +154 -0
  22. package/dist/browser/actions/menu-sheet.js.map +1 -0
  23. package/dist/browser/actions/toolbar-messages.js +20 -0
  24. package/dist/browser/actions/toolbar-messages.js.map +1 -0
  25. package/dist/browser/chart-inspection.js +3 -0
  26. package/dist/browser/chart-inspection.js.map +1 -0
  27. package/dist/browser/chat/ChatComposer.js +714 -410
  28. package/dist/browser/chat/ChatComposer.js.map +1 -1
  29. package/dist/browser/chat/ChatDecision.js +778 -0
  30. package/dist/browser/chat/ChatDecision.js.map +1 -0
  31. package/dist/browser/chat/ChatPrimitives.js +34 -28
  32. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  33. package/dist/browser/chat/ChatTimeline.js.map +1 -1
  34. package/dist/browser/chat/chat-messages.js +25 -1
  35. package/dist/browser/chat/chat-messages.js.map +1 -1
  36. package/dist/browser/chat/command-search.js +13 -3
  37. package/dist/browser/chat/command-search.js.map +1 -1
  38. package/dist/browser/chat/index.js +8 -1
  39. package/dist/browser/chat/index.js.map +1 -1
  40. package/dist/browser/chat/reaction-people.js +15 -0
  41. package/dist/browser/chat/reaction-people.js.map +1 -0
  42. package/dist/browser/chat/types.js.map +1 -1
  43. package/dist/browser/content/Calendar.js +3 -3
  44. package/dist/browser/content/Calendar.js.map +1 -1
  45. package/dist/browser/content/Chart.js +60 -86
  46. package/dist/browser/content/Chart.js.map +1 -1
  47. package/dist/browser/content/ChartSnapshotView.js +7 -10
  48. package/dist/browser/content/ChartSnapshotView.js.map +1 -1
  49. package/dist/browser/content/FileBrowser.js +2 -2
  50. package/dist/browser/content/FileBrowser.js.map +1 -1
  51. package/dist/browser/content/FileTree.js +42 -36
  52. package/dist/browser/content/FileTree.js.map +1 -1
  53. package/dist/browser/content/FileTypeIcon.js +72 -0
  54. package/dist/browser/content/FileTypeIcon.js.map +1 -0
  55. package/dist/browser/content/FileView.js +2 -2
  56. package/dist/browser/content/FileView.js.map +1 -1
  57. package/dist/browser/content/MessageRow.js +330 -448
  58. package/dist/browser/content/MessageRow.js.map +1 -1
  59. package/dist/browser/content/ReactionBar.js +419 -0
  60. package/dist/browser/content/ReactionBar.js.map +1 -0
  61. package/dist/browser/content/ReferenceChip.js +162 -0
  62. package/dist/browser/content/ReferenceChip.js.map +1 -0
  63. package/dist/browser/content/calendar-pointer.js +9 -0
  64. package/dist/browser/content/calendar-pointer.js.map +1 -1
  65. package/dist/browser/content/calendar-time-view.js +6 -3
  66. package/dist/browser/content/calendar-time-view.js.map +1 -1
  67. package/dist/browser/content/chart-inspection-core.js +557 -0
  68. package/dist/browser/content/chart-inspection-core.js.map +1 -0
  69. package/dist/browser/content/chart-inspection.js +10 -445
  70. package/dist/browser/content/chart-inspection.js.map +1 -1
  71. package/dist/browser/content/chart-line-gaps.js +1 -1
  72. package/dist/browser/content/chart-line-gaps.js.map +1 -1
  73. package/dist/browser/content/chart-snapshot.js +1 -1
  74. package/dist/browser/content/chart-snapshot.js.map +1 -1
  75. package/dist/browser/content/index.js +3 -0
  76. package/dist/browser/content/index.js.map +1 -1
  77. package/dist/browser/feedback/dialog-core.js +135 -23
  78. package/dist/browser/feedback/dialog-core.js.map +1 -1
  79. package/dist/browser/index.js +1 -0
  80. package/dist/browser/index.js.map +1 -1
  81. package/dist/browser/input-mode.js +40 -0
  82. package/dist/browser/input-mode.js.map +1 -0
  83. package/dist/browser/inputs/FileDropTarget.js +24 -9
  84. package/dist/browser/inputs/FileDropTarget.js.map +1 -1
  85. package/dist/browser/inputs/FileInputs.js +2 -2
  86. package/dist/browser/inputs/FileInputs.js.map +1 -1
  87. package/dist/browser/inputs/SelectChip.js +9 -0
  88. package/dist/browser/inputs/SelectChip.js.map +1 -1
  89. package/dist/browser/inputs/emoji/EmojiPicker.js +13 -0
  90. package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -1
  91. package/dist/browser/inputs/file-drop.js.map +1 -1
  92. package/dist/browser/layout/AppOverview.js +1 -1
  93. package/dist/browser/layout/AppOverview.js.map +1 -1
  94. package/dist/browser/layout/AppWorkspace.js +644 -445
  95. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  96. package/dist/browser/layout/DetailPanel.js +283 -197
  97. package/dist/browser/layout/DetailPanel.js.map +1 -1
  98. package/dist/browser/layout/Discussion.js +233 -185
  99. package/dist/browser/layout/Discussion.js.map +1 -1
  100. package/dist/browser/layout/PanelDialog.js +88 -86
  101. package/dist/browser/layout/PanelDialog.js.map +1 -1
  102. package/dist/browser/layout/ResizeSeparator.js +81 -0
  103. package/dist/browser/layout/ResizeSeparator.js.map +1 -0
  104. package/dist/browser/layout/SidebarSwitcher.js +9 -2
  105. package/dist/browser/layout/SidebarSwitcher.js.map +1 -1
  106. package/dist/browser/layout/TabBar.js +15 -12
  107. package/dist/browser/layout/TabBar.js.map +1 -1
  108. package/dist/browser/layout/TableOfContents.js +10 -2
  109. package/dist/browser/layout/TableOfContents.js.map +1 -1
  110. package/dist/browser/layout/app-workspace-controller.js +130 -17
  111. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  112. package/dist/browser/layout/detail-panel-messages.js +35 -0
  113. package/dist/browser/layout/detail-panel-messages.js.map +1 -0
  114. package/dist/browser/layout/index.js +3 -1
  115. package/dist/browser/layout/index.js.map +1 -1
  116. package/dist/browser/layout/resize-separator.js +90 -0
  117. package/dist/browser/layout/resize-separator.js.map +1 -0
  118. package/dist/browser/layout/workspace-messages.js +12 -2
  119. package/dist/browser/layout/workspace-messages.js.map +1 -1
  120. package/dist/browser/surfaces/Avatar.js +50 -20
  121. package/dist/browser/surfaces/Avatar.js.map +1 -1
  122. package/dist/browser/surfaces/AvatarStack.js +406 -0
  123. package/dist/browser/surfaces/AvatarStack.js.map +1 -0
  124. package/dist/browser/surfaces/LinkCard.js +1 -1
  125. package/dist/browser/surfaces/LinkCard.js.map +1 -1
  126. package/dist/browser/surfaces/Placeholder.js +14 -3
  127. package/dist/browser/surfaces/Placeholder.js.map +1 -1
  128. package/dist/browser/surfaces/StatCell.js +23 -0
  129. package/dist/browser/surfaces/StatCell.js.map +1 -1
  130. package/dist/browser/surfaces/avatar-messages.js +8 -2
  131. package/dist/browser/surfaces/avatar-messages.js.map +1 -1
  132. package/dist/browser/surfaces/index.js +1 -0
  133. package/dist/browser/surfaces/index.js.map +1 -1
  134. package/dist/browser/widgets/Widget.js +1 -1
  135. package/dist/browser/widgets/Widget.js.map +1 -1
  136. package/dist/ssr/actions/ContextMenu.js +26 -3
  137. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  138. package/dist/ssr/actions/DragAndDrop.js +63 -0
  139. package/dist/ssr/actions/DragAndDrop.js.map +1 -0
  140. package/dist/ssr/actions/Dropdown.js +140 -72
  141. package/dist/ssr/actions/Dropdown.js.map +1 -1
  142. package/dist/ssr/actions/GestureMenu.js +76 -107
  143. package/dist/ssr/actions/GestureMenu.js.map +1 -1
  144. package/dist/ssr/actions/Toolbar.js +294 -14
  145. package/dist/ssr/actions/Toolbar.js.map +1 -1
  146. package/dist/ssr/actions/drag-and-drop-messages.js +75 -0
  147. package/dist/ssr/actions/drag-and-drop-messages.js.map +1 -0
  148. package/dist/ssr/actions/drag-and-drop.js +692 -0
  149. package/dist/ssr/actions/drag-and-drop.js.map +1 -0
  150. package/dist/ssr/actions/index.js +1 -0
  151. package/dist/ssr/actions/index.js.map +1 -1
  152. package/dist/ssr/actions/menu-rows.js +15 -0
  153. package/dist/ssr/actions/menu-rows.js.map +1 -0
  154. package/dist/ssr/actions/menu-sheet.js +138 -0
  155. package/dist/ssr/actions/menu-sheet.js.map +1 -0
  156. package/dist/ssr/actions/toolbar-messages.js +20 -0
  157. package/dist/ssr/actions/toolbar-messages.js.map +1 -0
  158. package/dist/ssr/chart-inspection.js +3 -0
  159. package/dist/ssr/chart-inspection.js.map +1 -0
  160. package/dist/ssr/chat/ChatComposer.js +393 -102
  161. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  162. package/dist/ssr/chat/ChatDecision.js +465 -0
  163. package/dist/ssr/chat/ChatDecision.js.map +1 -0
  164. package/dist/ssr/chat/ChatPrimitives.js +2 -2
  165. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  166. package/dist/ssr/chat/ChatTimeline.js.map +1 -1
  167. package/dist/ssr/chat/chat-messages.js +25 -1
  168. package/dist/ssr/chat/chat-messages.js.map +1 -1
  169. package/dist/ssr/chat/command-search.js +13 -3
  170. package/dist/ssr/chat/command-search.js.map +1 -1
  171. package/dist/ssr/chat/index.js +8 -1
  172. package/dist/ssr/chat/index.js.map +1 -1
  173. package/dist/ssr/chat/reaction-people.js +15 -0
  174. package/dist/ssr/chat/reaction-people.js.map +1 -0
  175. package/dist/ssr/chat/types.js.map +1 -1
  176. package/dist/ssr/content/Calendar.js +3 -3
  177. package/dist/ssr/content/Calendar.js.map +1 -1
  178. package/dist/ssr/content/Chart.js +25 -38
  179. package/dist/ssr/content/Chart.js.map +1 -1
  180. package/dist/ssr/content/ChartSnapshotView.js +7 -10
  181. package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
  182. package/dist/ssr/content/FileBrowser.js +2 -2
  183. package/dist/ssr/content/FileBrowser.js.map +1 -1
  184. package/dist/ssr/content/FileTree.js +21 -14
  185. package/dist/ssr/content/FileTree.js.map +1 -1
  186. package/dist/ssr/content/FileTypeIcon.js +50 -0
  187. package/dist/ssr/content/FileTypeIcon.js.map +1 -0
  188. package/dist/ssr/content/FileView.js +2 -2
  189. package/dist/ssr/content/FileView.js.map +1 -1
  190. package/dist/ssr/content/MessageRow.js +98 -152
  191. package/dist/ssr/content/MessageRow.js.map +1 -1
  192. package/dist/ssr/content/ReactionBar.js +304 -0
  193. package/dist/ssr/content/ReactionBar.js.map +1 -0
  194. package/dist/ssr/content/ReferenceChip.js +121 -0
  195. package/dist/ssr/content/ReferenceChip.js.map +1 -0
  196. package/dist/ssr/content/calendar-pointer.js +9 -0
  197. package/dist/ssr/content/calendar-pointer.js.map +1 -1
  198. package/dist/ssr/content/calendar-time-view.js +6 -3
  199. package/dist/ssr/content/calendar-time-view.js.map +1 -1
  200. package/dist/ssr/content/chart-inspection-core.js +557 -0
  201. package/dist/ssr/content/chart-inspection-core.js.map +1 -0
  202. package/dist/ssr/content/chart-inspection.js +10 -445
  203. package/dist/ssr/content/chart-inspection.js.map +1 -1
  204. package/dist/ssr/content/chart-line-gaps.js +1 -1
  205. package/dist/ssr/content/chart-line-gaps.js.map +1 -1
  206. package/dist/ssr/content/chart-snapshot.js +1 -1
  207. package/dist/ssr/content/chart-snapshot.js.map +1 -1
  208. package/dist/ssr/content/index.js +3 -0
  209. package/dist/ssr/content/index.js.map +1 -1
  210. package/dist/ssr/feedback/dialog-core.js +135 -23
  211. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  212. package/dist/ssr/index.js +1 -0
  213. package/dist/ssr/index.js.map +1 -1
  214. package/dist/ssr/input-mode.js +40 -0
  215. package/dist/ssr/input-mode.js.map +1 -0
  216. package/dist/ssr/inputs/FileDropTarget.js +17 -5
  217. package/dist/ssr/inputs/FileDropTarget.js.map +1 -1
  218. package/dist/ssr/inputs/FileInputs.js +2 -2
  219. package/dist/ssr/inputs/FileInputs.js.map +1 -1
  220. package/dist/ssr/inputs/SelectChip.js +9 -0
  221. package/dist/ssr/inputs/SelectChip.js.map +1 -1
  222. package/dist/ssr/inputs/emoji/EmojiPicker.js +12 -0
  223. package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -1
  224. package/dist/ssr/inputs/file-drop.js.map +1 -1
  225. package/dist/ssr/layout/AppOverview.js +1 -1
  226. package/dist/ssr/layout/AppOverview.js.map +1 -1
  227. package/dist/ssr/layout/AppWorkspace.js +272 -95
  228. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  229. package/dist/ssr/layout/DetailPanel.js +112 -58
  230. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  231. package/dist/ssr/layout/Discussion.js +63 -36
  232. package/dist/ssr/layout/Discussion.js.map +1 -1
  233. package/dist/ssr/layout/PanelDialog.js +7 -6
  234. package/dist/ssr/layout/PanelDialog.js.map +1 -1
  235. package/dist/ssr/layout/ResizeSeparator.js +45 -0
  236. package/dist/ssr/layout/ResizeSeparator.js.map +1 -0
  237. package/dist/ssr/layout/SidebarSwitcher.js +9 -2
  238. package/dist/ssr/layout/SidebarSwitcher.js.map +1 -1
  239. package/dist/ssr/layout/TabBar.js +1 -1
  240. package/dist/ssr/layout/TabBar.js.map +1 -1
  241. package/dist/ssr/layout/TableOfContents.js +10 -2
  242. package/dist/ssr/layout/TableOfContents.js.map +1 -1
  243. package/dist/ssr/layout/app-workspace-controller.js +130 -17
  244. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  245. package/dist/ssr/layout/detail-panel-messages.js +35 -0
  246. package/dist/ssr/layout/detail-panel-messages.js.map +1 -0
  247. package/dist/ssr/layout/index.js +3 -1
  248. package/dist/ssr/layout/index.js.map +1 -1
  249. package/dist/ssr/layout/resize-separator.js +90 -0
  250. package/dist/ssr/layout/resize-separator.js.map +1 -0
  251. package/dist/ssr/layout/workspace-messages.js +12 -2
  252. package/dist/ssr/layout/workspace-messages.js.map +1 -1
  253. package/dist/ssr/surfaces/Avatar.js +20 -2
  254. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  255. package/dist/ssr/surfaces/AvatarStack.js +286 -0
  256. package/dist/ssr/surfaces/AvatarStack.js.map +1 -0
  257. package/dist/ssr/surfaces/LinkCard.js +1 -1
  258. package/dist/ssr/surfaces/LinkCard.js.map +1 -1
  259. package/dist/ssr/surfaces/Placeholder.js +4 -2
  260. package/dist/ssr/surfaces/Placeholder.js.map +1 -1
  261. package/dist/ssr/surfaces/StatCell.js +23 -0
  262. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  263. package/dist/ssr/surfaces/avatar-messages.js +8 -2
  264. package/dist/ssr/surfaces/avatar-messages.js.map +1 -1
  265. package/dist/ssr/surfaces/index.js +1 -0
  266. package/dist/ssr/surfaces/index.js.map +1 -1
  267. package/dist/ssr/widgets/Widget.js +1 -1
  268. package/dist/ssr/widgets/Widget.js.map +1 -1
  269. package/dist/styles.css +1 -1
  270. package/dist/types/actions/ContextMenu.d.ts +9 -0
  271. package/dist/types/actions/DragAndDrop.d.ts +29 -0
  272. package/dist/types/actions/Dropdown.d.ts +12 -5
  273. package/dist/types/actions/GestureMenu.d.ts +6 -4
  274. package/dist/types/actions/Toolbar.d.ts +20 -0
  275. package/dist/types/actions/drag-and-drop-messages.d.ts +35 -0
  276. package/dist/types/actions/drag-and-drop.d.ts +36 -0
  277. package/dist/types/actions/index.d.ts +3 -1
  278. package/dist/types/actions/menu-rows.d.ts +5 -0
  279. package/dist/types/actions/menu-sheet.d.ts +22 -0
  280. package/dist/types/actions/toolbar-messages.d.ts +8 -0
  281. package/dist/types/chart-inspection.d.ts +2 -0
  282. package/dist/types/chat/ChatComposer.d.ts +26 -1
  283. package/dist/types/chat/ChatDecision.d.ts +133 -0
  284. package/dist/types/chat/ChatPrimitives.d.ts +4 -1
  285. package/dist/types/chat/ChatTimeline.d.ts +2 -1
  286. package/dist/types/chat/chat-messages.d.ts +13 -2
  287. package/dist/types/chat/command-search.d.ts +5 -0
  288. package/dist/types/chat/index.d.ts +25 -1
  289. package/dist/types/chat/reaction-people.d.ts +9 -0
  290. package/dist/types/chat/types.d.ts +54 -1
  291. package/dist/types/content/Chart.d.ts +11 -5
  292. package/dist/types/content/FileTypeIcon.d.ts +27 -0
  293. package/dist/types/content/MessageRow.d.ts +3 -10
  294. package/dist/types/content/ReactionBar.d.ts +32 -0
  295. package/dist/types/content/ReferenceChip.d.ts +49 -0
  296. package/dist/types/content/calendar-pointer.d.ts +6 -0
  297. package/dist/types/content/chart-inspection-core.d.ts +61 -0
  298. package/dist/types/content/chart-inspection.d.ts +4 -20
  299. package/dist/types/content/chart-snapshot.d.ts +1 -1
  300. package/dist/types/content/index.d.ts +5 -1
  301. package/dist/types/feedback/dialog-core.d.ts +16 -1
  302. package/dist/types/index.d.ts +1 -0
  303. package/dist/types/input-mode.d.ts +7 -0
  304. package/dist/types/inputs/FileDropTarget.d.ts +6 -3
  305. package/dist/types/inputs/SelectChip.d.ts +2 -0
  306. package/dist/types/inputs/file-drop.d.ts +2 -0
  307. package/dist/types/layout/AppWorkspace.d.ts +5 -3
  308. package/dist/types/layout/DetailPanel.d.ts +11 -0
  309. package/dist/types/layout/Discussion.d.ts +8 -0
  310. package/dist/types/layout/ResizeSeparator.d.ts +9 -0
  311. package/dist/types/layout/TabBar.d.ts +7 -0
  312. package/dist/types/layout/TableOfContents.d.ts +2 -0
  313. package/dist/types/layout/app-workspace-controller.d.ts +2 -0
  314. package/dist/types/layout/detail-panel-messages.d.ts +15 -0
  315. package/dist/types/layout/index.d.ts +3 -1
  316. package/dist/types/layout/resize-separator.d.ts +18 -0
  317. package/dist/types/layout/workspace-messages.d.ts +5 -0
  318. package/dist/types/surfaces/Avatar.d.ts +12 -0
  319. package/dist/types/surfaces/AvatarStack.d.ts +41 -0
  320. package/dist/types/surfaces/StatCell.d.ts +6 -0
  321. package/dist/types/surfaces/avatar-messages.d.ts +3 -0
  322. package/dist/types/surfaces/index.d.ts +3 -1
  323. package/package.json +7 -2
@@ -18,41 +18,43 @@ var _tmpl$ = ["<span><strong", ">", "</strong></span>"],
18
18
  _tmpl$1 = ["<div class=\"k2b-chat-composer__input\" role=\"group\"", ">", "<textarea class=\"", "\" rows=\"1\"", "", "></textarea>", "</div>"],
19
19
  _tmpl$10 = ["<span class=\"", "\">", "</span>"],
20
20
  _tmpl$11 = ["<small", ">", "</small>"],
21
- _tmpl$12 = ["<button class=\"k2b-chat-composer__command\"", " type=\"button\" role=\"option\" tabindex=\"-1\"", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-chat-composer__command-text\"><strong>", "</strong>", "</span></button>"],
22
- _tmpl$13 = ["<b>", "</b>"],
23
- _tmpl$14 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "</div></div>"],
24
- _tmpl$15 = ["<div role=\"status\" class=\"k2b-chat-composer__loading\"><i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i><strong>", "</strong></div>"],
25
- _tmpl$16 = ["<div role=\"status\"><i class=\"ti ti-alert-circle\"></i> ", "</div>"],
26
- _tmpl$17 = ["<div role=\"status\">", "</div>"],
27
- _tmpl$18 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "", "", "", "</div></div>"],
28
- _tmpl$19 = ["<div class=\"k2b-chat-composer-slot\"", "><div style=\"", "\"", ">", "</div>", "", "</div>"],
29
- _tmpl$20 = ["<div class=\"k2b-chat-composer__hint\" aria-live=\"polite\">", "</div>"],
30
- _tmpl$21 = ["<div class=\"k2b-chat-composer__attachments\" role=\"list\"", " tabindex=\"0\">", "</div>"],
31
- _tmpl$22 = ["<div class=\"k2b-chat-composer__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i>", "</div>"],
32
- _tmpl$23 = ["<div class=\"k2b-chat-composer__row\">", "", "<div class=\"k2b-chat-composer__field\">", "<div class=\"k2b-chat-composer__field-tools\">", "", "", "</div></div>", "</div>"],
33
- _tmpl$24 = ["<div class=\"k2b-chat-composer-shell\">", "<section class=\"", "\"", " role=\"group\"", ">", "", "", "", "</section><div class=\"k2b-sr-only\" role=\"status\" aria-live=\"polite\">", "</div><div class=\"k2b-chat-composer__status\" role=\"status\">", "", "</div></div>"],
34
- _tmpl$25 = ["<button id=\"", "\" type=\"button\" role=\"option\" tabindex=\"-1\"", "><span class=\"k2b-chat-composer__emoji-glyph\" aria-hidden=\"true\">", "</span><strong>", "</strong>", "</button>"],
35
- _tmpl$26 = ["<small>:", ":</small>"],
36
- _tmpl$27 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
37
- _tmpl$28 = "<div class=\"k2b-chat-composer__command-placeholder\" aria-hidden=\"true\"><span></span><span><span></span><span></span></span></div>",
38
- _tmpl$29 = ["<div class=\"k2b-chat-composer__command-group\" role=\"group\"", "><div class=\"k2b-chat-composer__command-heading\"", " role=\"presentation\"><span>", "</span>", "</div>", "", "</div>"],
39
- _tmpl$30 = ["<span class=\"k2b-chat-composer__hint-text\">", "</span>"],
40
- _tmpl$31 = ["<span class=\"k2b-chat-composer__dictation\"", "><i", " aria-hidden=\"true\"></i><span>", "</span></span>"],
41
- _tmpl$32 = ["<button type=\"button\" class=\"k2b-chat-composer__hint-action\"", ">", "</button>"],
42
- _tmpl$33 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-remove\"", "", "><i class=\"ti ti-x\" aria-hidden=\"true\"></i></button>"],
43
- _tmpl$34 = ["<div class=\"k2b-chat-composer__attachment\" role=\"listitem\"><div class=\"", "\">", "", "</div>", "</div>"],
44
- _tmpl$35 = ["<img", ">"],
45
- _tmpl$36 = ["<div class=\"k2b-chat-composer__attachment-identity\">", "", "</div>"],
46
- _tmpl$37 = ["<a class=\"k2b-chat-composer__attachment-link\"", " target=\"_blank\" rel=\"noopener noreferrer\"", ">", "", "</a>"],
47
- _tmpl$38 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-action\"", ">", "", "</button>"],
48
- _tmpl$39 = ["<footer class=\"k2b-chat-composer__footer\">", "</footer>"],
49
- _tmpl$40 = ["<div class=\"k2b-chat-composer__tools\">", "", "", "", "", "", "", "</div>"],
50
- _tmpl$41 = ["<div class=\"k2b-chat-composer__submit\">", "", "", "", "", "</div>"],
51
- _tmpl$42 = ["<span>", "</span>"],
52
- _tmpl$43 = ["<div class=\"k2b-chat-composer__status-line\"", "><i aria-hidden=\"true\"", "></i><span>", "</span></div>"],
53
- _tmpl$44 = ["<span class=\"k2b-sr-only\">", "</span>"];
21
+ _tmpl$12 = ["<span class=\"k2b-chat-composer__command-end\"><i class=\"ti ti-check\" aria-hidden=\"true\"></i><span class=\"k2b-sr-only\">", "</span></span>"],
22
+ _tmpl$13 = ["<button class=\"k2b-chat-composer__command\"", " type=\"button\" role=\"option\" tabindex=\"-1\"", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-chat-composer__command-text\"><strong>", "</strong>", "</span>", "</button>"],
23
+ _tmpl$14 = ["<b>", "</b>"],
24
+ _tmpl$15 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "</div></div>"],
25
+ _tmpl$16 = ["<div role=\"status\" class=\"k2b-chat-composer__loading\"><i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i><strong>", "</strong></div>"],
26
+ _tmpl$17 = ["<div role=\"status\"><i class=\"ti ti-alert-circle\"></i> ", "</div>"],
27
+ _tmpl$18 = ["<div role=\"status\">", "</div>"],
28
+ _tmpl$19 = ["<div class=\"k2b-chat-composer-accessory\" data-commands><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "", "", "", "", "</div></div>"],
29
+ _tmpl$20 = ["<div class=\"k2b-chat-composer-slot\"", "><div style=\"", "\"", ">", "</div>", "", "</div>"],
30
+ _tmpl$21 = ["<div class=\"k2b-chat-composer__hint\" aria-live=\"polite\">", "</div>"],
31
+ _tmpl$22 = ["<div class=\"k2b-chat-composer__attachments\" role=\"list\"", " tabindex=\"0\">", "</div>"],
32
+ _tmpl$23 = ["<div class=\"k2b-chat-composer__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i>", "</div>"],
33
+ _tmpl$24 = ["<div class=\"k2b-chat-composer__row\">", "", "<div class=\"k2b-chat-composer__field\">", "<div class=\"k2b-chat-composer__field-tools\">", "", "", "</div></div>", "</div>"],
34
+ _tmpl$25 = ["<div class=\"k2b-chat-composer-shell\">", "<section class=\"", "\"", " role=\"group\"", ">", "", "", "", "</section><div class=\"k2b-sr-only\" role=\"status\" aria-live=\"polite\">", "</div><div class=\"k2b-chat-composer__status\" role=\"status\">", "", "</div></div>"],
35
+ _tmpl$26 = ["<button id=\"", "\" type=\"button\" role=\"option\" tabindex=\"-1\"", "><span class=\"k2b-chat-composer__emoji-glyph\" aria-hidden=\"true\">", "</span><strong>", "</strong>", "</button>"],
36
+ _tmpl$27 = ["<small>:", ":</small>"],
37
+ _tmpl$28 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
38
+ _tmpl$29 = "<div class=\"k2b-chat-composer__command-placeholder\" aria-hidden=\"true\"><span></span><span><span></span><span></span></span></div>",
39
+ _tmpl$30 = ["<div class=\"k2b-chat-composer__command-group\" role=\"group\"", "><div class=\"k2b-chat-composer__command-heading\"", " role=\"presentation\"><span>", "</span>", "</div>", "", "</div>"],
40
+ _tmpl$31 = ["<span class=\"k2b-chat-composer__hint-text\">", "</span>"],
41
+ _tmpl$32 = ["<span class=\"k2b-chat-composer__dictation\"", "><i", " aria-hidden=\"true\"></i><span>", "</span></span>"],
42
+ _tmpl$33 = ["<button type=\"button\" class=\"k2b-chat-composer__hint-action\"", ">", "</button>"],
43
+ _tmpl$34 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-remove\"", "", "><i class=\"ti ti-x\" aria-hidden=\"true\"></i></button>"],
44
+ _tmpl$35 = ["<div class=\"k2b-chat-composer__attachment\" role=\"listitem\"><div class=\"", "\">", "", "</div>", "</div>"],
45
+ _tmpl$36 = ["<img", ">"],
46
+ _tmpl$37 = ["<div class=\"k2b-chat-composer__attachment-identity\">", "", "</div>"],
47
+ _tmpl$38 = ["<a class=\"k2b-chat-composer__attachment-link\"", " target=\"_blank\" rel=\"noopener noreferrer\"", ">", "", "</a>"],
48
+ _tmpl$39 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-action\"", ">", "", "</button>"],
49
+ _tmpl$40 = ["<footer class=\"k2b-chat-composer__footer\">", "</footer>"],
50
+ _tmpl$41 = ["<div class=\"k2b-chat-composer__tools\">", "", "", "", "", "", "", "</div>"],
51
+ _tmpl$42 = ["<div class=\"k2b-chat-composer__submit\">", "", "", "", "", "</div>"],
52
+ _tmpl$43 = ["<span>", "</span>"],
53
+ _tmpl$44 = ["<div class=\"k2b-chat-composer__status-line\"", "><i aria-hidden=\"true\"", "></i><span>", "</span></div>"],
54
+ _tmpl$45 = ["<span class=\"k2b-sr-only\">", "</span>"];
54
55
  import { batch, children, createEffect, createMemo, createSignal, createUniqueId, For, onCleanup, onMount, Show, untrack } from "solid-js";
55
56
  import { Dropdown } from "../actions/Dropdown";
57
+ import { dialogHistorySettled, holdDialogHistory } from "../feedback/dialog-core";
56
58
  import { Tooltip } from "../feedback/Tooltip";
57
59
  import { replaceTextareaRange } from "../inputs/editor-dom";
58
60
  import { completedEmojiShortcode, emojiInTone, emojiName, emojiShortcodeQuery, loadEmojiIndex, suggestEmoji } from "../inputs/emoji/emoji-index";
@@ -76,7 +78,10 @@ const emojiSuggestionLimit = 8;
76
78
  // Keep editor undo text within approximately 2 MiB, plus the current edit.
77
79
  const composerUndoBudgetChars = 1_000_000;
78
80
 
79
- /** An action can cancel and restore its token, or report a short completion message. */
81
+ /**
82
+ * An action can cancel and restore its token, report a short completion message, or hand back a reference chosen in a
83
+ * dialog: `mention` takes the token's place exactly as a `mention` row does, and Ctrl/⌘+Z brings the token back.
84
+ */
80
85
 
81
86
  /** A fixed place in the slash list. Sections keep the given order, also while searching. */
82
87
 
@@ -114,12 +119,40 @@ const isTouchOnly = () => typeof matchMedia === "function" && matchMedia(touchOn
114
119
  const microphoneHoldMs = 500;
115
120
  /** Keeps the field focused (and a phone's keyboard open) when a composer button is pressed with a mouse or finger. */
116
121
  const keepFieldFocus = event => event.preventDefault();
122
+ /**
123
+ * Keeps one tap from reaching the page: its pointer, touch and mouse events stop at the window, and neither focus nor a
124
+ * click happens. A finger's tap ends with `touchend`, a scroll with `pointercancel`, a mouse's press with `click`.
125
+ */
126
+ const swallowTap = down => {
127
+ const blocked = ["pointerup", "pointercancel", "touchstart", "touchend", "mousedown", "mouseup", "click", "contextmenu"];
128
+ const stop = () => {
129
+ window.removeEventListener("pointerdown", next, true);
130
+ for (const type of blocked) window.removeEventListener(type, block, true);
131
+ };
132
+ const block = event => {
133
+ event.stopPropagation();
134
+ if (event.type !== "pointercancel" && event.type !== "touchstart" && event.type !== "pointerup") event.preventDefault();
135
+ if (event.type === "click" || event.type === "touchend" || event.type === "pointercancel") stop();
136
+ };
137
+ // The next press is a new gesture, whatever became of this one.
138
+ const next = event => {
139
+ if (event !== down) stop();
140
+ };
141
+ down.stopPropagation();
142
+ down.preventDefault();
143
+ window.addEventListener("pointerdown", next, true);
144
+ for (const type of blocked) window.addEventListener(type, block, {
145
+ capture: true,
146
+ passive: false
147
+ });
148
+ };
117
149
  const attachmentIcon = attachment => attachment.icon ?? (attachment.kind === "image" ? "ti ti-photo" : attachment.kind === "resource" ? "ti ti-link" : "ti ti-file");
118
150
  const hasContent = items => items.some(item => item != null && typeof item !== "boolean" && item !== "");
119
151
  export function ChatComposer(props) {
120
152
  const accessory = children(() => props.accessory);
121
153
  const hasAccessory = () => hasContent(accessory.toArray());
122
154
  const hint = children(() => props.hint);
155
+ const footer = children(() => props.footerContent);
123
156
  const messages = useUiMessages();
124
157
  const markdownToolbarMessages = useMarkdownToolbarMessages();
125
158
  const chatMessages = useChatMessages();
@@ -140,6 +173,7 @@ export function ChatComposer(props) {
140
173
  const [closedAt, setClosedAt] = createSignal();
141
174
  // Where the pending native edit starts; a selection replaced as a whole starts at its beginning.
142
175
  let inputStart;
176
+ let inputEnd;
143
177
  const [commandStatus, setCommandStatus] = createSignal();
144
178
  const [announcement, setAnnouncement] = createSignal();
145
179
  let statusTimer;
@@ -158,6 +192,7 @@ export function ChatComposer(props) {
158
192
  });
159
193
  const [addingFiles, setAddingFiles] = createSignal(false);
160
194
  const [submitting, setSubmitting] = createSignal(false);
195
+ let shellRef;
161
196
  let composerRef;
162
197
  let textareaRef;
163
198
  let fileInputRef;
@@ -185,6 +220,45 @@ export function ChatComposer(props) {
185
220
  const [searching, setSearching] = createSignal(false);
186
221
  const [searchError, setSearchError] = createSignal(false);
187
222
  const [executing, setExecuting] = createSignal(false);
223
+ const [modelOpen, setModelOpen] = createSignal();
224
+ const [choosingModel, setChoosingModel] = createSignal(false);
225
+ let resolveModel;
226
+ const finishModelSelection = id => {
227
+ const resolve = resolveModel;
228
+ resolveModel = undefined;
229
+ if (choosingModel()) {
230
+ setModelOpen(false);
231
+ // Let the controlled close reach Dropdown before returning normal clicks to its own state.
232
+ queueMicrotask(() => {
233
+ if (!choosingModel()) setModelOpen(undefined);
234
+ });
235
+ }
236
+ setChoosingModel(false);
237
+ resolve?.(id);
238
+ };
239
+ const blockedBesidesCommand = () => Boolean(props.disabled || stopping() || state() === "submitting" || addingFiles() || submitting());
240
+ const usableModelControl = () => !conversation() && !footer() && !blockedBesidesCommand() && !running() && Boolean(props.models?.length && props.onModelChange);
241
+ const openModelSelection = () => {
242
+ if (!usableModelControl()) return Promise.resolve(null);
243
+ finishModelSelection(null);
244
+ return new Promise(resolve => {
245
+ resolveModel = resolve;
246
+ setChoosingModel(true);
247
+ setModelOpen(true);
248
+ queueMicrotask(() => {
249
+ if (!choosingModel() || !modelOpen()) return;
250
+ // Start on the current model, as a click on the chip would show it checked.
251
+ const menu = composerRef?.querySelector(".k2b-chat-composer__model");
252
+ (menu?.querySelector('[role="menuitemradio"][aria-checked="true"]:not(:disabled)') ?? menu?.querySelector('[role="menuitemradio"]:not(:disabled)'))?.focus();
253
+ });
254
+ });
255
+ };
256
+ createEffect(() => {
257
+ if (!usableModelControl()) finishModelSelection(null);
258
+ });
259
+ onCleanup(() => finishModelSelection(null));
260
+ const [commandOpening, setCommandOpening] = createSignal(0);
261
+ let retrySearch = false;
188
262
  let highlightRef;
189
263
  let commandListRef;
190
264
  const mentions = () => props.mentions ?? [];
@@ -218,7 +292,21 @@ export function ChatComposer(props) {
218
292
  const token = caretCommandToken();
219
293
  return dismissed() || composing() || token?.start === closedAt() ? null : token;
220
294
  });
295
+ const openingStart = createMemo(() => commandQuery()?.start);
296
+ createEffect(() => {
297
+ props.draftKey;
298
+ const start = openingStart();
299
+ if (start !== undefined) setCommandOpening(opening => opening + 1);
300
+ });
221
301
  const parsedCommandQuery = createMemo(() => parseChatCommandQuery(commandQuery()?.query ?? "", props.commandScopes ?? []));
302
+ const commandFallbackRow = createMemo(() => {
303
+ const parsed = parsedCommandQuery();
304
+ const query = parsed.query.trim();
305
+ return commandQuery() && (query || parsed.scope) ? props.commandFallback?.({
306
+ query,
307
+ scope: parsed.scope?.id
308
+ }) : undefined;
309
+ });
222
310
  const currentCommandScope = createMemo(() => parsedCommandQuery().scope?.id);
223
311
  createEffect(() => {
224
312
  currentCommandScope();
@@ -234,9 +322,12 @@ export function ChatComposer(props) {
234
322
  searchResults: searchResultsScope() === parsedCommandQuery().scope?.id ? searchResults() : [],
235
323
  searchResultsFresh: searchResultsQuery() === commandQuery()?.query && searchResultsScope() === parsedCommandQuery().scope?.id,
236
324
  searchSettled: searchResultsQuery() !== undefined && searchResultsScope() === parsedCommandQuery().scope?.id,
237
- searching: searching()
325
+ searching: searching(),
326
+ fallback: commandFallbackRow()
238
327
  }));
239
328
  const commandMatches = createMemo(() => commandQuery() ? commandModel().rows.map(row => row.command) : []);
329
+ /** Rows that matched; an unscoped fallback row only follows them. */
330
+ const foundCommandCount = () => commandMatches().length - (commandModel().fallback ? 1 : 0);
240
331
  const currentCommandQuery = createMemo(() => caretCommandToken()?.query);
241
332
  // Arrow marking belongs to one token in one draft; another draft or `/` starts at the top row again.
242
333
  createEffect(() => {
@@ -245,21 +336,46 @@ export function ChatComposer(props) {
245
336
  currentCommandQuery();
246
337
  setCommandMoved(false);
247
338
  });
248
- const commandsOpen = () => Boolean(!emojiOpen() && commandQuery() && (localCommands().length || props.searchCommands));
339
+ const commandsOpen = () => Boolean(!emojiOpen() && commandQuery() && (localCommands().length || props.searchCommands || commandFallbackRow()));
340
+ const commandSheetHeld = createMemo(() => commandsOpen() && isTouchOnly());
341
+ createEffect(() => {
342
+ if (!commandSheetHeld()) return;
343
+ onCleanup(holdDialogHistory(() => setClosedAt(commandQuery()?.start)));
344
+ });
249
345
  const suggestionsOpen = () => commandsOpen() || emojiOpen();
250
346
  createEffect(() => {
251
347
  if (!suggestionsOpen()) return;
252
348
  let disposed = false;
253
349
  const measure = () => {
254
350
  if (disposed || !commandListRef) return;
255
- let top = window.visualViewport?.offsetTop ?? 0;
256
- for (let parent = commandListRef.parentElement; parent; parent = parent.parentElement) {
257
- if (/(auto|scroll|hidden|clip)/.test(window.getComputedStyle(parent).overflowY)) {
351
+ const accessory = commandListRef.parentElement;
352
+ const viewportTop = window.visualViewport?.offsetTop ?? 0;
353
+ let top = viewportTop;
354
+ // The column a phone sheet spans: the nearest ancestor that clips sideways, or the page.
355
+ let column;
356
+ for (let parent = accessory.parentElement; parent; parent = parent.parentElement) {
357
+ const style = window.getComputedStyle(parent);
358
+ if (/(auto|scroll|hidden|clip)/.test(style.overflowY)) {
258
359
  top = Math.max(top, parent.getBoundingClientRect().top);
259
360
  }
361
+ if (!column && /(auto|scroll|hidden|clip)/.test(style.overflowX)) {
362
+ const left = parent.getBoundingClientRect().left + parent.clientLeft;
363
+ column = {
364
+ left,
365
+ right: left + parent.clientWidth
366
+ };
367
+ }
260
368
  }
261
- const bottom = commandListRef.parentElement.getBoundingClientRect().bottom;
369
+ const page = document.documentElement.clientWidth;
370
+ const left = Math.max(0, column?.left ?? 0);
371
+ const right = Math.min(page, column?.right ?? page);
372
+ const slot = accessory.parentElement.getBoundingClientRect();
373
+ const bottom = accessory.getBoundingClientRect().bottom;
262
374
  commandListRef.style.setProperty("--k2b-chat-command-space", `${Math.max(40, bottom - top - 8)}px`);
375
+ // Only a list that reaches the screen's top edge keeps clear of a notch or the status bar.
376
+ commandListRef.style.setProperty("--k2b-chat-command-safe-top", top === viewportTop ? "env(safe-area-inset-top, 0px)" : "0px");
377
+ accessory.style.setProperty("--k2b-chat-command-reach-left", `${Math.max(0, slot.left - left)}px`);
378
+ accessory.style.setProperty("--k2b-chat-command-reach-right", `${Math.max(0, right - slot.right)}px`);
263
379
  };
264
380
  queueMicrotask(measure);
265
381
  window.addEventListener("resize", measure);
@@ -272,6 +388,20 @@ export function ChatComposer(props) {
272
388
  window.visualViewport?.removeEventListener("resize", measure);
273
389
  });
274
390
  });
391
+ // A press outside the command list and the field closes the list, as Escape does; the text stays. With a mouse or a
392
+ // trackpad the press then acts as usual. On a touch-only device a tap outside the composer closes only the list: it
393
+ // reaches nothing beneath, and the field keeps focus, so the keyboard stays open.
394
+ createEffect(() => {
395
+ if (!commandsOpen()) return;
396
+ const onPointerDown = event => {
397
+ const target = event.target;
398
+ if (!(target instanceof Node) || commandListRef?.contains(target) || textareaRef?.parentElement?.contains(target)) return;
399
+ setClosedAt(commandQuery()?.start);
400
+ if (isTouchOnly() && !shellRef?.contains(target)) swallowTap(event);
401
+ };
402
+ window.addEventListener("pointerdown", onPointerDown, true);
403
+ onCleanup(() => window.removeEventListener("pointerdown", onPointerDown, true));
404
+ });
275
405
  createEffect(() => {
276
406
  props.draftKey;
277
407
  const token = commandQuery();
@@ -280,12 +410,17 @@ export function ChatComposer(props) {
280
410
  setSearching(false);
281
411
  return;
282
412
  }
413
+ running();
283
414
  const parsed = parsedCommandQuery();
415
+ const opening = commandOpening();
284
416
  const abort = new AbortController();
285
417
  setSearching(true);
418
+ const delay = retrySearch ? 0 : 120;
419
+ retrySearch = false;
286
420
  const timer = setTimeout(() => {
287
421
  Promise.resolve().then(() => props.searchCommands(parsed.query, abort.signal, {
288
- scope: parsed.scope?.id
422
+ scope: parsed.scope?.id,
423
+ opening
289
424
  })).then(results => {
290
425
  if (!abort.signal.aborted) {
291
426
  batch(() => {
@@ -303,7 +438,7 @@ export function ChatComposer(props) {
303
438
  }).finally(() => {
304
439
  if (!abort.signal.aborted) setSearching(false);
305
440
  });
306
- }, 120);
441
+ }, delay);
307
442
  onCleanup(() => {
308
443
  clearTimeout(timer);
309
444
  abort.abort();
@@ -362,6 +497,7 @@ export function ChatComposer(props) {
362
497
  untrack(() => {
363
498
  history.length = 0;
364
499
  historyIndex = -1;
500
+ finishModelSelection(null);
365
501
  setDismissed(true);
366
502
  setClosedAt(undefined);
367
503
  setSearchResults([]);
@@ -387,6 +523,13 @@ export function ChatComposer(props) {
387
523
  };
388
524
  const edit = (value, nextMentions = reconcileChatMentions(props.value, value, mentions())) => {
389
525
  if (commandStatus()?.tone === "error") showCommandStatus();
526
+ // Replacing `/` can leave its position and query identical, but starts a new opening.
527
+ if (inputStart !== undefined && inputEnd !== undefined && props.value.slice(inputStart, inputEnd).includes("/")) {
528
+ setCommandOpening(opening => opening + 1);
529
+ setSearchResults([]);
530
+ setSearchResultsQuery(undefined);
531
+ setCommandMoved(false);
532
+ }
390
533
  // An edit that starts at or before the closed `/` (select all, then type `/`) replaces it; the new `/` may open again.
391
534
  const closed = closedAt();
392
535
  if (closed !== undefined) {
@@ -395,6 +538,7 @@ export function ChatComposer(props) {
395
538
  if (Math.min(same, inputStart ?? same) <= closed) setClosedAt(undefined);
396
539
  }
397
540
  inputStart = undefined;
541
+ inputEnd = undefined;
398
542
  record();
399
543
  props.onValueChange(value);
400
544
  props.onMentionsChange?.(nextMentions);
@@ -438,7 +582,9 @@ export function ChatComposer(props) {
438
582
  /** The conversation field stays editable while a message is sent, so it keeps the focus and a phone its keyboard. */
439
583
  const editBlocked = () => conversation() ? busy() : blocked();
440
584
  const hasDraft = () => Boolean(props.value.trim() || !running() && attachments().length > 0);
441
- const canSubmit = () => !blocked() && hasDraft() && !(running() && runningSubmitIntent() === "steer" && mentions().length);
585
+ /** The draft may go out now, apart from a submission that is already on its way. */
586
+ const submittable = () => !busy() && hasDraft() && !(running() && runningSubmitIntent() === "steer" && mentions().length);
587
+ const canSubmit = () => !submitting() && submittable();
442
588
  const canSelectFiles = () => Boolean(props.fileSelection && !props.fileSelection.disabled && !running() && !blocked());
443
589
  const hasAddMenu = () => Boolean(props.fileSelection || props.menuActions?.length);
444
590
  const hasContextUsage = () => {
@@ -534,12 +680,78 @@ export function ChatComposer(props) {
534
680
  });
535
681
  const setAttachments = next => props.onAttachmentsChange?.(next);
536
682
  const renderAttachmentCopy = attachment => _$ssr(_tmpl$, _$ssrAttribute("title", _$escape(attachment.name, true), false), _$escape(attachment.name));
537
- const chooseFiles = () => {
683
+
684
+ /** `onNothing` runs when the person closes the selection without a file. */
685
+ const chooseFiles = onNothing => {
538
686
  const selection = props.fileSelection;
539
- if (!selection?.choose) return fileInputRef?.click();
540
- selection.choose().then(
687
+ if (!selection?.choose) {
688
+ const input = fileInputRef;
689
+ if (input && onNothing) {
690
+ const done = new AbortController();
691
+ input.addEventListener("cancel", () => {
692
+ done.abort();
693
+ onNothing();
694
+ }, {
695
+ signal: done.signal
696
+ });
697
+ input.addEventListener("change", () => done.abort(), {
698
+ signal: done.signal
699
+ });
700
+ }
701
+ return input?.click();
702
+ }
703
+ const failed = error => {
704
+ onNothing?.();
705
+ (props.fileSelection?.onError ?? props.onError)?.(error);
706
+ };
707
+ let chosen;
708
+ try {
709
+ chosen = selection.choose();
710
+ } catch (error) {
711
+ failed(error);
712
+ return;
713
+ }
714
+ chosen.then(
541
715
  // A single-file composer keeps the first chosen file, as its file dialog and drops do.
542
- files => void runFiles(selection.multiple === false ? files.slice(0, 1) : files), error => (props.fileSelection?.onError ?? props.onError)?.(error));
716
+ files => {
717
+ if (files.length === 0) onNothing?.();else void runFiles(selection.multiple === false ? files.slice(0, 1) : files);
718
+ }, failed);
719
+ };
720
+
721
+ /**
722
+ * "Attach files" from the slash list: the token leaves as one edit, and the selection opens synchronously, inside the
723
+ * key or click that chose the row, because the device's file dialog needs that activation. Closing the selection
724
+ * without a file brings the token back, unless the draft changed meanwhile.
725
+ */
726
+ const attachFilesFromCommand = token => {
727
+ if (!canSelectFiles()) return;
728
+ const key = props.draftKey;
729
+ const previous = snapshot();
730
+ const previousHistory = {
731
+ entries: [...history],
732
+ index: historyIndex
733
+ };
734
+ const nextValue = props.value.slice(0, token.start) + props.value.slice(token.end);
735
+ edit(nextValue, reconcileChatMentions(props.value, nextValue, mentions()));
736
+ setDismissed(true);
737
+ chooseFiles(() => {
738
+ if (props.draftKey !== key || props.value !== nextValue) return;
739
+ setDismissed(true);
740
+ props.onValueChange(previous.value);
741
+ props.onMentionsChange?.(previous.mentions);
742
+ history.splice(0, history.length, ...previousHistory.entries);
743
+ historyIndex = previousHistory.index;
744
+ queueMicrotask(() => {
745
+ textareaRef?.setSelectionRange(token.end, token.end);
746
+ syncCaret();
747
+ focus();
748
+ });
749
+ });
750
+ queueMicrotask(() => {
751
+ if (props.draftKey !== key || props.value !== nextValue) return;
752
+ textareaRef?.setSelectionRange(token.start, token.start);
753
+ syncCaret();
754
+ });
543
755
  };
544
756
  const runFiles = async files => {
545
757
  if (!canSelectFiles()) return;
@@ -558,18 +770,26 @@ export function ChatComposer(props) {
558
770
  };
559
771
  const submit = async () => {
560
772
  if (!canSubmit()) return;
561
- const intent = running() ? runningSubmitIntent() : "send";
562
- const previousValue = props.value;
563
- const previousAttachments = attachments();
564
- const previousMentions = mentions();
565
- const input = {
566
- intent,
567
- text: previousValue,
568
- mentions: previousMentions,
569
- attachments: intent !== "steer" ? previousAttachments : []
570
- };
773
+ const key = props.draftKey;
571
774
  setSubmitting(true);
572
775
  try {
776
+ if (isTouchOnly()) {
777
+ if (commandSheetHeld()) setClosedAt(commandQuery()?.start);
778
+ // The sheet's Back entry leaves first, so a submit handler that navigates finds the history settled.
779
+ await dialogHistorySettled();
780
+ // A draft that another one replaced, or that may no longer go out, is not sent and keeps its text.
781
+ if (disposed || props.draftKey !== key || !submittable()) return;
782
+ }
783
+ const intent = running() ? runningSubmitIntent() : "send";
784
+ const previousValue = props.value;
785
+ const previousAttachments = attachments();
786
+ const previousMentions = mentions();
787
+ const input = {
788
+ intent,
789
+ text: previousValue,
790
+ mentions: previousMentions,
791
+ attachments: intent !== "steer" ? previousAttachments : []
792
+ };
573
793
  await runChatSubmission({
574
794
  clear: () => {
575
795
  props.onValueChange("");
@@ -612,6 +832,7 @@ export function ChatComposer(props) {
612
832
  };
613
833
  const executeCommand = async (command, tab = false) => {
614
834
  const token = commandQuery();
835
+ const heldHistory = commandSheetHeld();
615
836
  if (!token || blocked()) return;
616
837
  if (command.disabled || command.reason) {
617
838
  // A new object each time, so choosing the same row again inserts and announces its reason again.
@@ -620,7 +841,16 @@ export function ChatComposer(props) {
620
841
  } : undefined);
621
842
  return;
622
843
  }
623
- if (command.scope && (tab || !command.action && !command.mention) && narrowCommand(command)) return;
844
+ if (command.searchAgain) {
845
+ retrySearch = true;
846
+ setCommandOpening(opening => opening + 1);
847
+ return;
848
+ }
849
+ if (command.scope && (tab || !command.action && !command.mention && !command.attachFiles) && narrowCommand(command)) return;
850
+ if (command.attachFiles) {
851
+ attachFilesFromCommand(token);
852
+ return;
853
+ }
624
854
  const previous = snapshot();
625
855
  const key = props.draftKey;
626
856
  const replacement = command.mention ? command.mention.name + " " : "";
@@ -641,6 +871,7 @@ export function ChatComposer(props) {
641
871
  setExecuting(true);
642
872
  let submitRequested = false;
643
873
  let restored = false;
874
+ let mentioned;
644
875
  const restore = () => {
645
876
  if (props.draftKey !== key || props.value !== nextValue) return;
646
877
  setDismissed(true);
@@ -655,17 +886,50 @@ export function ChatComposer(props) {
655
886
  text: command.busyLabel
656
887
  } : undefined);
657
888
  try {
889
+ // The sheet's Back entry leaves first, so an action that navigates or opens a dialog finds the history settled.
890
+ if (heldHistory && command.action) {
891
+ await dialogHistorySettled();
892
+ // Another draft or no composer any more: the command does not run against it.
893
+ if (disposed || props.draftKey !== key) return;
894
+ }
658
895
  const result = await command.action?.({
659
896
  setValue: value => edit(value),
660
897
  submit: () => {
661
898
  submitRequested = true;
662
899
  },
663
- focus
900
+ focus,
901
+ openModelSelection
664
902
  });
665
903
  if (result === "cancelled") {
666
904
  submitRequested = false;
667
905
  restore();
668
906
  showCommandStatus();
907
+ } else if (result && "mention" in result) {
908
+ // One undo step from the token to the reference, as if the row had been a reference itself.
909
+ if (props.draftKey === key && props.value === nextValue) {
910
+ restore();
911
+ restored = false;
912
+ const name = result.mention.name;
913
+ const withMention = previous.value.slice(0, token.start) + name + " " + previous.value.slice(token.end);
914
+ edit(withMention, [...reconcileChatMentions(previous.value, withMention, previous.mentions), {
915
+ start: token.start,
916
+ end: token.start + name.length,
917
+ attachment: result.mention
918
+ }]);
919
+ setDismissed(true);
920
+ mentioned = {
921
+ value: withMention,
922
+ caret: token.start + name.length + 1
923
+ };
924
+ showCommandStatus();
925
+ } else {
926
+ // The pick is lost; say so, also when the draft moved to another key meanwhile (such as a new chat saved
927
+ // while the dialog was open), so it never ends in silence.
928
+ showCommandStatus({
929
+ tone: "error",
930
+ text: chatMessages().commandDraftChanged
931
+ });
932
+ }
669
933
  } else if (result) {
670
934
  showCommandStatus({
671
935
  tone: "done",
@@ -687,8 +951,8 @@ export function ChatComposer(props) {
687
951
  if (submitRequested && props.draftKey === key) await submit();
688
952
  queueMicrotask(() => {
689
953
  if (props.draftKey !== key) return;
690
- if (restored || props.value === nextValue) {
691
- const position = restored ? token.end : token.start + replacement.length;
954
+ if (restored || props.value === nextValue || mentioned && props.value === mentioned.value) {
955
+ const position = restored ? token.end : mentioned?.caret ?? token.start + replacement.length;
692
956
  textareaRef?.setSelectionRange(position, position);
693
957
  }
694
958
  syncCaret();
@@ -940,7 +1204,7 @@ export function ChatComposer(props) {
940
1204
  get disabled() {
941
1205
  return !canSelectFiles();
942
1206
  },
943
- onClick: chooseFiles,
1207
+ onClick: () => chooseFiles(),
944
1208
  get children() {
945
1209
  return _$ssr(_tmpl$3, _$ssrAttribute("class", addingFiles() ? "ti ti-loader-2 k2b-spin" : "ti ti-plus", false));
946
1210
  }
@@ -1216,14 +1480,14 @@ export function ChatComposer(props) {
1216
1480
  const renderCommandRow = command => {
1217
1481
  const highlight = () => commandRowMap().get(command)?.highlight;
1218
1482
  const title = () => command.label ?? command.name;
1219
- return _$ssr(_tmpl$12, _$ssrAttribute("id", _$escape(commandOptionId(command), true), false), _$ssrAttribute("aria-selected", _$escape(selectedCommand(), true) === _$escape(command, true), false) + _$ssrAttribute("aria-disabled", _$escape(Boolean(command.disabled || command.reason), true), false) + _$ssrAttribute("aria-describedby", command.reason ? `${_$escape(commandOptionId(command), true)}-reason` : _$escape(undefined, true), false) + _$ssrAttribute("data-reason", command.reason ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", selectedCommand() === command ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(command.icon ?? "ti ti-slash", true), false), _$escape(_$createComponent(Show, {
1483
+ return _$ssr(_tmpl$13, _$ssrAttribute("id", _$escape(commandOptionId(command), true), false), _$ssrAttribute("aria-selected", _$escape(selectedCommand(), true) === _$escape(command, true), false) + _$ssrAttribute("aria-disabled", _$escape(Boolean(command.disabled || command.reason), true), false) + _$ssrAttribute("aria-describedby", command.reason ? `${_$escape(commandOptionId(command), true)}-reason` : _$escape(undefined, true), false) + _$ssrAttribute("data-reason", command.reason ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", selectedCommand() === command ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(command.icon ?? "ti ti-slash", true), false), _$escape(_$createComponent(Show, {
1220
1484
  get when() {
1221
1485
  return highlight();
1222
1486
  },
1223
1487
  get fallback() {
1224
1488
  return title();
1225
1489
  },
1226
- children: range => [title().slice(0, range()[0]), _$ssr(_tmpl$13, _$escape(title().slice(range()[0], range()[1]))), title().slice(range()[1])]
1490
+ children: range => [title().slice(0, range()[0]), _$ssr(_tmpl$14, _$escape(title().slice(range()[0], range()[1]))), title().slice(range()[1])]
1227
1491
  })), _$escape(_$createComponent(Show, {
1228
1492
  get when() {
1229
1493
  return command.reason ?? command.description;
@@ -1231,27 +1495,34 @@ export function ChatComposer(props) {
1231
1495
  get children() {
1232
1496
  return _$ssr(_tmpl$11, _$ssrAttribute("id", command.reason ? `${_$escape(commandOptionId(command), true)}-reason` : _$escape(undefined, true), false), _$escape(command.reason ?? command.description));
1233
1497
  }
1498
+ })), _$escape(_$createComponent(Show, {
1499
+ get when() {
1500
+ return command.selected;
1501
+ },
1502
+ get children() {
1503
+ return _$ssr(_tmpl$12, _$escape(chatMessages().commandSelected));
1504
+ }
1234
1505
  })));
1235
1506
  };
1236
- return _$ssr(_tmpl$24, _$escape(_$createComponent(Show, {
1507
+ return _$ssr(_tmpl$25, _$escape(_$createComponent(Show, {
1237
1508
  get when() {
1238
1509
  return suggestionsOpen() || hasAccessory();
1239
1510
  },
1240
1511
  get children() {
1241
- return _$ssr(_tmpl$19, _$ssrAttribute("data-overlay", hasAccessory() ? _$escape(undefined, true) : "true", false), _$ssrStyleProperty("visibility:", suggestionsOpen() ? "hidden" : _$escape(undefined, true)), _$ssrAttribute("inert", suggestionsOpen(), true), _$escape(accessory()), _$escape(_$createComponent(Show, {
1512
+ return _$ssr(_tmpl$20, _$ssrAttribute("data-overlay", hasAccessory() ? _$escape(undefined, true) : "true", false), _$ssrStyleProperty("visibility:", suggestionsOpen() ? "hidden" : _$escape(undefined, true)), _$ssrAttribute("inert", suggestionsOpen(), true), _$escape(accessory()), _$escape(_$createComponent(Show, {
1242
1513
  get when() {
1243
1514
  return emojiOpen();
1244
1515
  },
1245
1516
  get children() {
1246
- return _$ssr(_tmpl$14, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().emojiSuggestions, true), false), _$escape(_$createComponent(For, {
1517
+ return _$ssr(_tmpl$15, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().emojiSuggestions, true), false), _$escape(_$createComponent(For, {
1247
1518
  get each() {
1248
1519
  return emojiMatches();
1249
1520
  },
1250
- children: (entry, index) => _$ssr(_tmpl$25, `${_$escape(commandListId, true)}-${_$escape(index(), true)}`, _$ssrAttribute("aria-selected", _$escape(index(), true) === _$escape(selectedCommandIndex(), true), false) + _$ssrAttribute("aria-label", _$escape(emojiName(entry, locale()), true), false) + _$ssrAttribute("data-active", index() === selectedCommandIndex() ? "true" : _$escape(undefined, true), false), _$escape(emojiInTone(entry, props.emoji?.skinTone ?? 0)), _$escape(emojiName(entry, locale())), _$escape(_$createComponent(Show, {
1521
+ children: (entry, index) => _$ssr(_tmpl$26, `${_$escape(commandListId, true)}-${_$escape(index(), true)}`, _$ssrAttribute("aria-selected", _$escape(index(), true) === _$escape(selectedCommandIndex(), true), false) + _$ssrAttribute("aria-label", _$escape(emojiName(entry, locale()), true), false) + _$ssrAttribute("data-active", index() === selectedCommandIndex() ? "true" : _$escape(undefined, true), false), _$escape(emojiInTone(entry, props.emoji?.skinTone ?? 0)), _$escape(emojiName(entry, locale())), _$escape(_$createComponent(Show, {
1251
1522
  get when() {
1252
1523
  return entry.shortcodes[0];
1253
1524
  },
1254
- children: code => _$ssr(_tmpl$26, _$escape(code()))
1525
+ children: code => _$ssr(_tmpl$27, _$escape(code()))
1255
1526
  })))
1256
1527
  })));
1257
1528
  }
@@ -1260,7 +1531,7 @@ export function ChatComposer(props) {
1260
1531
  return commandsOpen();
1261
1532
  },
1262
1533
  get children() {
1263
- return _$ssr(_tmpl$18, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().commands, true), false) + _$ssrAttribute("aria-busy", searching() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(For, {
1534
+ return _$ssr(_tmpl$19, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().commands, true), false) + _$ssrAttribute("aria-busy", searching() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(For, {
1264
1535
  get each() {
1265
1536
  return commandModel().groups.map(commandGroupKey);
1266
1537
  },
@@ -1279,12 +1550,12 @@ export function ChatComposer(props) {
1279
1550
  });
1280
1551
  },
1281
1552
  get children() {
1282
- return _$ssr(_tmpl$29, _$ssrAttribute("aria-labelledby", _$escape(headingId(), true), false), _$ssrAttribute("id", _$escape(headingId(), true), false), _$escape(group()?.scopeTitle ?? group()?.section?.label), _$escape(_$createComponent(Show, {
1553
+ return _$ssr(_tmpl$30, _$ssrAttribute("aria-labelledby", _$escape(headingId(), true), false), _$ssrAttribute("id", _$escape(headingId(), true), false), _$escape(group()?.scopeTitle ?? group()?.section?.label), _$escape(_$createComponent(Show, {
1283
1554
  get when() {
1284
1555
  return group()?.loading;
1285
1556
  },
1286
1557
  get children() {
1287
- return _$ssr(_tmpl$27);
1558
+ return _$ssr(_tmpl$28);
1288
1559
  }
1289
1560
  })), _$escape(_$createComponent(For, {
1290
1561
  get each() {
@@ -1296,7 +1567,7 @@ export function ChatComposer(props) {
1296
1567
  return group()?.placeholder;
1297
1568
  },
1298
1569
  get children() {
1299
- return _$ssr(_tmpl$28);
1570
+ return _$ssr(_tmpl$29);
1300
1571
  }
1301
1572
  })));
1302
1573
  }
@@ -1304,27 +1575,33 @@ export function ChatComposer(props) {
1304
1575
  }
1305
1576
  })), _$escape(_$createComponent(Show, {
1306
1577
  get when() {
1307
- return _$memo(() => !!(searching() && commandMatches().length === 0))() && !commandModel().groups.some(group => group.section || group.scopeTitle);
1578
+ return _$memo(() => !!(searching() && foundCommandCount() === 0))() && !commandModel().groups.some(group => group.section || group.scopeTitle);
1308
1579
  },
1309
1580
  get children() {
1310
- return _$ssr(_tmpl$15, _$escape(messages().loading));
1581
+ return _$ssr(_tmpl$16, _$escape(messages().loading));
1311
1582
  }
1312
1583
  })), _$escape(_$createComponent(Show, {
1313
1584
  get when() {
1314
1585
  return searchError();
1315
1586
  },
1316
1587
  get children() {
1317
- return _$ssr(_tmpl$16, _$escape(messages().error));
1588
+ return _$ssr(_tmpl$17, _$escape(messages().error));
1318
1589
  }
1319
1590
  })), _$escape(_$createComponent(Show, {
1320
1591
  get when() {
1321
- return _$memo(() => !!(!searching() && !searchError()))() && commandMatches().length === 0;
1592
+ return _$memo(() => !!(!searching() && !searchError()))() && foundCommandCount() === 0;
1322
1593
  },
1323
1594
  get children() {
1324
- return _$ssr(_tmpl$17, !parsedCommandQuery().query.trim() ? _$escape(messages().noResults) : _$escape(chatMessages().commandsNothingFound({
1595
+ return _$ssr(_tmpl$18, !parsedCommandQuery().query.trim() ? _$escape(messages().noResults) : _$escape(chatMessages().commandsNothingFound({
1325
1596
  query: parsedCommandQuery().query.trim()
1326
1597
  })));
1327
1598
  }
1599
+ })), _$escape(_$createComponent(Show, {
1600
+ get when() {
1601
+ return commandModel().fallback?.command;
1602
+ },
1603
+ keyed: true,
1604
+ children: command => renderCommandRow(command)
1328
1605
  })));
1329
1606
  }
1330
1607
  })));
@@ -1334,18 +1611,18 @@ export function ChatComposer(props) {
1334
1611
  return showHint();
1335
1612
  },
1336
1613
  get children() {
1337
- return _$ssr(_tmpl$20, _$escape(_$createComponent(Show, {
1614
+ return _$ssr(_tmpl$21, _$escape(_$createComponent(Show, {
1338
1615
  get when() {
1339
1616
  return dictationStatus();
1340
1617
  },
1341
1618
  get fallback() {
1342
- return _$ssr(_tmpl$30, _$escape(hint()));
1619
+ return _$ssr(_tmpl$31, _$escape(hint()));
1343
1620
  },
1344
- children: status => [_$ssr(_tmpl$31, _$ssrAttribute("data-state", _$escape(dictation() ?? undefined, true), false), _$ssrAttribute("class", _$escape(status().icon, true), false), _$escape(status().text)), _$createComponent(Show, {
1621
+ children: status => [_$ssr(_tmpl$32, _$ssrAttribute("data-state", _$escape(dictation() ?? undefined, true), false), _$ssrAttribute("class", _$escape(status().icon, true), false), _$escape(status().text)), _$createComponent(Show, {
1345
1622
  get when() {
1346
1623
  return _$memo(() => dictation() === "refined")() ? props.microphone?.onRestoreOriginal : undefined;
1347
1624
  },
1348
- children: restore => _$ssr(_tmpl$32, _$ssrAttribute("disabled", microphoneDisabled(), true), _$escape(chatMessages().restoreOriginal))
1625
+ children: restore => _$ssr(_tmpl$33, _$ssrAttribute("disabled", microphoneDisabled(), true), _$escape(chatMessages().restoreOriginal))
1349
1626
  })]
1350
1627
  })));
1351
1628
  }
@@ -1354,16 +1631,16 @@ export function ChatComposer(props) {
1354
1631
  return attachments().length > 0;
1355
1632
  },
1356
1633
  get children() {
1357
- return _$ssr(_tmpl$21, _$ssrAttribute("aria-label", _$escape(chatMessages().attachments, true), false), _$escape(_$createComponent(For, {
1634
+ return _$ssr(_tmpl$22, _$ssrAttribute("aria-label", _$escape(chatMessages().attachments, true), false), _$escape(_$createComponent(For, {
1358
1635
  get each() {
1359
1636
  return attachments();
1360
1637
  },
1361
- children: attachment => _$ssr(_tmpl$34, `k2b-chat-composer__attachment-content ${Boolean(attachment.action) ? "k2b-chat-composer__attachment-content--action" : ""}`, _$escape(_$createComponent(Show, {
1638
+ children: attachment => _$ssr(_tmpl$35, `k2b-chat-composer__attachment-content ${Boolean(attachment.action) ? "k2b-chat-composer__attachment-content--action" : ""}`, _$escape(_$createComponent(Show, {
1362
1639
  get when() {
1363
1640
  return attachment.href;
1364
1641
  },
1365
1642
  get fallback() {
1366
- return _$ssr(_tmpl$36, _$escape(_$createComponent(Show, {
1643
+ return _$ssr(_tmpl$37, _$escape(_$createComponent(Show, {
1367
1644
  get when() {
1368
1645
  return _$memo(() => attachment.kind === "image")() && attachment.previewUrl;
1369
1646
  },
@@ -1371,11 +1648,11 @@ export function ChatComposer(props) {
1371
1648
  return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
1372
1649
  },
1373
1650
  get children() {
1374
- return _$ssr(_tmpl$35, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
1651
+ return _$ssr(_tmpl$36, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
1375
1652
  }
1376
1653
  })), _$escape(renderAttachmentCopy(attachment)));
1377
1654
  },
1378
- children: href => _$ssr(_tmpl$37, _$ssrAttribute("href", _$escape(href(), true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().openInNewTab({
1655
+ children: href => _$ssr(_tmpl$38, _$ssrAttribute("href", _$escape(href(), true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().openInNewTab({
1379
1656
  name: attachment.name
1380
1657
  }), true), false), _$escape(_$createComponent(Show, {
1381
1658
  get when() {
@@ -1385,14 +1662,14 @@ export function ChatComposer(props) {
1385
1662
  return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
1386
1663
  },
1387
1664
  get children() {
1388
- return _$ssr(_tmpl$35, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
1665
+ return _$ssr(_tmpl$36, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
1389
1666
  }
1390
1667
  })), _$escape(renderAttachmentCopy(attachment)))
1391
1668
  })), _$escape(_$createComponent(Show, {
1392
1669
  get when() {
1393
1670
  return attachment.action;
1394
1671
  },
1395
- children: action => _$ssr(_tmpl$38, _$ssrAttribute("disabled", blocked() || action().disabled, true), _$escape(_$createComponent(Show, {
1672
+ children: action => _$ssr(_tmpl$39, _$ssrAttribute("disabled", blocked() || action().disabled, true), _$escape(_$createComponent(Show, {
1396
1673
  get when() {
1397
1674
  return action().icon;
1398
1675
  },
@@ -1403,7 +1680,7 @@ export function ChatComposer(props) {
1403
1680
  return props.onAttachmentsChange;
1404
1681
  },
1405
1682
  get children() {
1406
- return _$ssr(_tmpl$33, _$ssrAttribute("aria-label", _$escape(selectMessages().removeNamed({
1683
+ return _$ssr(_tmpl$34, _$ssrAttribute("aria-label", _$escape(selectMessages().removeNamed({
1407
1684
  name: attachment.name
1408
1685
  }), true), false), _$ssrAttribute("disabled", blocked(), true));
1409
1686
  }
@@ -1420,14 +1697,14 @@ export function ChatComposer(props) {
1420
1697
  return props.error;
1421
1698
  },
1422
1699
  get children() {
1423
- return _$ssr(_tmpl$22, _$escape(props.error));
1700
+ return _$ssr(_tmpl$23, _$escape(props.error));
1424
1701
  }
1425
- }), formatRow(), _$ssr(_tmpl$39, _$escape(_$createComponent(Show, {
1702
+ }), formatRow(), _$ssr(_tmpl$40, _$escape(_$createComponent(Show, {
1426
1703
  get when() {
1427
- return props.footerContent;
1704
+ return footer();
1428
1705
  },
1429
1706
  get fallback() {
1430
- return [_$ssr(_tmpl$40, _$escape(props.footerTools), _$escape(_$createComponent(Show, {
1707
+ return [_$ssr(_tmpl$41, _$escape(props.footerTools), _$escape(_$createComponent(Show, {
1431
1708
  get when() {
1432
1709
  return hasAddMenu();
1433
1710
  },
@@ -1459,16 +1736,28 @@ export function ChatComposer(props) {
1459
1736
  image: model.image
1460
1737
  }));
1461
1738
  },
1739
+ get open() {
1740
+ return modelOpen();
1741
+ },
1742
+ onOpenChange: open => {
1743
+ if (!choosingModel()) return;
1744
+ setModelOpen(open);
1745
+ // Dropdown closes before committing the choice; let onValueChange settle it first.
1746
+ if (!open) queueMicrotask(() => {
1747
+ if (!modelOpen()) finishModelSelection(null);
1748
+ });
1749
+ },
1462
1750
  get disabled() {
1463
- return blocked() || running() || !props.onModelChange;
1751
+ return (choosingModel() ? blockedBesidesCommand() : blocked()) || running() || !props.onModelChange;
1464
1752
  },
1465
1753
  onValueChange: modelId => {
1466
1754
  props.onModelChange?.(modelId);
1755
+ finishModelSelection(modelId);
1467
1756
  queueMicrotask(focus);
1468
1757
  }
1469
1758
  });
1470
1759
  }
1471
- })), _$escape(props.modelDetails)), _$ssr(_tmpl$41, _$escape(_$createComponent(For, {
1760
+ })), _$escape(props.modelDetails)), _$ssr(_tmpl$42, _$escape(_$createComponent(For, {
1472
1761
  get each() {
1473
1762
  return props.contextActions;
1474
1763
  },
@@ -1509,7 +1798,9 @@ export function ChatComposer(props) {
1509
1798
  }))
1510
1799
  })), _$escape(props.submitTools), _$escape(microphoneControl()), _$escape(sendControl()))];
1511
1800
  },
1512
- children: content => content()
1801
+ get children() {
1802
+ return footer();
1803
+ }
1513
1804
  })))];
1514
1805
  },
1515
1806
  get children() {
@@ -1518,9 +1809,9 @@ export function ChatComposer(props) {
1518
1809
  return props.error;
1519
1810
  },
1520
1811
  get children() {
1521
- return _$ssr(_tmpl$22, _$escape(props.error));
1812
+ return _$ssr(_tmpl$23, _$escape(props.error));
1522
1813
  }
1523
- }), formatRow(), _$ssr(_tmpl$23, _$escape(attachControl()), _$escape(fileInput()), _$escape(field()), _$escape(formatToggle()), _$escape(emojiButton()), _$escape(microphoneControl()), _$escape(sendControl()))];
1814
+ }), formatRow(), _$ssr(_tmpl$24, _$escape(attachControl()), _$escape(fileInput()), _$escape(field()), _$escape(formatToggle()), _$escape(emojiButton()), _$escape(microphoneControl()), _$escape(sendControl()))];
1524
1815
  }
1525
1816
  })), _$escape(_$createComponent(Show, {
1526
1817
  get when() {
@@ -1546,18 +1837,18 @@ export function ChatComposer(props) {
1546
1837
  return suggestionAnnouncement();
1547
1838
  },
1548
1839
  keyed: true,
1549
- children: announced => _$ssr(_tmpl$42, _$escape(announced.text))
1840
+ children: announced => _$ssr(_tmpl$43, _$escape(announced.text))
1550
1841
  })), _$escape(_$createComponent(Show, {
1551
1842
  get when() {
1552
1843
  return _$memo(() => !!!suggestionsOpen())() && commandStatus();
1553
1844
  },
1554
- children: status => _$ssr(_tmpl$43, _$ssrAttribute("data-tone", _$escape(status().tone, true), false), _$ssrAttribute("class", status().tone === "busy" ? "ti ti-loader-2 k2b-spin" : status().tone === "done" ? "ti ti-check" : "ti ti-alert-circle", false), _$escape(status().text))
1845
+ children: status => _$ssr(_tmpl$44, _$ssrAttribute("data-tone", _$escape(status().tone, true), false), _$ssrAttribute("class", status().tone === "busy" ? "ti ti-loader-2 k2b-spin" : status().tone === "done" ? "ti ti-check" : "ti ti-alert-circle", false), _$escape(status().text))
1555
1846
  })), _$escape(_$createComponent(Show, {
1556
1847
  get when() {
1557
1848
  return _$memo(() => !!suggestionsOpen())() && announcement();
1558
1849
  },
1559
1850
  keyed: true,
1560
- children: announced => _$ssr(_tmpl$44, _$escape(announced.text))
1851
+ children: announced => _$ssr(_tmpl$45, _$escape(announced.text))
1561
1852
  })));
1562
1853
  }
1563
1854
  //# sourceMappingURL=ChatComposer.js.map