@agent-native/toolkit 0.5.1 → 0.7.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 (349) hide show
  1. package/README.md +17 -1
  2. package/dist/chat-history/ChatHistoryList.d.ts +103 -0
  3. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
  4. package/dist/chat-history/ChatHistoryList.js +109 -0
  5. package/dist/chat-history/ChatHistoryList.js.map +1 -0
  6. package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
  7. package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
  8. package/dist/chat-history/ChatHistoryList.spec.js +283 -0
  9. package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
  10. package/dist/chat-history/index.d.ts +2 -0
  11. package/dist/chat-history/index.d.ts.map +1 -0
  12. package/dist/chat-history/index.js +2 -0
  13. package/dist/chat-history/index.js.map +1 -0
  14. package/dist/chat-history.css +314 -0
  15. package/dist/composer/AgentComposerFrame.d.ts +19 -0
  16. package/dist/composer/AgentComposerFrame.d.ts.map +1 -0
  17. package/dist/composer/AgentComposerFrame.js +14 -0
  18. package/dist/composer/AgentComposerFrame.js.map +1 -0
  19. package/dist/composer/ComposerPlusMenu.d.ts +17 -0
  20. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -0
  21. package/dist/composer/ComposerPlusMenu.js +501 -0
  22. package/dist/composer/ComposerPlusMenu.js.map +1 -0
  23. package/dist/composer/MentionPopover.d.ts +29 -0
  24. package/dist/composer/MentionPopover.d.ts.map +1 -0
  25. package/dist/composer/MentionPopover.js +180 -0
  26. package/dist/composer/MentionPopover.js.map +1 -0
  27. package/dist/composer/PastedTextChip.d.ts +9 -0
  28. package/dist/composer/PastedTextChip.d.ts.map +1 -0
  29. package/dist/composer/PastedTextChip.js +47 -0
  30. package/dist/composer/PastedTextChip.js.map +1 -0
  31. package/dist/composer/PromptComposer.d.ts +114 -0
  32. package/dist/composer/PromptComposer.d.ts.map +1 -0
  33. package/dist/composer/PromptComposer.js +302 -0
  34. package/dist/composer/PromptComposer.js.map +1 -0
  35. package/dist/composer/PromptComposer.spec.d.ts +2 -0
  36. package/dist/composer/PromptComposer.spec.d.ts.map +1 -0
  37. package/dist/composer/PromptComposer.spec.js +64 -0
  38. package/dist/composer/PromptComposer.spec.js.map +1 -0
  39. package/dist/composer/RealtimeVoiceMode.d.ts +104 -0
  40. package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -0
  41. package/dist/composer/RealtimeVoiceMode.js +245 -0
  42. package/dist/composer/RealtimeVoiceMode.js.map +1 -0
  43. package/dist/composer/RealtimeVoiceMode.spec.d.ts +2 -0
  44. package/dist/composer/RealtimeVoiceMode.spec.d.ts.map +1 -0
  45. package/dist/composer/RealtimeVoiceMode.spec.js +439 -0
  46. package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -0
  47. package/dist/composer/TiptapComposer.d.ts +221 -0
  48. package/dist/composer/TiptapComposer.d.ts.map +1 -0
  49. package/dist/composer/TiptapComposer.js +1838 -0
  50. package/dist/composer/TiptapComposer.js.map +1 -0
  51. package/dist/composer/TiptapComposer.spec.d.ts +2 -0
  52. package/dist/composer/TiptapComposer.spec.d.ts.map +1 -0
  53. package/dist/composer/TiptapComposer.spec.js +277 -0
  54. package/dist/composer/TiptapComposer.spec.js.map +1 -0
  55. package/dist/composer/VoiceButton.d.ts +25 -0
  56. package/dist/composer/VoiceButton.d.ts.map +1 -0
  57. package/dist/composer/VoiceButton.js +100 -0
  58. package/dist/composer/VoiceButton.js.map +1 -0
  59. package/dist/composer/VoiceButton.spec.d.ts +2 -0
  60. package/dist/composer/VoiceButton.spec.d.ts.map +1 -0
  61. package/dist/composer/VoiceButton.spec.js +19 -0
  62. package/dist/composer/VoiceButton.spec.js.map +1 -0
  63. package/dist/composer/asset-picker-url.d.ts +16 -0
  64. package/dist/composer/asset-picker-url.d.ts.map +1 -0
  65. package/dist/composer/asset-picker-url.js +64 -0
  66. package/dist/composer/asset-picker-url.js.map +1 -0
  67. package/dist/composer/asset-picker-url.spec.d.ts +2 -0
  68. package/dist/composer/asset-picker-url.spec.d.ts.map +1 -0
  69. package/dist/composer/asset-picker-url.spec.js +23 -0
  70. package/dist/composer/asset-picker-url.spec.js.map +1 -0
  71. package/dist/composer/attachment-accept.d.ts +9 -0
  72. package/dist/composer/attachment-accept.d.ts.map +1 -0
  73. package/dist/composer/attachment-accept.js +65 -0
  74. package/dist/composer/attachment-accept.js.map +1 -0
  75. package/dist/composer/attachment-accept.spec.d.ts +2 -0
  76. package/dist/composer/attachment-accept.spec.d.ts.map +1 -0
  77. package/dist/composer/attachment-accept.spec.js +15 -0
  78. package/dist/composer/attachment-accept.spec.js.map +1 -0
  79. package/dist/composer/draft-key.d.ts +2 -0
  80. package/dist/composer/draft-key.d.ts.map +1 -0
  81. package/dist/composer/draft-key.js +8 -0
  82. package/dist/composer/draft-key.js.map +1 -0
  83. package/dist/composer/draft-key.spec.d.ts +2 -0
  84. package/dist/composer/draft-key.spec.d.ts.map +1 -0
  85. package/dist/composer/draft-key.spec.js +13 -0
  86. package/dist/composer/draft-key.spec.js.map +1 -0
  87. package/dist/composer/extensions/FileReference.d.ts +3 -0
  88. package/dist/composer/extensions/FileReference.d.ts.map +1 -0
  89. package/dist/composer/extensions/FileReference.js +36 -0
  90. package/dist/composer/extensions/FileReference.js.map +1 -0
  91. package/dist/composer/extensions/MentionReference.d.ts +3 -0
  92. package/dist/composer/extensions/MentionReference.d.ts.map +1 -0
  93. package/dist/composer/extensions/MentionReference.js +66 -0
  94. package/dist/composer/extensions/MentionReference.js.map +1 -0
  95. package/dist/composer/extensions/SkillReference.d.ts +3 -0
  96. package/dist/composer/extensions/SkillReference.d.ts.map +1 -0
  97. package/dist/composer/extensions/SkillReference.js +40 -0
  98. package/dist/composer/extensions/SkillReference.js.map +1 -0
  99. package/dist/composer/index.d.ts +14 -0
  100. package/dist/composer/index.d.ts.map +1 -0
  101. package/dist/composer/index.js +13 -0
  102. package/dist/composer/index.js.map +1 -0
  103. package/dist/composer/pasted-text.d.ts +31 -0
  104. package/dist/composer/pasted-text.d.ts.map +1 -0
  105. package/dist/composer/pasted-text.js +130 -0
  106. package/dist/composer/pasted-text.js.map +1 -0
  107. package/dist/composer/pasted-text.spec.d.ts +2 -0
  108. package/dist/composer/pasted-text.spec.d.ts.map +1 -0
  109. package/dist/composer/pasted-text.spec.js +107 -0
  110. package/dist/composer/pasted-text.spec.js.map +1 -0
  111. package/dist/composer/prompt-attachments.d.ts +18 -0
  112. package/dist/composer/prompt-attachments.d.ts.map +1 -0
  113. package/dist/composer/prompt-attachments.js +65 -0
  114. package/dist/composer/prompt-attachments.js.map +1 -0
  115. package/dist/composer/prompt-attachments.spec.d.ts +2 -0
  116. package/dist/composer/prompt-attachments.spec.d.ts.map +1 -0
  117. package/dist/composer/prompt-attachments.spec.js +63 -0
  118. package/dist/composer/prompt-attachments.spec.js.map +1 -0
  119. package/dist/composer/realtime-voice-audio-level.d.ts +14 -0
  120. package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -0
  121. package/dist/composer/realtime-voice-audio-level.js +47 -0
  122. package/dist/composer/realtime-voice-audio-level.js.map +1 -0
  123. package/dist/composer/realtime-voice-audio-level.spec.d.ts +2 -0
  124. package/dist/composer/realtime-voice-audio-level.spec.d.ts.map +1 -0
  125. package/dist/composer/realtime-voice-audio-level.spec.js +25 -0
  126. package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -0
  127. package/dist/composer/realtime-voice-transcript.d.ts +26 -0
  128. package/dist/composer/realtime-voice-transcript.d.ts.map +1 -0
  129. package/dist/composer/realtime-voice-transcript.js +103 -0
  130. package/dist/composer/realtime-voice-transcript.js.map +1 -0
  131. package/dist/composer/realtime-voice-transcript.spec.d.ts +2 -0
  132. package/dist/composer/realtime-voice-transcript.spec.d.ts.map +1 -0
  133. package/dist/composer/realtime-voice-transcript.spec.js +76 -0
  134. package/dist/composer/realtime-voice-transcript.spec.js.map +1 -0
  135. package/dist/composer/runtime-adapters.d.ts +163 -0
  136. package/dist/composer/runtime-adapters.d.ts.map +1 -0
  137. package/dist/composer/runtime-adapters.js +130 -0
  138. package/dist/composer/runtime-adapters.js.map +1 -0
  139. package/dist/composer/types.d.ts +60 -0
  140. package/dist/composer/types.d.ts.map +1 -0
  141. package/dist/composer/types.js +2 -0
  142. package/dist/composer/types.js.map +1 -0
  143. package/dist/composer/use-file-search.d.ts +6 -0
  144. package/dist/composer/use-file-search.d.ts.map +1 -0
  145. package/dist/composer/use-file-search.js +53 -0
  146. package/dist/composer/use-file-search.js.map +1 -0
  147. package/dist/composer/use-mention-search.d.ts +6 -0
  148. package/dist/composer/use-mention-search.d.ts.map +1 -0
  149. package/dist/composer/use-mention-search.js +75 -0
  150. package/dist/composer/use-mention-search.js.map +1 -0
  151. package/dist/composer/use-skills.d.ts +7 -0
  152. package/dist/composer/use-skills.d.ts.map +1 -0
  153. package/dist/composer/use-skills.js +40 -0
  154. package/dist/composer/use-skills.js.map +1 -0
  155. package/dist/composer/useRealtimeVoiceMode.d.ts +179 -0
  156. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -0
  157. package/dist/composer/useRealtimeVoiceMode.js +1657 -0
  158. package/dist/composer/useRealtimeVoiceMode.js.map +1 -0
  159. package/dist/composer/useRealtimeVoiceMode.spec.d.ts +2 -0
  160. package/dist/composer/useRealtimeVoiceMode.spec.d.ts.map +1 -0
  161. package/dist/composer/useRealtimeVoiceMode.spec.js +618 -0
  162. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -0
  163. package/dist/composer/useVoiceDictation.d.ts +57 -0
  164. package/dist/composer/useVoiceDictation.d.ts.map +1 -0
  165. package/dist/composer/useVoiceDictation.js +1004 -0
  166. package/dist/composer/useVoiceDictation.js.map +1 -0
  167. package/dist/composer/useVoiceDictation.spec.d.ts +2 -0
  168. package/dist/composer/useVoiceDictation.spec.d.ts.map +1 -0
  169. package/dist/composer/useVoiceDictation.spec.js +32 -0
  170. package/dist/composer/useVoiceDictation.spec.js.map +1 -0
  171. package/dist/editor/RegistryBlockContext.d.ts +68 -0
  172. package/dist/editor/RegistryBlockContext.d.ts.map +1 -0
  173. package/dist/editor/RegistryBlockContext.js +10 -0
  174. package/dist/editor/RegistryBlockContext.js.map +1 -0
  175. package/dist/editor/RegistryBlockNode.d.ts +66 -0
  176. package/dist/editor/RegistryBlockNode.d.ts.map +1 -0
  177. package/dist/editor/RegistryBlockNode.js +406 -0
  178. package/dist/editor/RegistryBlockNode.js.map +1 -0
  179. package/dist/editor/RegistryBlockNode.spec.d.ts +2 -0
  180. package/dist/editor/RegistryBlockNode.spec.d.ts.map +1 -0
  181. package/dist/editor/RegistryBlockNode.spec.js +215 -0
  182. package/dist/editor/RegistryBlockNode.spec.js.map +1 -0
  183. package/dist/editor/index.d.ts +2 -0
  184. package/dist/editor/index.d.ts.map +1 -1
  185. package/dist/editor/index.js +2 -0
  186. package/dist/editor/index.js.map +1 -1
  187. package/dist/editor/registrySlashCommands.d.ts +68 -0
  188. package/dist/editor/registrySlashCommands.d.ts.map +1 -0
  189. package/dist/editor/registrySlashCommands.js +51 -0
  190. package/dist/editor/registrySlashCommands.js.map +1 -0
  191. package/package.json +32 -1
  192. package/src/app-shell/header-actions.tsx +84 -0
  193. package/src/app-shell/index.ts +1 -0
  194. package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
  195. package/src/chat-history/ChatHistoryList.tsx +469 -0
  196. package/src/chat-history/index.ts +7 -0
  197. package/src/chat-history.css +314 -0
  198. package/src/collab-ui/AgentPresenceChip.tsx +69 -0
  199. package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  200. package/src/collab-ui/PresenceBar.tsx +346 -0
  201. package/src/collab-ui/RecentEditHighlights.tsx +225 -0
  202. package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  203. package/src/collab-ui/agent-identity.ts +16 -0
  204. package/src/collab-ui/index.ts +39 -0
  205. package/src/collab-ui/lead-client.ts +32 -0
  206. package/src/collab-ui/types.spec.ts +18 -0
  207. package/src/collab-ui/types.ts +96 -0
  208. package/src/composer/AgentComposerFrame.tsx +59 -0
  209. package/src/composer/ComposerPlusMenu.tsx +983 -0
  210. package/src/composer/MentionPopover.tsx +412 -0
  211. package/src/composer/PastedTextChip.tsx +150 -0
  212. package/src/composer/PromptComposer.spec.ts +73 -0
  213. package/src/composer/PromptComposer.tsx +718 -0
  214. package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
  215. package/src/composer/RealtimeVoiceMode.tsx +856 -0
  216. package/src/composer/TiptapComposer.spec.ts +390 -0
  217. package/src/composer/TiptapComposer.tsx +2936 -0
  218. package/src/composer/VoiceButton.spec.ts +33 -0
  219. package/src/composer/VoiceButton.tsx +255 -0
  220. package/src/composer/asset-picker-url.spec.ts +56 -0
  221. package/src/composer/asset-picker-url.ts +80 -0
  222. package/src/composer/attachment-accept.spec.ts +22 -0
  223. package/src/composer/attachment-accept.ts +71 -0
  224. package/src/composer/draft-key.spec.ts +19 -0
  225. package/src/composer/draft-key.ts +7 -0
  226. package/src/composer/extensions/FileReference.tsx +55 -0
  227. package/src/composer/extensions/MentionReference.tsx +93 -0
  228. package/src/composer/extensions/SkillReference.tsx +56 -0
  229. package/src/composer/index.ts +79 -0
  230. package/src/composer/pasted-text.spec.ts +162 -0
  231. package/src/composer/pasted-text.ts +160 -0
  232. package/src/composer/prompt-attachments.spec.ts +80 -0
  233. package/src/composer/prompt-attachments.ts +93 -0
  234. package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
  235. package/src/composer/realtime-voice-audio-level.ts +64 -0
  236. package/src/composer/realtime-voice-transcript.spec.ts +95 -0
  237. package/src/composer/realtime-voice-transcript.ts +134 -0
  238. package/src/composer/runtime-adapters.tsx +343 -0
  239. package/src/composer/types.ts +66 -0
  240. package/src/composer/use-file-search.ts +64 -0
  241. package/src/composer/use-mention-search.ts +90 -0
  242. package/src/composer/use-skills.ts +45 -0
  243. package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
  244. package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  245. package/src/composer/useVoiceDictation.spec.ts +45 -0
  246. package/src/composer/useVoiceDictation.ts +1191 -0
  247. package/src/context-ui/ContextMeter.tsx +101 -0
  248. package/src/context-ui/ContextSegmentRow.tsx +184 -0
  249. package/src/context-ui/ContextTreemap.tsx +125 -0
  250. package/src/context-ui/ContextXRayPanel.tsx +377 -0
  251. package/src/context-ui/format.ts +42 -0
  252. package/src/context-ui/index.ts +15 -0
  253. package/src/context-ui/types.ts +43 -0
  254. package/src/design-tweaks/index.ts +1 -0
  255. package/src/design-tweaks/visual-style-controls.tsx +1084 -0
  256. package/src/editor/BubbleToolbar.tsx +253 -0
  257. package/src/editor/CodeBlockNode.tsx +254 -0
  258. package/src/editor/DragHandle.spec.ts +588 -0
  259. package/src/editor/DragHandle.ts +1437 -0
  260. package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
  261. package/src/editor/ImageExtension.ts +306 -0
  262. package/src/editor/RegistryBlockContext.tsx +107 -0
  263. package/src/editor/RegistryBlockNode.spec.ts +279 -0
  264. package/src/editor/RegistryBlockNode.tsx +652 -0
  265. package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
  266. package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
  267. package/src/editor/RichMarkdownEditor.tsx +81 -0
  268. package/src/editor/RunId.ts +61 -0
  269. package/src/editor/SharedRichEditor.tsx +321 -0
  270. package/src/editor/SlashCommandMenu.tsx +310 -0
  271. package/src/editor/disableHistory.spec.ts +85 -0
  272. package/src/editor/extensions.ts +402 -0
  273. package/src/editor/gfmDoc.spec.ts +230 -0
  274. package/src/editor/gfmDoc.ts +92 -0
  275. package/src/editor/index.ts +93 -0
  276. package/src/editor/registrySlashCommands.ts +131 -0
  277. package/src/editor/structuralReorderUndo.spec.ts +144 -0
  278. package/src/editor/surgical-apply.spec.ts +279 -0
  279. package/src/editor/surgical-apply.ts +192 -0
  280. package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
  281. package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
  282. package/src/editor/useCollabReconcile.ts +589 -0
  283. package/src/editor.css +510 -0
  284. package/src/hooks/index.ts +2 -0
  285. package/src/hooks/use-mobile.tsx +21 -0
  286. package/src/hooks/use-toast.ts +188 -0
  287. package/src/index.ts +8 -0
  288. package/src/onboarding/index.ts +1 -0
  289. package/src/provider.tsx +47 -0
  290. package/src/sharing/VisibilityBadge.spec.ts +96 -0
  291. package/src/sharing/VisibilityBadge.tsx +37 -0
  292. package/src/sharing/index.ts +4 -0
  293. package/src/styles.css +21 -0
  294. package/src/ui/accordion.tsx +56 -0
  295. package/src/ui/alert-dialog.tsx +141 -0
  296. package/src/ui/alert.tsx +59 -0
  297. package/src/ui/aspect-ratio.tsx +5 -0
  298. package/src/ui/avatar.tsx +48 -0
  299. package/src/ui/badge.tsx +37 -0
  300. package/src/ui/breadcrumb.tsx +115 -0
  301. package/src/ui/button.tsx +78 -0
  302. package/src/ui/calendar.tsx +214 -0
  303. package/src/ui/card.tsx +86 -0
  304. package/src/ui/carousel.tsx +260 -0
  305. package/src/ui/chart.tsx +375 -0
  306. package/src/ui/checkbox.tsx +28 -0
  307. package/src/ui/collapsible.tsx +9 -0
  308. package/src/ui/command.spec.tsx +51 -0
  309. package/src/ui/command.tsx +174 -0
  310. package/src/ui/context-menu.tsx +202 -0
  311. package/src/ui/date-picker.tsx +64 -0
  312. package/src/ui/dialog.spec.tsx +43 -0
  313. package/src/ui/dialog.tsx +146 -0
  314. package/src/ui/drawer.tsx +126 -0
  315. package/src/ui/dropdown-menu.tsx +218 -0
  316. package/src/ui/form.tsx +177 -0
  317. package/src/ui/hover-card.tsx +27 -0
  318. package/src/ui/index.ts +52 -0
  319. package/src/ui/input-otp.tsx +69 -0
  320. package/src/ui/input.tsx +22 -0
  321. package/src/ui/label.tsx +24 -0
  322. package/src/ui/logo.tsx +36 -0
  323. package/src/ui/menubar.tsx +235 -0
  324. package/src/ui/navigation-menu.tsx +128 -0
  325. package/src/ui/pagination.tsx +121 -0
  326. package/src/ui/popover.tsx +44 -0
  327. package/src/ui/progress.tsx +26 -0
  328. package/src/ui/radio-group.tsx +42 -0
  329. package/src/ui/resizable.tsx +43 -0
  330. package/src/ui/scroll-area.tsx +46 -0
  331. package/src/ui/select.tsx +158 -0
  332. package/src/ui/separator.tsx +29 -0
  333. package/src/ui/sheet.tsx +160 -0
  334. package/src/ui/sidebar.tsx +777 -0
  335. package/src/ui/skeleton.tsx +18 -0
  336. package/src/ui/slider.tsx +26 -0
  337. package/src/ui/sonner.tsx +58 -0
  338. package/src/ui/spinner.tsx +17 -0
  339. package/src/ui/switch.tsx +27 -0
  340. package/src/ui/table.tsx +117 -0
  341. package/src/ui/tabs.tsx +53 -0
  342. package/src/ui/textarea.tsx +23 -0
  343. package/src/ui/toast.tsx +127 -0
  344. package/src/ui/toaster.tsx +33 -0
  345. package/src/ui/toggle-group.tsx +59 -0
  346. package/src/ui/toggle.tsx +43 -0
  347. package/src/ui/tooltip.tsx +53 -0
  348. package/src/ui/use-toast.ts +3 -0
  349. package/src/utils.ts +8 -0
@@ -0,0 +1,588 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { Editor } from "@tiptap/core";
4
+ import StarterKit from "@tiptap/starter-kit";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { DragHandle, type DragHandleOptions } from "./DragHandle.js";
8
+
9
+ function makeRect({
10
+ left,
11
+ top,
12
+ width,
13
+ height,
14
+ }: {
15
+ left: number;
16
+ top: number;
17
+ width: number;
18
+ height: number;
19
+ }): DOMRect {
20
+ return {
21
+ x: left,
22
+ y: top,
23
+ left,
24
+ top,
25
+ width,
26
+ height,
27
+ right: left + width,
28
+ bottom: top + height,
29
+ toJSON: () => ({}),
30
+ } as DOMRect;
31
+ }
32
+
33
+ function setRect(element: Element, rect: DOMRect): void {
34
+ Object.defineProperty(element, "getBoundingClientRect", {
35
+ configurable: true,
36
+ value: () => rect,
37
+ });
38
+ }
39
+
40
+ function mountEditor(
41
+ content: string,
42
+ options: Partial<DragHandleOptions> = {},
43
+ rect: DOMRect = makeRect({ left: 24, top: 0, width: 640, height: 400 }),
44
+ parent: HTMLElement = document.body,
45
+ hoverPoint: { x: number; y: number } = { x: 32, y: rect.top + 12 },
46
+ ): {
47
+ editor: Editor;
48
+ wrapper: HTMLElement;
49
+ handle: HTMLElement;
50
+ } {
51
+ const wrapper = document.createElement("div");
52
+ wrapper.className = "visual-editor-wrapper";
53
+ setRect(wrapper, rect);
54
+ parent.appendChild(wrapper);
55
+
56
+ const element = document.createElement("div");
57
+ wrapper.appendChild(element);
58
+
59
+ const editor = new Editor({
60
+ element,
61
+ extensions: [StarterKit, DragHandle.configure(options)],
62
+ content,
63
+ });
64
+
65
+ setRect(editor.view.dom, rect);
66
+
67
+ let index = 0;
68
+ editor.state.doc.forEach((_node, offset) => {
69
+ const dom = editor.view.nodeDOM(offset);
70
+ if (dom instanceof HTMLElement) {
71
+ setRect(
72
+ dom,
73
+ makeRect({
74
+ left: 24,
75
+ top: rect.top + index * 40,
76
+ width: 640,
77
+ height: 24,
78
+ }),
79
+ );
80
+ index += 1;
81
+ }
82
+ });
83
+
84
+ document.dispatchEvent(
85
+ new MouseEvent("mousemove", {
86
+ bubbles: true,
87
+ clientX: hoverPoint.x,
88
+ clientY: hoverPoint.y,
89
+ }),
90
+ );
91
+
92
+ const handle = wrapper.querySelector<HTMLElement>(".drag-handle");
93
+ if (!handle) throw new Error("Expected drag handle to mount");
94
+ setRect(
95
+ handle,
96
+ makeRect({ left: 0, top: rect.top + 2, width: 24, height: 24 }),
97
+ );
98
+
99
+ return { editor, wrapper, handle };
100
+ }
101
+
102
+ function hoverAt(x: number, y: number): void {
103
+ document.dispatchEvent(
104
+ new MouseEvent("mousemove", {
105
+ bubbles: true,
106
+ clientX: x,
107
+ clientY: y,
108
+ }),
109
+ );
110
+ }
111
+
112
+ function clickHandle(handle: HTMLElement): void {
113
+ handle.dispatchEvent(
114
+ new MouseEvent("mousedown", {
115
+ bubbles: true,
116
+ button: 0,
117
+ clientX: 12,
118
+ clientY: 12,
119
+ }),
120
+ );
121
+ document.dispatchEvent(
122
+ new MouseEvent("mouseup", {
123
+ bubbles: true,
124
+ button: 0,
125
+ clientX: 12,
126
+ clientY: 12,
127
+ }),
128
+ );
129
+ }
130
+
131
+ function getMenuItems(): HTMLButtonElement[] {
132
+ return Array.from(
133
+ document.querySelectorAll<HTMLButtonElement>(".an-rich-md-drag-menu__item"),
134
+ );
135
+ }
136
+
137
+ function clickMenuItem(label: string): void {
138
+ const item = getMenuItems().find((button) =>
139
+ button.textContent?.includes(label),
140
+ );
141
+ if (!item) throw new Error(`Expected menu item "${label}"`);
142
+ item.dispatchEvent(new MouseEvent("click", { bubbles: true }));
143
+ }
144
+
145
+ function childText(editor: Editor, index: number): string {
146
+ return editor.state.doc.child(index).textContent;
147
+ }
148
+
149
+ afterEach(() => {
150
+ document.body.innerHTML = "";
151
+ document
152
+ .querySelectorAll("#an-rich-md-drag-menu-styles")
153
+ .forEach((node) => node.remove());
154
+ });
155
+
156
+ describe("DragHandle menu", () => {
157
+ it("opens the block menu on a single click", () => {
158
+ const { editor, handle } = mountEditor("<p>First</p><p>Second</p>");
159
+
160
+ try {
161
+ clickHandle(handle);
162
+
163
+ expect(getMenuItems().map((item) => item.textContent)).toEqual([
164
+ "Duplicate",
165
+ "Delete",
166
+ "Insert block below",
167
+ ]);
168
+ } finally {
169
+ editor.destroy();
170
+ }
171
+ });
172
+
173
+ it("inserts an empty focused paragraph below the current block", () => {
174
+ const { editor, handle } = mountEditor("<p>First</p><p>Second</p>");
175
+
176
+ try {
177
+ clickHandle(handle);
178
+ clickMenuItem("Insert block below");
179
+
180
+ expect(editor.state.doc.childCount).toBe(3);
181
+ expect(childText(editor, 0)).toBe("First");
182
+ expect(childText(editor, 1)).toBe("");
183
+ expect(childText(editor, 2)).toBe("Second");
184
+ expect(editor.state.selection.$from.parent.type.name).toBe("paragraph");
185
+ expect(editor.state.selection.$from.parent.textContent).toBe("");
186
+ } finally {
187
+ editor.destroy();
188
+ }
189
+ });
190
+
191
+ it("duplicates and deletes the current block from the menu", () => {
192
+ const { editor, handle } = mountEditor("<p>First</p><p>Second</p>");
193
+
194
+ try {
195
+ clickHandle(handle);
196
+ clickMenuItem("Duplicate");
197
+
198
+ expect(editor.state.doc.childCount).toBe(3);
199
+ expect(childText(editor, 0)).toBe("First");
200
+ expect(childText(editor, 1)).toBe("First");
201
+ expect(childText(editor, 2)).toBe("Second");
202
+
203
+ document.dispatchEvent(
204
+ new MouseEvent("mousemove", {
205
+ bubbles: true,
206
+ clientX: 32,
207
+ clientY: 12,
208
+ }),
209
+ );
210
+ clickHandle(handle);
211
+ clickMenuItem("Delete");
212
+
213
+ expect(editor.state.doc.childCount).toBe(2);
214
+ expect(childText(editor, 0)).toBe("First");
215
+ expect(childText(editor, 1)).toBe("Second");
216
+ } finally {
217
+ editor.destroy();
218
+ }
219
+ });
220
+
221
+ it("keeps drag-to-reorder behavior when the handle is moved", () => {
222
+ const { editor, handle } = mountEditor("<p>First</p><p>Second</p>");
223
+
224
+ try {
225
+ handle.dispatchEvent(
226
+ new MouseEvent("mousedown", {
227
+ bubbles: true,
228
+ button: 0,
229
+ clientX: 12,
230
+ clientY: 12,
231
+ }),
232
+ );
233
+ document.dispatchEvent(
234
+ new MouseEvent("mousemove", {
235
+ bubbles: true,
236
+ clientX: 12,
237
+ clientY: 56,
238
+ }),
239
+ );
240
+ document.dispatchEvent(
241
+ new MouseEvent("mouseup", {
242
+ bubbles: true,
243
+ button: 0,
244
+ clientX: 12,
245
+ clientY: 56,
246
+ }),
247
+ );
248
+
249
+ expect(document.querySelector(".an-rich-md-drag-menu")).toBeNull();
250
+ expect(editor.state.doc.childCount).toBe(2);
251
+ expect(childText(editor, 0)).toBe("Second");
252
+ expect(childText(editor, 1)).toBe("First");
253
+ } finally {
254
+ editor.destroy();
255
+ }
256
+ });
257
+
258
+ it("moves a block across registered editor regions and passes transfer payloads", () => {
259
+ const transferData = { blockId: "source-block-1", extra: "side-map data" };
260
+ const getDragTransferData = vi.fn(() => transferData);
261
+ const receiveDragTransferData = vi.fn();
262
+ const source = mountEditor(
263
+ "<p>Move me</p><p>Keep source</p>",
264
+ { getDragTransferData },
265
+ makeRect({ left: 24, top: 0, width: 640, height: 120 }),
266
+ );
267
+ const target = mountEditor(
268
+ "<p>Target first</p><p>Target second</p>",
269
+ { receiveDragTransferData },
270
+ makeRect({ left: 24, top: 160, width: 640, height: 120 }),
271
+ );
272
+
273
+ try {
274
+ hoverAt(32, 12);
275
+ source.handle.dispatchEvent(
276
+ new MouseEvent("mousedown", {
277
+ bubbles: true,
278
+ button: 0,
279
+ clientX: 12,
280
+ clientY: 12,
281
+ }),
282
+ );
283
+ document.dispatchEvent(
284
+ new MouseEvent("mousemove", {
285
+ bubbles: true,
286
+ clientX: 32,
287
+ clientY: 164,
288
+ }),
289
+ );
290
+ document.dispatchEvent(
291
+ new MouseEvent("mouseup", {
292
+ bubbles: true,
293
+ button: 0,
294
+ clientX: 32,
295
+ clientY: 164,
296
+ }),
297
+ );
298
+
299
+ expect(getDragTransferData).toHaveBeenCalledTimes(1);
300
+ expect(getDragTransferData).toHaveBeenCalledWith({
301
+ view: source.editor.view,
302
+ node: expect.objectContaining({ textContent: "Move me" }),
303
+ pos: 0,
304
+ });
305
+ expect(receiveDragTransferData).toHaveBeenCalledTimes(1);
306
+ expect(receiveDragTransferData).toHaveBeenCalledWith(transferData, {
307
+ view: target.editor.view,
308
+ node: expect.objectContaining({ textContent: "Move me" }),
309
+ pos: 0,
310
+ sourceView: source.editor.view,
311
+ });
312
+ expect(source.editor.state.doc.childCount).toBe(1);
313
+ expect(childText(source.editor, 0)).toBe("Keep source");
314
+ expect(target.editor.state.doc.childCount).toBe(3);
315
+ expect(childText(target.editor, 0)).toBe("Move me");
316
+ expect(childText(target.editor, 1)).toBe("Target first");
317
+ expect(childText(target.editor, 2)).toBe("Target second");
318
+ } finally {
319
+ source.editor.destroy();
320
+ target.editor.destroy();
321
+ }
322
+ });
323
+
324
+ it("lets a host handle left/right side drops without ProseMirror moving the node", () => {
325
+ const transferData = { blockId: "source-block-1", extra: "side-map data" };
326
+ const getDragTransferData = vi.fn(() => transferData);
327
+ const handleDrop = vi.fn(() => true);
328
+ const source = mountEditor(
329
+ "<p>Move me</p><p>Keep source</p>",
330
+ { getDragTransferData },
331
+ makeRect({ left: 24, top: 0, width: 640, height: 120 }),
332
+ );
333
+ const target = mountEditor(
334
+ "<p>Target first</p><p>Target second</p>",
335
+ { handleDrop },
336
+ makeRect({ left: 24, top: 160, width: 640, height: 120 }),
337
+ );
338
+
339
+ try {
340
+ hoverAt(32, 12);
341
+ source.handle.dispatchEvent(
342
+ new MouseEvent("mousedown", {
343
+ bubbles: true,
344
+ button: 0,
345
+ clientX: 12,
346
+ clientY: 12,
347
+ }),
348
+ );
349
+ document.dispatchEvent(
350
+ new MouseEvent("mousemove", {
351
+ bubbles: true,
352
+ clientX: 660,
353
+ clientY: 172,
354
+ }),
355
+ );
356
+ document.dispatchEvent(
357
+ new MouseEvent("mouseup", {
358
+ bubbles: true,
359
+ button: 0,
360
+ clientX: 660,
361
+ clientY: 172,
362
+ }),
363
+ );
364
+
365
+ expect(getDragTransferData).toHaveBeenCalledTimes(1);
366
+ expect(handleDrop).toHaveBeenCalledTimes(1);
367
+ expect(handleDrop).toHaveBeenCalledWith(
368
+ transferData,
369
+ expect.objectContaining({
370
+ view: target.editor.view,
371
+ sourceView: source.editor.view,
372
+ sourceNode: expect.objectContaining({ textContent: "Move me" }),
373
+ targetNode: expect.objectContaining({ textContent: "Target first" }),
374
+ placement: "right",
375
+ targetPos: 0,
376
+ }),
377
+ );
378
+ expect(source.editor.state.doc.childCount).toBe(2);
379
+ expect(childText(source.editor, 0)).toBe("Move me");
380
+ expect(childText(source.editor, 1)).toBe("Keep source");
381
+ expect(target.editor.state.doc.childCount).toBe(2);
382
+ expect(childText(target.editor, 0)).toBe("Target first");
383
+ expect(childText(target.editor, 1)).toBe("Target second");
384
+ } finally {
385
+ source.editor.destroy();
386
+ target.editor.destroy();
387
+ }
388
+ });
389
+
390
+ it("shows only the innermost drag grip when editor regions overlap", () => {
391
+ const outer = mountEditor(
392
+ "<p>Outer container</p>",
393
+ {},
394
+ makeRect({ left: 24, top: 0, width: 640, height: 320 }),
395
+ );
396
+ const outerBlock = outer.editor.view.nodeDOM(0);
397
+ if (outerBlock instanceof HTMLElement) {
398
+ setRect(
399
+ outerBlock,
400
+ makeRect({ left: 24, top: 0, width: 640, height: 280 }),
401
+ );
402
+ }
403
+ const inner = mountEditor(
404
+ "<p>Inner block</p>",
405
+ {},
406
+ makeRect({ left: 48, top: 160, width: 300, height: 90 }),
407
+ outer.wrapper,
408
+ { x: 60, y: 172 },
409
+ );
410
+
411
+ try {
412
+ hoverAt(60, 172);
413
+
414
+ expect(inner.handle.style.display).toBe("flex");
415
+ expect(outer.handle.style.display).toBe("none");
416
+ } finally {
417
+ inner.editor.destroy();
418
+ outer.editor.destroy();
419
+ }
420
+ });
421
+
422
+ it("shows the outer container grip when hovering the shared left gutter", () => {
423
+ // A flush-left nested region (like the first column of a `columns` block)
424
+ // shares its container's left-margin gutter. Hovering that gutter — where the
425
+ // grip lives — must select the CONTAINER so it can be dragged to reorder,
426
+ // even though the nested editor is the smaller area. (Regression: nested
427
+ // column editors used to win everywhere, leaving the columns block ungrabbable.)
428
+ const outer = mountEditor(
429
+ "<p>Outer container</p>",
430
+ {},
431
+ makeRect({ left: 24, top: 0, width: 640, height: 320 }),
432
+ );
433
+ const outerBlock = outer.editor.view.nodeDOM(0);
434
+ if (outerBlock instanceof HTMLElement) {
435
+ setRect(
436
+ outerBlock,
437
+ makeRect({ left: 24, top: 0, width: 640, height: 280 }),
438
+ );
439
+ }
440
+ // Inner region mounted flush-left (block left === container left), mirroring a
441
+ // column that starts at the container's left edge.
442
+ const inner = mountEditor(
443
+ "<p>Inner block</p>",
444
+ {},
445
+ makeRect({ left: 24, top: 160, width: 300, height: 90 }),
446
+ outer.wrapper,
447
+ { x: 60, y: 172 },
448
+ );
449
+
450
+ try {
451
+ // Over the inner body, the innermost (smallest) editor still wins.
452
+ hoverAt(60, 172);
453
+ expect(inner.handle.style.display).toBe("flex");
454
+ expect(outer.handle.style.display).toBe("none");
455
+
456
+ // In the shared left-margin gutter AT THE NESTED BLOCK'S ROW, the inner
457
+ // block owns the grip band — so a block inside a flush-left column stays
458
+ // grabbable there to drag OUT of / BETWEEN columns. Its grip glyph renders
459
+ // in this exact gutter, so flipping to the container here would make the
460
+ // block's own grip unclickable (the whole point of column drag handles).
461
+ hoverAt(10, 172);
462
+ expect(inner.handle.style.display).toBe("flex");
463
+ expect(outer.handle.style.display).toBe("none");
464
+
465
+ // In the gutter ABOVE the nested region (a row the container alone
466
+ // occupies), the container still wins, so the columns block itself remains
467
+ // grabbable — it just yields the rows where a nested block's grip lives.
468
+ hoverAt(10, 40);
469
+ expect(outer.handle.style.display).toBe("flex");
470
+ expect(inner.handle.style.display).toBe("none");
471
+ } finally {
472
+ inner.editor.destroy();
473
+ outer.editor.destroy();
474
+ }
475
+ });
476
+
477
+ it("moves a block from a nested editor region out to the parent editor", () => {
478
+ const target = mountEditor(
479
+ "<p>Outer target</p>",
480
+ {},
481
+ makeRect({ left: 24, top: 0, width: 640, height: 320 }),
482
+ );
483
+ const targetBlock = target.editor.view.nodeDOM(0);
484
+ if (targetBlock instanceof HTMLElement) {
485
+ setRect(
486
+ targetBlock,
487
+ makeRect({ left: 24, top: 0, width: 640, height: 280 }),
488
+ );
489
+ }
490
+ const source = mountEditor(
491
+ "<p>Nested move</p><p>Nested keep</p>",
492
+ {},
493
+ makeRect({ left: 48, top: 160, width: 300, height: 120 }),
494
+ target.wrapper,
495
+ { x: 60, y: 172 },
496
+ );
497
+
498
+ try {
499
+ hoverAt(60, 172);
500
+ source.handle.dispatchEvent(
501
+ new MouseEvent("mousedown", {
502
+ bubbles: true,
503
+ button: 0,
504
+ clientX: 60,
505
+ clientY: 172,
506
+ }),
507
+ );
508
+ document.dispatchEvent(
509
+ new MouseEvent("mousemove", {
510
+ bubbles: true,
511
+ clientX: 32,
512
+ clientY: 12,
513
+ }),
514
+ );
515
+ document.dispatchEvent(
516
+ new MouseEvent("mouseup", {
517
+ bubbles: true,
518
+ button: 0,
519
+ clientX: 32,
520
+ clientY: 12,
521
+ }),
522
+ );
523
+
524
+ expect(source.editor.state.doc.childCount).toBe(1);
525
+ expect(childText(source.editor, 0)).toBe("Nested keep");
526
+ expect(target.editor.state.doc.childCount).toBe(2);
527
+ expect(childText(target.editor, 0)).toBe("Nested move");
528
+ expect(childText(target.editor, 1)).toBe("Outer target");
529
+ } finally {
530
+ source.editor.destroy();
531
+ target.editor.destroy();
532
+ }
533
+ });
534
+
535
+ it("keeps a non-left-aligned block's grip alive while the cursor approaches it", () => {
536
+ // A right column / tab body renders its grip in a gap, NOT the page-left
537
+ // gutter. Moving the cursor from that block's body toward its grip crosses
538
+ // into the neighbour's wide forgiving zone (and even its content), so the
539
+ // plain picker flips hover to the neighbour and the grip vanishes before the
540
+ // cursor reaches it. The keepalive must hold the grip across that approach.
541
+ // Here the right grip glyph sits at x:320–344 — LEFT of the 28px gutter band
542
+ // and inside the left region's content [24,336], so without keepalive the
543
+ // left region wins at x=330 and the right grip disappears.
544
+ const left = mountEditor(
545
+ "<p>Left region</p>",
546
+ {},
547
+ makeRect({ left: 24, top: 160, width: 312, height: 90 }),
548
+ document.body,
549
+ { x: 100, y: 172 },
550
+ );
551
+ setRect(
552
+ left.editor.view.nodeDOM(0) as HTMLElement,
553
+ makeRect({ left: 24, top: 160, width: 312, height: 24 }),
554
+ );
555
+ const right = mountEditor(
556
+ "<p>Right region</p>",
557
+ {},
558
+ makeRect({ left: 360, top: 160, width: 300, height: 90 }),
559
+ document.body,
560
+ { x: 500, y: 172 },
561
+ );
562
+ setRect(
563
+ right.editor.view.nodeDOM(0) as HTMLElement,
564
+ makeRect({ left: 360, top: 160, width: 300, height: 24 }),
565
+ );
566
+ // The right block's grip glyph, rendered in the inter-region gap.
567
+ setRect(
568
+ right.handle,
569
+ makeRect({ left: 320, top: 162, width: 24, height: 24 }),
570
+ );
571
+
572
+ try {
573
+ // Over the right block's body → its grip shows.
574
+ hoverAt(500, 172);
575
+ expect(right.handle.style.display).toBe("flex");
576
+
577
+ // Cursor moved left onto the right block's grip glyph (x=330). This is
578
+ // inside the LEFT region's content, so the plain picker would hand hover to
579
+ // the left region — the keepalive must keep the RIGHT grip visible.
580
+ hoverAt(330, 172);
581
+ expect(right.handle.style.display).toBe("flex");
582
+ expect(left.handle.style.display).toBe("none");
583
+ } finally {
584
+ right.editor.destroy();
585
+ left.editor.destroy();
586
+ }
587
+ });
588
+ });