@dxos/ui-editor 0.9.1-staging.ee54ba693a → 0.11.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 (551) hide show
  1. package/dist/lib/chunk-diff.mjs +935 -0
  2. package/dist/lib/chunk-diff.mjs.map +1 -0
  3. package/dist/lib/headless.mjs +2 -0
  4. package/dist/lib/index.mjs +9937 -0
  5. package/dist/lib/index.mjs.map +1 -0
  6. package/dist/lib/types.mjs +18 -0
  7. package/dist/lib/types.mjs.map +1 -0
  8. package/dist/types/src/extensions/{bookmarks.d.ts → behavior/bookmarks.d.ts} +3 -0
  9. package/dist/types/src/extensions/behavior/bookmarks.d.ts.map +1 -0
  10. package/dist/types/src/extensions/behavior/dnd.d.ts.map +1 -0
  11. package/dist/types/src/extensions/behavior/index.d.ts +6 -0
  12. package/dist/types/src/extensions/behavior/index.d.ts.map +1 -0
  13. package/dist/types/src/extensions/{listener.d.ts → behavior/listener.d.ts} +3 -0
  14. package/dist/types/src/extensions/behavior/listener.d.ts.map +1 -0
  15. package/dist/types/src/extensions/{spacing.d.ts → behavior/spacing.d.ts} +3 -0
  16. package/dist/types/src/extensions/behavior/spacing.d.ts.map +1 -0
  17. package/dist/types/src/extensions/behavior/submit.d.ts.map +1 -0
  18. package/dist/types/src/extensions/collab/automerge/automerge.d.ts.map +1 -0
  19. package/dist/types/src/extensions/collab/automerge/automerge.test.d.ts.map +1 -0
  20. package/dist/types/src/extensions/{automerge → collab/automerge}/cursor.d.ts +1 -1
  21. package/dist/types/src/extensions/collab/automerge/cursor.d.ts.map +1 -0
  22. package/dist/types/src/extensions/{automerge → collab/automerge}/defs.d.ts +3 -1
  23. package/dist/types/src/extensions/collab/automerge/defs.d.ts.map +1 -0
  24. package/dist/types/src/extensions/collab/automerge/index.d.ts +3 -0
  25. package/dist/types/src/extensions/collab/automerge/index.d.ts.map +1 -0
  26. package/dist/types/src/extensions/collab/automerge/sync.d.ts.map +1 -0
  27. package/dist/types/src/extensions/{automerge → collab/automerge}/update-automerge.d.ts +4 -0
  28. package/dist/types/src/extensions/collab/automerge/update-automerge.d.ts.map +1 -0
  29. package/dist/types/src/extensions/{automerge → collab/automerge}/update-codemirror.d.ts +4 -0
  30. package/dist/types/src/extensions/collab/automerge/update-codemirror.d.ts.map +1 -0
  31. package/dist/types/src/extensions/collab/awareness/awareness-provider.d.ts.map +1 -0
  32. package/dist/types/src/extensions/{awareness → collab/awareness}/awareness.d.ts +4 -1
  33. package/dist/types/src/extensions/collab/awareness/awareness.d.ts.map +1 -0
  34. package/dist/types/src/extensions/collab/awareness/index.d.ts.map +1 -0
  35. package/dist/types/src/extensions/collab/index.d.ts +3 -0
  36. package/dist/types/src/extensions/collab/index.d.ts.map +1 -0
  37. package/dist/types/src/extensions/completion/autocomplete.d.ts.map +1 -0
  38. package/dist/types/src/extensions/{autocomplete → completion}/index.d.ts +1 -0
  39. package/dist/types/src/extensions/completion/index.d.ts.map +1 -0
  40. package/dist/types/src/extensions/{autocomplete → completion}/match.d.ts +2 -2
  41. package/dist/types/src/extensions/completion/match.d.ts.map +1 -0
  42. package/dist/types/src/extensions/{mention.d.ts → completion/mention.d.ts} +3 -0
  43. package/dist/types/src/extensions/completion/mention.d.ts.map +1 -0
  44. package/dist/types/src/extensions/completion/placeholder.d.ts.map +1 -0
  45. package/dist/types/src/extensions/{autocomplete → completion}/typeahead.d.ts +2 -2
  46. package/dist/types/src/extensions/completion/typeahead.d.ts.map +1 -0
  47. package/dist/types/src/extensions/{factories.d.ts → core/factories.d.ts} +23 -2
  48. package/dist/types/src/extensions/core/factories.d.ts.map +1 -0
  49. package/dist/types/src/extensions/core/factories.test.d.ts.map +1 -0
  50. package/dist/types/src/extensions/core/index.d.ts +3 -0
  51. package/dist/types/src/extensions/core/index.d.ts.map +1 -0
  52. package/dist/types/src/extensions/{modes.d.ts → core/modes.d.ts} +3 -0
  53. package/dist/types/src/extensions/core/modes.d.ts.map +1 -0
  54. package/dist/types/src/extensions/{debug.d.ts → debug/debug.d.ts} +3 -0
  55. package/dist/types/src/extensions/debug/debug.d.ts.map +1 -0
  56. package/dist/types/src/extensions/debug/index.d.ts +2 -0
  57. package/dist/types/src/extensions/debug/index.d.ts.map +1 -0
  58. package/dist/types/src/extensions/{annotations.d.ts → decoration/annotations.d.ts} +1 -1
  59. package/dist/types/src/extensions/decoration/annotations.d.ts.map +1 -0
  60. package/dist/types/src/extensions/decoration/assistant.d.ts +27 -0
  61. package/dist/types/src/extensions/decoration/assistant.d.ts.map +1 -0
  62. package/dist/types/src/extensions/decoration/fader.d.ts.map +1 -0
  63. package/dist/types/src/extensions/decoration/hashtag.d.ts +6 -0
  64. package/dist/types/src/extensions/decoration/hashtag.d.ts.map +1 -0
  65. package/dist/types/src/extensions/decoration/index.d.ts +8 -0
  66. package/dist/types/src/extensions/decoration/index.d.ts.map +1 -0
  67. package/dist/types/src/extensions/decoration/marker.d.ts.map +1 -0
  68. package/dist/types/src/extensions/{pos.d.ts → decoration/pos.d.ts} +40 -14
  69. package/dist/types/src/extensions/decoration/pos.d.ts.map +1 -0
  70. package/dist/types/src/extensions/decoration/pos.test.d.ts.map +1 -0
  71. package/dist/types/src/extensions/{replacer.d.ts → decoration/replacer.d.ts} +8 -8
  72. package/dist/types/src/extensions/decoration/replacer.d.ts.map +1 -0
  73. package/dist/types/src/extensions/decoration/replacer.test.d.ts.map +1 -0
  74. package/dist/types/src/extensions/{blast.d.ts → demo/blast.d.ts} +3 -0
  75. package/dist/types/src/extensions/demo/blast.d.ts.map +1 -0
  76. package/dist/types/src/extensions/demo/index.d.ts +3 -0
  77. package/dist/types/src/extensions/demo/index.d.ts.map +1 -0
  78. package/dist/types/src/extensions/demo/snippets.d.ts.map +1 -0
  79. package/dist/types/src/extensions/index.d.ts +10 -33
  80. package/dist/types/src/extensions/index.d.ts.map +1 -1
  81. package/dist/types/src/extensions/language/index.d.ts +5 -0
  82. package/dist/types/src/extensions/language/index.d.ts.map +1 -0
  83. package/dist/types/src/extensions/language/json/index.d.ts +2 -0
  84. package/dist/types/src/extensions/language/json/index.d.ts.map +1 -0
  85. package/dist/types/src/extensions/language/json/json.d.ts +12 -0
  86. package/dist/types/src/extensions/language/json/json.d.ts.map +1 -0
  87. package/dist/types/src/extensions/language/markdown/action.d.ts.map +1 -0
  88. package/dist/types/src/extensions/language/markdown/bundle.d.ts.map +1 -0
  89. package/dist/types/src/extensions/{markdown → language/markdown}/changes.d.ts +1 -1
  90. package/dist/types/src/extensions/language/markdown/changes.d.ts.map +1 -0
  91. package/dist/types/src/extensions/language/markdown/changes.test.d.ts.map +1 -0
  92. package/dist/types/src/extensions/{markdown → language/markdown}/debug.d.ts +3 -0
  93. package/dist/types/src/extensions/language/markdown/debug.d.ts.map +1 -0
  94. package/dist/types/src/extensions/{markdown → language/markdown}/decorate.d.ts +5 -1
  95. package/dist/types/src/extensions/language/markdown/decorate.d.ts.map +1 -0
  96. package/dist/types/src/extensions/{markdown → language/markdown}/formatting.d.ts +11 -2
  97. package/dist/types/src/extensions/language/markdown/formatting.d.ts.map +1 -0
  98. package/dist/types/src/extensions/language/markdown/formatting.test.d.ts.map +1 -0
  99. package/dist/types/src/extensions/{markdown → language/markdown}/highlight.d.ts +16 -12
  100. package/dist/types/src/extensions/language/markdown/highlight.d.ts.map +1 -0
  101. package/dist/types/src/extensions/language/markdown/image.d.ts.map +1 -0
  102. package/dist/types/src/extensions/language/markdown/image.test.d.ts.map +1 -0
  103. package/dist/types/src/extensions/language/markdown/index.d.ts.map +1 -0
  104. package/dist/types/src/extensions/language/markdown/link.d.ts +25 -0
  105. package/dist/types/src/extensions/language/markdown/link.d.ts.map +1 -0
  106. package/dist/types/src/extensions/language/markdown/link.test.d.ts +2 -0
  107. package/dist/types/src/extensions/language/markdown/link.test.d.ts.map +1 -0
  108. package/dist/types/src/extensions/language/markdown/parser.test.d.ts.map +1 -0
  109. package/dist/types/src/extensions/{markdown → language/markdown}/styles.d.ts +3 -0
  110. package/dist/types/src/extensions/language/markdown/styles.d.ts.map +1 -0
  111. package/dist/types/src/extensions/language/markdown/table.d.ts.map +1 -0
  112. package/dist/types/src/extensions/language/mermaid/index.d.ts +2 -0
  113. package/dist/types/src/extensions/language/mermaid/index.d.ts.map +1 -0
  114. package/dist/types/src/extensions/language/mermaid/mermaid.d.ts +18 -0
  115. package/dist/types/src/extensions/language/mermaid/mermaid.d.ts.map +1 -0
  116. package/dist/types/src/extensions/language/xml/extended-markdown.d.ts.map +1 -0
  117. package/dist/types/src/extensions/language/xml/extended-markdown.test.d.ts.map +1 -0
  118. package/dist/types/src/extensions/language/xml/index.d.ts.map +1 -0
  119. package/dist/types/src/extensions/language/xml/stub.d.ts +76 -0
  120. package/dist/types/src/extensions/language/xml/stub.d.ts.map +1 -0
  121. package/dist/types/src/extensions/language/xml/widgets/anchor.d.ts.map +1 -0
  122. package/dist/types/src/extensions/language/xml/widgets/index.d.ts.map +1 -0
  123. package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts +48 -0
  124. package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts.map +1 -0
  125. package/dist/types/src/extensions/language/xml/xml-formatting.d.ts.map +1 -0
  126. package/dist/types/src/extensions/{tags → language/xml}/xml-tags.d.ts +12 -1
  127. package/dist/types/src/extensions/language/xml/xml-tags.d.ts.map +1 -0
  128. package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts +2 -0
  129. package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts.map +1 -0
  130. package/dist/types/src/extensions/language/xml/xml-util.d.ts.map +1 -0
  131. package/dist/types/src/extensions/language/xml/xml-util.test.d.ts.map +1 -0
  132. package/dist/types/src/extensions/review/change-bar.d.ts +29 -0
  133. package/dist/types/src/extensions/review/change-bar.d.ts.map +1 -0
  134. package/dist/types/src/extensions/{comments.d.ts → review/comments.d.ts} +56 -38
  135. package/dist/types/src/extensions/review/comments.d.ts.map +1 -0
  136. package/dist/types/src/extensions/review/comments.test.d.ts +2 -0
  137. package/dist/types/src/extensions/review/comments.test.d.ts.map +1 -0
  138. package/dist/types/src/extensions/review/diff.d.ts +131 -0
  139. package/dist/types/src/extensions/review/diff.d.ts.map +1 -0
  140. package/dist/types/src/extensions/review/diff.test.d.ts +2 -0
  141. package/dist/types/src/extensions/review/diff.test.d.ts.map +1 -0
  142. package/dist/types/src/extensions/review/index.d.ts +7 -0
  143. package/dist/types/src/extensions/review/index.d.ts.map +1 -0
  144. package/dist/types/src/extensions/review/suggest.d.ts +89 -0
  145. package/dist/types/src/extensions/review/suggest.d.ts.map +1 -0
  146. package/dist/types/src/extensions/review/suggest.test.d.ts +2 -0
  147. package/dist/types/src/extensions/review/suggest.test.d.ts.map +1 -0
  148. package/dist/types/src/extensions/review/suggestions-overlay.d.ts +25 -0
  149. package/dist/types/src/extensions/review/suggestions-overlay.d.ts.map +1 -0
  150. package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts +2 -0
  151. package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts.map +1 -0
  152. package/dist/types/src/extensions/review/track-changes.d.ts +27 -0
  153. package/dist/types/src/extensions/review/track-changes.d.ts.map +1 -0
  154. package/dist/types/src/extensions/review/track-changes.test.d.ts +2 -0
  155. package/dist/types/src/extensions/review/track-changes.test.d.ts.map +1 -0
  156. package/dist/types/src/extensions/{busy.d.ts → state/busy.d.ts} +1 -0
  157. package/dist/types/src/extensions/state/busy.d.ts.map +1 -0
  158. package/dist/types/src/extensions/{focus.d.ts → state/focus.d.ts} +4 -0
  159. package/dist/types/src/extensions/state/focus.d.ts.map +1 -0
  160. package/dist/types/src/extensions/state/index.d.ts +5 -0
  161. package/dist/types/src/extensions/state/index.d.ts.map +1 -0
  162. package/dist/types/src/extensions/state/modal.d.ts.map +1 -0
  163. package/dist/types/src/extensions/{selection.d.ts → state/selection.d.ts} +1 -1
  164. package/dist/types/src/extensions/state/selection.d.ts.map +1 -0
  165. package/dist/types/src/extensions/state/selection.test.d.ts.map +1 -0
  166. package/dist/types/src/extensions/streaming/index.d.ts +4 -0
  167. package/dist/types/src/extensions/streaming/index.d.ts.map +1 -0
  168. package/dist/types/src/extensions/streaming/pending/index.d.ts.map +1 -0
  169. package/dist/types/src/extensions/streaming/pending/pending-text-stream.d.ts.map +1 -0
  170. package/dist/types/src/extensions/streaming/pending/pending-text-stream.test.d.ts.map +1 -0
  171. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.d.ts +1 -0
  172. package/dist/types/src/extensions/streaming/pending/pending-text.d.ts.map +1 -0
  173. package/dist/types/src/extensions/streaming/pending/pending-text.test.d.ts.map +1 -0
  174. package/dist/types/src/extensions/streaming/scrolling/auto-scroll.d.ts.map +1 -0
  175. package/dist/types/src/extensions/streaming/scrolling/crawler.d.ts.map +1 -0
  176. package/dist/types/src/extensions/streaming/scrolling/index.d.ts.map +1 -0
  177. package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts +4 -0
  178. package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts.map +1 -0
  179. package/dist/types/src/extensions/streaming/scrolling/scrollbar-autohide.d.ts.map +1 -0
  180. package/dist/types/src/extensions/streaming/scrolling/scroller.d.ts.map +1 -0
  181. package/dist/types/src/extensions/{typewriter.d.ts → streaming/typewriter.d.ts} +12 -12
  182. package/dist/types/src/extensions/streaming/typewriter.d.ts.map +1 -0
  183. package/dist/types/src/extensions/streaming/typewriter.test.d.ts.map +1 -0
  184. package/dist/types/src/extensions/structure/blocks/blocks.d.ts +52 -0
  185. package/dist/types/src/extensions/structure/blocks/blocks.d.ts.map +1 -0
  186. package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts +2 -0
  187. package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts.map +1 -0
  188. package/dist/types/src/extensions/structure/blocks/drag.d.ts +57 -0
  189. package/dist/types/src/extensions/structure/blocks/drag.d.ts.map +1 -0
  190. package/dist/types/src/extensions/structure/blocks/index.d.ts +5 -0
  191. package/dist/types/src/extensions/structure/blocks/index.d.ts.map +1 -0
  192. package/dist/types/src/extensions/structure/blocks/selection.d.ts +47 -0
  193. package/dist/types/src/extensions/structure/blocks/selection.d.ts.map +1 -0
  194. package/dist/types/src/extensions/structure/blocks/selection.test.d.ts +2 -0
  195. package/dist/types/src/extensions/structure/blocks/selection.test.d.ts.map +1 -0
  196. package/dist/types/src/extensions/structure/blocks/types.d.ts +17 -0
  197. package/dist/types/src/extensions/structure/blocks/types.d.ts.map +1 -0
  198. package/dist/types/src/extensions/structure/folding/folding.d.ts.map +1 -0
  199. package/dist/types/src/extensions/structure/folding/index.d.ts +3 -0
  200. package/dist/types/src/extensions/structure/folding/index.d.ts.map +1 -0
  201. package/dist/types/src/extensions/structure/folding/turns.d.ts +55 -0
  202. package/dist/types/src/extensions/structure/folding/turns.d.ts.map +1 -0
  203. package/dist/types/src/extensions/structure/folding/turns.test.d.ts +2 -0
  204. package/dist/types/src/extensions/structure/folding/turns.test.d.ts.map +1 -0
  205. package/dist/types/src/extensions/structure/index.d.ts +4 -0
  206. package/dist/types/src/extensions/structure/index.d.ts.map +1 -0
  207. package/dist/types/src/extensions/structure/outliner/commands.d.ts +31 -0
  208. package/dist/types/src/extensions/structure/outliner/commands.d.ts.map +1 -0
  209. package/dist/types/src/extensions/structure/outliner/dnd.d.ts +43 -0
  210. package/dist/types/src/extensions/structure/outliner/dnd.d.ts.map +1 -0
  211. package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts +2 -0
  212. package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts.map +1 -0
  213. package/dist/types/src/extensions/structure/outliner/editor.d.ts.map +1 -0
  214. package/dist/types/src/extensions/structure/outliner/editor.test.d.ts.map +1 -0
  215. package/dist/types/src/extensions/{outliner → structure/outliner}/index.d.ts +1 -0
  216. package/dist/types/src/extensions/structure/outliner/index.d.ts.map +1 -0
  217. package/dist/types/src/extensions/{outliner → structure/outliner}/menu.d.ts +1 -0
  218. package/dist/types/src/extensions/structure/outliner/menu.d.ts.map +1 -0
  219. package/dist/types/src/extensions/structure/outliner/outliner.d.ts.map +1 -0
  220. package/dist/types/src/extensions/structure/outliner/outliner.test.d.ts.map +1 -0
  221. package/dist/types/src/extensions/{outliner → structure/outliner}/tree.d.ts +1 -1
  222. package/dist/types/src/extensions/structure/outliner/tree.d.ts.map +1 -0
  223. package/dist/types/src/extensions/structure/outliner/tree.test.d.ts.map +1 -0
  224. package/dist/types/src/headless.d.ts +4 -0
  225. package/dist/types/src/headless.d.ts.map +1 -0
  226. package/dist/types/src/styles/theme.d.ts +1 -1
  227. package/dist/types/src/styles/theme.d.ts.map +1 -1
  228. package/dist/types/src/util/util.d.ts +2 -0
  229. package/dist/types/src/util/util.d.ts.map +1 -1
  230. package/dist/types/tsconfig.tsbuildinfo +1 -1
  231. package/package.json +48 -42
  232. package/src/defaults.ts +1 -1
  233. package/src/extensions/{bookmarks.ts → behavior/bookmarks.ts} +24 -14
  234. package/src/extensions/behavior/index.ts +9 -0
  235. package/src/extensions/{listener.ts → behavior/listener.ts} +4 -1
  236. package/src/extensions/{spacing.ts → behavior/spacing.ts} +8 -2
  237. package/src/extensions/{submit.ts → behavior/submit.ts} +2 -10
  238. package/src/extensions/{automerge → collab/automerge}/automerge.test.tsx +4 -4
  239. package/src/extensions/{automerge → collab/automerge}/automerge.ts +7 -4
  240. package/src/extensions/{automerge → collab/automerge}/cursor.ts +1 -1
  241. package/src/extensions/{automerge → collab/automerge}/defs.ts +4 -1
  242. package/src/extensions/{automerge → collab/automerge}/index.ts +1 -0
  243. package/src/extensions/{automerge → collab/automerge}/sync.ts +1 -2
  244. package/src/extensions/{automerge → collab/automerge}/update-automerge.ts +4 -0
  245. package/src/extensions/{automerge → collab/automerge}/update-codemirror.ts +5 -1
  246. package/src/extensions/{awareness → collab/awareness}/awareness-provider.ts +3 -4
  247. package/src/extensions/{awareness → collab/awareness}/awareness.ts +19 -16
  248. package/src/extensions/collab/index.ts +6 -0
  249. package/src/extensions/{autocomplete → completion}/autocomplete.ts +7 -5
  250. package/src/extensions/{autocomplete → completion}/index.ts +2 -1
  251. package/src/extensions/{autocomplete → completion}/match.ts +2 -2
  252. package/src/extensions/{mention.ts → completion/mention.ts} +3 -0
  253. package/src/extensions/{autocomplete → completion}/placeholder.ts +1 -1
  254. package/src/extensions/completion/typeahead.ts +97 -0
  255. package/src/extensions/{factories.ts → core/factories.ts} +35 -13
  256. package/src/extensions/core/index.ts +6 -0
  257. package/src/extensions/{modes.ts → core/modes.ts} +4 -1
  258. package/src/extensions/{debug.ts → debug/debug.ts} +3 -0
  259. package/src/extensions/debug/index.ts +5 -0
  260. package/src/extensions/{annotations.ts → decoration/annotations.ts} +12 -10
  261. package/src/extensions/decoration/assistant.ts +225 -0
  262. package/src/extensions/{fader.ts → decoration/fader.ts} +17 -19
  263. package/src/extensions/{hashtag.ts → decoration/hashtag.ts} +35 -28
  264. package/src/extensions/decoration/index.ts +11 -0
  265. package/src/extensions/{marker.ts → decoration/marker.ts} +18 -18
  266. package/src/extensions/{pos.test.ts → decoration/pos.test.ts} +57 -1
  267. package/src/extensions/{pos.ts → decoration/pos.ts} +165 -31
  268. package/src/extensions/{blast.ts → demo/blast.ts} +3 -0
  269. package/src/extensions/demo/index.ts +6 -0
  270. package/src/extensions/{snippets.ts → demo/snippets.ts} +5 -1
  271. package/src/extensions/index.ts +10 -33
  272. package/src/extensions/language/index.ts +8 -0
  273. package/src/extensions/language/json/index.ts +5 -0
  274. package/src/extensions/{json.ts → language/json/json.ts} +11 -6
  275. package/src/extensions/{markdown → language/markdown}/action.ts +1 -1
  276. package/src/extensions/{markdown → language/markdown}/bundle.ts +7 -1
  277. package/src/extensions/{markdown → language/markdown}/changes.test.ts +2 -2
  278. package/src/extensions/{markdown → language/markdown}/changes.ts +2 -2
  279. package/src/extensions/{markdown → language/markdown}/debug.ts +3 -0
  280. package/src/extensions/{markdown → language/markdown}/decorate.ts +96 -85
  281. package/src/extensions/{markdown → language/markdown}/formatting.test.ts +88 -1
  282. package/src/extensions/{markdown → language/markdown}/formatting.ts +193 -74
  283. package/src/extensions/{markdown → language/markdown}/highlight.ts +42 -41
  284. package/src/extensions/{markdown → language/markdown}/image.test.ts +1 -1
  285. package/src/extensions/{markdown → language/markdown}/image.ts +6 -6
  286. package/src/extensions/language/markdown/link.test.ts +58 -0
  287. package/src/extensions/language/markdown/link.ts +109 -0
  288. package/src/extensions/{markdown → language/markdown}/parser.test.ts +0 -28
  289. package/src/extensions/{markdown → language/markdown}/styles.ts +38 -7
  290. package/src/extensions/{markdown → language/markdown}/table.ts +5 -5
  291. package/src/extensions/language/mermaid/index.ts +5 -0
  292. package/src/extensions/language/mermaid/mermaid.ts +44 -0
  293. package/src/extensions/{tags → language/xml}/DESIGN.md +3 -3
  294. package/src/extensions/{tags → language/xml}/extended-markdown.test.ts +2 -1
  295. package/src/extensions/{tags → language/xml}/extended-markdown.ts +2 -4
  296. package/src/extensions/language/xml/stub.ts +233 -0
  297. package/src/extensions/language/xml/xml-block-decoration.ts +165 -0
  298. package/src/extensions/{tags → language/xml}/xml-formatting.ts +54 -38
  299. package/src/extensions/language/xml/xml-tags.test.ts +588 -0
  300. package/src/extensions/{tags → language/xml}/xml-tags.ts +165 -66
  301. package/src/extensions/{tags → language/xml}/xml-util.test.ts +2 -2
  302. package/src/extensions/review/change-bar.ts +94 -0
  303. package/src/extensions/review/comments.test.ts +72 -0
  304. package/src/extensions/{comments.ts → review/comments.ts} +395 -267
  305. package/src/extensions/review/diff.test.ts +405 -0
  306. package/src/extensions/review/diff.ts +502 -0
  307. package/src/extensions/review/index.ts +10 -0
  308. package/src/extensions/review/suggest.test.ts +170 -0
  309. package/src/extensions/review/suggest.ts +512 -0
  310. package/src/extensions/review/suggestions-overlay.test.ts +58 -0
  311. package/src/extensions/review/suggestions-overlay.ts +50 -0
  312. package/src/extensions/review/track-changes.test.ts +101 -0
  313. package/src/extensions/review/track-changes.ts +215 -0
  314. package/src/extensions/{busy.ts → state/busy.ts} +1 -0
  315. package/src/extensions/{focus.ts → state/focus.ts} +4 -0
  316. package/src/extensions/state/index.ts +8 -0
  317. package/src/extensions/{selection.ts → state/selection.ts} +3 -3
  318. package/src/extensions/streaming/index.ts +7 -0
  319. package/src/extensions/{pending → streaming/pending}/pending-text-stream.ts +10 -3
  320. package/src/extensions/{pending → streaming/pending}/pending-text.test.ts +1 -1
  321. package/src/extensions/{pending → streaming/pending}/pending-text.ts +20 -19
  322. package/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.ts +9 -6
  323. package/src/extensions/{scrolling → streaming/scrolling}/scroll-past-end.ts +1 -0
  324. package/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.ts +13 -11
  325. package/src/extensions/{typewriter.ts → streaming/typewriter.ts} +46 -47
  326. package/src/extensions/structure/blocks/DESIGN.md +134 -0
  327. package/src/extensions/structure/blocks/blocks.test.ts +147 -0
  328. package/src/extensions/structure/blocks/blocks.ts +219 -0
  329. package/src/extensions/structure/blocks/drag.ts +996 -0
  330. package/src/extensions/structure/blocks/index.ts +8 -0
  331. package/src/extensions/structure/blocks/selection.test.ts +56 -0
  332. package/src/extensions/structure/blocks/selection.ts +279 -0
  333. package/src/extensions/structure/blocks/types.ts +22 -0
  334. package/src/extensions/{folding.ts → structure/folding/folding.ts} +12 -10
  335. package/src/extensions/structure/folding/index.ts +6 -0
  336. package/src/extensions/structure/folding/turns.test.ts +72 -0
  337. package/src/extensions/structure/folding/turns.ts +209 -0
  338. package/src/extensions/structure/index.ts +7 -0
  339. package/src/extensions/structure/outliner/DESIGN.md +183 -0
  340. package/src/extensions/structure/outliner/SPEC.mdl +305 -0
  341. package/src/extensions/{outliner → structure/outliner}/commands.ts +125 -42
  342. package/src/extensions/structure/outliner/dnd.test.ts +142 -0
  343. package/src/extensions/structure/outliner/dnd.ts +375 -0
  344. package/src/extensions/structure/outliner/editor.test.ts +85 -0
  345. package/src/extensions/{outliner → structure/outliner}/editor.ts +14 -9
  346. package/src/extensions/{outliner → structure/outliner}/index.ts +1 -0
  347. package/src/extensions/{outliner → structure/outliner}/menu.ts +48 -25
  348. package/src/extensions/structure/outliner/outliner.test.ts +193 -0
  349. package/src/extensions/structure/outliner/outliner.ts +183 -0
  350. package/src/extensions/{outliner → structure/outliner}/tree.test.ts +2 -2
  351. package/src/extensions/{outliner → structure/outliner}/tree.ts +1 -1
  352. package/src/headless.ts +13 -0
  353. package/src/styles/theme.ts +3 -1
  354. package/src/util/util.ts +3 -0
  355. package/dist/lib/browser/index.mjs +0 -9594
  356. package/dist/lib/browser/index.mjs.map +0 -7
  357. package/dist/lib/browser/meta.json +0 -1
  358. package/dist/lib/browser/types/index.mjs +0 -33
  359. package/dist/lib/browser/types/index.mjs.map +0 -7
  360. package/dist/lib/node-esm/index.mjs +0 -9596
  361. package/dist/lib/node-esm/index.mjs.map +0 -7
  362. package/dist/lib/node-esm/meta.json +0 -1
  363. package/dist/lib/node-esm/types/index.mjs +0 -35
  364. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  365. package/dist/types/src/extensions/annotations.d.ts.map +0 -1
  366. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +0 -1
  367. package/dist/types/src/extensions/autocomplete/index.d.ts.map +0 -1
  368. package/dist/types/src/extensions/autocomplete/match.d.ts.map +0 -1
  369. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +0 -1
  370. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +0 -1
  371. package/dist/types/src/extensions/automerge/automerge.d.ts.map +0 -1
  372. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +0 -1
  373. package/dist/types/src/extensions/automerge/cursor.d.ts.map +0 -1
  374. package/dist/types/src/extensions/automerge/defs.d.ts.map +0 -1
  375. package/dist/types/src/extensions/automerge/index.d.ts +0 -2
  376. package/dist/types/src/extensions/automerge/index.d.ts.map +0 -1
  377. package/dist/types/src/extensions/automerge/sync.d.ts.map +0 -1
  378. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +0 -1
  379. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +0 -1
  380. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +0 -1
  381. package/dist/types/src/extensions/awareness/awareness.d.ts.map +0 -1
  382. package/dist/types/src/extensions/awareness/index.d.ts.map +0 -1
  383. package/dist/types/src/extensions/blast.d.ts.map +0 -1
  384. package/dist/types/src/extensions/blocks.d.ts +0 -2
  385. package/dist/types/src/extensions/blocks.d.ts.map +0 -1
  386. package/dist/types/src/extensions/bookmarks.d.ts.map +0 -1
  387. package/dist/types/src/extensions/busy.d.ts.map +0 -1
  388. package/dist/types/src/extensions/comments.d.ts.map +0 -1
  389. package/dist/types/src/extensions/debug.d.ts.map +0 -1
  390. package/dist/types/src/extensions/dnd.d.ts.map +0 -1
  391. package/dist/types/src/extensions/factories.d.ts.map +0 -1
  392. package/dist/types/src/extensions/factories.test.d.ts.map +0 -1
  393. package/dist/types/src/extensions/fader.d.ts.map +0 -1
  394. package/dist/types/src/extensions/focus.d.ts.map +0 -1
  395. package/dist/types/src/extensions/folding.d.ts.map +0 -1
  396. package/dist/types/src/extensions/hashtag.d.ts +0 -3
  397. package/dist/types/src/extensions/hashtag.d.ts.map +0 -1
  398. package/dist/types/src/extensions/json.d.ts +0 -7
  399. package/dist/types/src/extensions/json.d.ts.map +0 -1
  400. package/dist/types/src/extensions/listener.d.ts.map +0 -1
  401. package/dist/types/src/extensions/markdown/action.d.ts.map +0 -1
  402. package/dist/types/src/extensions/markdown/bundle.d.ts.map +0 -1
  403. package/dist/types/src/extensions/markdown/changes.d.ts.map +0 -1
  404. package/dist/types/src/extensions/markdown/changes.test.d.ts.map +0 -1
  405. package/dist/types/src/extensions/markdown/debug.d.ts.map +0 -1
  406. package/dist/types/src/extensions/markdown/decorate.d.ts.map +0 -1
  407. package/dist/types/src/extensions/markdown/formatting.d.ts.map +0 -1
  408. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +0 -1
  409. package/dist/types/src/extensions/markdown/highlight.d.ts.map +0 -1
  410. package/dist/types/src/extensions/markdown/image.d.ts.map +0 -1
  411. package/dist/types/src/extensions/markdown/image.test.d.ts.map +0 -1
  412. package/dist/types/src/extensions/markdown/index.d.ts.map +0 -1
  413. package/dist/types/src/extensions/markdown/link.d.ts +0 -7
  414. package/dist/types/src/extensions/markdown/link.d.ts.map +0 -1
  415. package/dist/types/src/extensions/markdown/parser.test.d.ts.map +0 -1
  416. package/dist/types/src/extensions/markdown/styles.d.ts.map +0 -1
  417. package/dist/types/src/extensions/markdown/table.d.ts.map +0 -1
  418. package/dist/types/src/extensions/marker.d.ts.map +0 -1
  419. package/dist/types/src/extensions/mention.d.ts.map +0 -1
  420. package/dist/types/src/extensions/modal.d.ts.map +0 -1
  421. package/dist/types/src/extensions/modes.d.ts.map +0 -1
  422. package/dist/types/src/extensions/outliner/commands.d.ts +0 -10
  423. package/dist/types/src/extensions/outliner/commands.d.ts.map +0 -1
  424. package/dist/types/src/extensions/outliner/editor.d.ts.map +0 -1
  425. package/dist/types/src/extensions/outliner/editor.test.d.ts.map +0 -1
  426. package/dist/types/src/extensions/outliner/index.d.ts.map +0 -1
  427. package/dist/types/src/extensions/outliner/menu.d.ts.map +0 -1
  428. package/dist/types/src/extensions/outliner/outliner.d.ts.map +0 -1
  429. package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +0 -1
  430. package/dist/types/src/extensions/outliner/selection.d.ts +0 -12
  431. package/dist/types/src/extensions/outliner/selection.d.ts.map +0 -1
  432. package/dist/types/src/extensions/outliner/tree.d.ts.map +0 -1
  433. package/dist/types/src/extensions/outliner/tree.test.d.ts.map +0 -1
  434. package/dist/types/src/extensions/pending/index.d.ts.map +0 -1
  435. package/dist/types/src/extensions/pending/pending-text-stream.d.ts.map +0 -1
  436. package/dist/types/src/extensions/pending/pending-text-stream.test.d.ts.map +0 -1
  437. package/dist/types/src/extensions/pending/pending-text.d.ts.map +0 -1
  438. package/dist/types/src/extensions/pending/pending-text.test.d.ts.map +0 -1
  439. package/dist/types/src/extensions/pos.d.ts.map +0 -1
  440. package/dist/types/src/extensions/pos.test.d.ts.map +0 -1
  441. package/dist/types/src/extensions/replacer.d.ts.map +0 -1
  442. package/dist/types/src/extensions/replacer.test.d.ts.map +0 -1
  443. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +0 -1
  444. package/dist/types/src/extensions/scrolling/crawler.d.ts.map +0 -1
  445. package/dist/types/src/extensions/scrolling/index.d.ts.map +0 -1
  446. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts +0 -3
  447. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +0 -1
  448. package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts.map +0 -1
  449. package/dist/types/src/extensions/scrolling/scroller.d.ts.map +0 -1
  450. package/dist/types/src/extensions/selection.d.ts.map +0 -1
  451. package/dist/types/src/extensions/selection.test.d.ts.map +0 -1
  452. package/dist/types/src/extensions/snippets.d.ts.map +0 -1
  453. package/dist/types/src/extensions/spacing.d.ts.map +0 -1
  454. package/dist/types/src/extensions/state.d.ts +0 -2
  455. package/dist/types/src/extensions/state.d.ts.map +0 -1
  456. package/dist/types/src/extensions/submit.d.ts.map +0 -1
  457. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +0 -1
  458. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +0 -1
  459. package/dist/types/src/extensions/tags/index.d.ts.map +0 -1
  460. package/dist/types/src/extensions/tags/stub.d.ts +0 -35
  461. package/dist/types/src/extensions/tags/stub.d.ts.map +0 -1
  462. package/dist/types/src/extensions/tags/widgets/anchor.d.ts.map +0 -1
  463. package/dist/types/src/extensions/tags/widgets/index.d.ts.map +0 -1
  464. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +0 -31
  465. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +0 -1
  466. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +0 -1
  467. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +0 -1
  468. package/dist/types/src/extensions/tags/xml-util.d.ts.map +0 -1
  469. package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +0 -1
  470. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  471. package/dist/types/src/extensions/typewriter.test.d.ts.map +0 -1
  472. package/src/extensions/autocomplete/typeahead.ts +0 -87
  473. package/src/extensions/blocks.ts +0 -131
  474. package/src/extensions/markdown/link.ts +0 -49
  475. package/src/extensions/outliner/editor.test.ts +0 -33
  476. package/src/extensions/outliner/outliner.test.ts +0 -99
  477. package/src/extensions/outliner/outliner.ts +0 -166
  478. package/src/extensions/outliner/selection.ts +0 -50
  479. package/src/extensions/state.ts +0 -7
  480. package/src/extensions/tags/stub.ts +0 -80
  481. package/src/extensions/tags/xml-block-decoration.ts +0 -123
  482. package/dist/types/src/extensions/{dnd.d.ts → behavior/dnd.d.ts} +0 -0
  483. package/dist/types/src/extensions/{submit.d.ts → behavior/submit.d.ts} +0 -0
  484. package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.d.ts +0 -0
  485. package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.test.d.ts +0 -0
  486. package/dist/types/src/extensions/{automerge → collab/automerge}/sync.d.ts +0 -0
  487. package/dist/types/src/extensions/{awareness → collab/awareness}/awareness-provider.d.ts +0 -0
  488. package/dist/types/src/extensions/{awareness → collab/awareness}/index.d.ts +0 -0
  489. package/dist/types/src/extensions/{autocomplete → completion}/autocomplete.d.ts +0 -0
  490. package/dist/types/src/extensions/{autocomplete → completion}/placeholder.d.ts +0 -0
  491. package/dist/types/src/extensions/{factories.test.d.ts → core/factories.test.d.ts} +0 -0
  492. package/dist/types/src/extensions/{fader.d.ts → decoration/fader.d.ts} +0 -0
  493. package/dist/types/src/extensions/{marker.d.ts → decoration/marker.d.ts} +0 -0
  494. package/dist/types/src/extensions/{pos.test.d.ts → decoration/pos.test.d.ts} +0 -0
  495. package/dist/types/src/extensions/{replacer.test.d.ts → decoration/replacer.test.d.ts} +0 -0
  496. package/dist/types/src/extensions/{snippets.d.ts → demo/snippets.d.ts} +0 -0
  497. package/dist/types/src/extensions/{markdown → language/markdown}/action.d.ts +0 -0
  498. package/dist/types/src/extensions/{markdown → language/markdown}/bundle.d.ts +0 -0
  499. package/dist/types/src/extensions/{markdown → language/markdown}/changes.test.d.ts +0 -0
  500. package/dist/types/src/extensions/{markdown → language/markdown}/formatting.test.d.ts +0 -0
  501. package/dist/types/src/extensions/{markdown → language/markdown}/image.d.ts +0 -0
  502. package/dist/types/src/extensions/{markdown → language/markdown}/image.test.d.ts +0 -0
  503. package/dist/types/src/extensions/{markdown → language/markdown}/index.d.ts +0 -0
  504. package/dist/types/src/extensions/{markdown → language/markdown}/parser.test.d.ts +0 -0
  505. package/dist/types/src/extensions/{markdown → language/markdown}/table.d.ts +0 -0
  506. package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.d.ts +0 -0
  507. package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.test.d.ts +0 -0
  508. package/dist/types/src/extensions/{tags → language/xml}/index.d.ts +0 -0
  509. package/dist/types/src/extensions/{tags → language/xml}/widgets/anchor.d.ts +0 -0
  510. package/dist/types/src/extensions/{tags → language/xml}/widgets/index.d.ts +0 -0
  511. package/dist/types/src/extensions/{tags → language/xml}/xml-formatting.d.ts +0 -0
  512. package/dist/types/src/extensions/{tags → language/xml}/xml-util.d.ts +0 -0
  513. package/dist/types/src/extensions/{tags → language/xml}/xml-util.test.d.ts +0 -0
  514. package/dist/types/src/extensions/{modal.d.ts → state/modal.d.ts} +0 -0
  515. package/dist/types/src/extensions/{selection.test.d.ts → state/selection.test.d.ts} +0 -0
  516. package/dist/types/src/extensions/{pending → streaming/pending}/index.d.ts +0 -0
  517. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.d.ts +0 -0
  518. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.test.d.ts +0 -0
  519. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.test.d.ts +0 -0
  520. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.d.ts +0 -0
  521. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/crawler.d.ts +0 -0
  522. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/index.d.ts +0 -0
  523. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.d.ts +0 -0
  524. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scroller.d.ts +0 -0
  525. package/dist/types/src/extensions/{typewriter.test.d.ts → streaming/typewriter.test.d.ts} +0 -0
  526. package/dist/types/src/extensions/{folding.d.ts → structure/folding/folding.d.ts} +0 -0
  527. package/dist/types/src/extensions/{outliner → structure/outliner}/editor.d.ts +0 -0
  528. package/dist/types/src/extensions/{outliner → structure/outliner}/editor.test.d.ts +0 -0
  529. package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.d.ts +0 -0
  530. package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.test.d.ts +0 -0
  531. package/dist/types/src/extensions/{outliner → structure/outliner}/tree.test.d.ts +0 -0
  532. package/src/extensions/{dnd.ts → behavior/dnd.ts} +29 -29
  533. package/src/extensions/{awareness → collab/awareness}/index.ts +0 -0
  534. package/src/extensions/{factories.test.ts → core/factories.test.ts} +0 -0
  535. package/src/extensions/{replacer.test.ts → decoration/replacer.test.ts} +0 -0
  536. package/src/extensions/{replacer.ts → decoration/replacer.ts} +25 -25
  537. /package/src/extensions/{markdown → language/markdown}/index.ts +0 -0
  538. /package/src/extensions/{tags → language/xml}/index.ts +0 -0
  539. /package/src/extensions/{tags → language/xml}/testing/text.md +0 -0
  540. /package/src/extensions/{tags → language/xml}/testing/text.txt +0 -0
  541. /package/src/extensions/{tags → language/xml}/widgets/anchor.ts +0 -0
  542. /package/src/extensions/{tags → language/xml}/widgets/index.ts +0 -0
  543. /package/src/extensions/{tags → language/xml}/xml-util.ts +0 -0
  544. /package/src/extensions/{modal.ts → state/modal.ts} +0 -0
  545. /package/src/extensions/{selection.test.ts → state/selection.test.ts} +0 -0
  546. /package/src/extensions/{pending → streaming/pending}/index.ts +0 -0
  547. /package/src/extensions/{pending → streaming/pending}/pending-text-stream.test.ts +0 -0
  548. /package/src/extensions/{scrolling → streaming/scrolling}/crawler.ts +0 -0
  549. /package/src/extensions/{scrolling → streaming/scrolling}/index.ts +0 -0
  550. /package/src/extensions/{scrolling → streaming/scrolling}/scroller.ts +0 -0
  551. /package/src/extensions/{typewriter.test.ts → streaming/typewriter.test.ts} +0 -0
@@ -0,0 +1,375 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { getIndentUnit } from '@codemirror/language';
6
+ import { type EditorState, type Extension } from '@codemirror/state';
7
+ import { type Command, EditorView } from '@codemirror/view';
8
+
9
+ import { bulletListIndentationWidth } from '../../language/markdown/styles';
10
+ import {
11
+ type Block,
12
+ type BlockOps,
13
+ blockSelectionField,
14
+ createBlockDrag,
15
+ createBlockSelection,
16
+ setBlockSelection,
17
+ } from '../blocks';
18
+ import { type Item, type Tree, getRange, treeFacet } from './tree';
19
+
20
+ /**
21
+ * Flattens the tree into draggable units in document order, one per item. Each unit spans only the
22
+ * item's own lines (up to its first child), so the ranges are non-overlapping and every item — parent
23
+ * or leaf — gets its own gutter handle. Memoized per state so the gutter and drag plugin share it.
24
+ */
25
+ const itemCache = new WeakMap<EditorState, Item[]>();
26
+ const flattenItems = (state: EditorState): Item[] => {
27
+ const cached = itemCache.get(state);
28
+ if (cached) {
29
+ return cached;
30
+ }
31
+
32
+ const tree = state.facet(treeFacet);
33
+ const items: Item[] = [];
34
+ tree.traverse((item) => {
35
+ items.push(item);
36
+ });
37
+
38
+ itemCache.set(state, items);
39
+ return items;
40
+ };
41
+
42
+ const getBlocks = (state: EditorState): Block[] =>
43
+ flattenItems(state).map((item) => ({
44
+ from: item.lineRange.from,
45
+ // The item's own lines end just before its first child (or at its line range end for a leaf).
46
+ to: item.children.length > 0 ? item.children[0].lineRange.from - 1 : item.lineRange.to,
47
+ }));
48
+
49
+ // The range a block visually occupies for selection/drag: its whole subtree (item + descendants), so
50
+ // selecting or dragging a parent covers its children. `getBlocks` still yields per-item own-lines.
51
+ // Exported for tests.
52
+ export const getExtent = (state: EditorState, block: Block): Block => {
53
+ const tree = state.facet(treeFacet);
54
+ const item = tree.find(block.from);
55
+ if (!item) {
56
+ return block;
57
+ }
58
+ const [from, to] = getRange(tree, item);
59
+ return { from, to };
60
+ };
61
+
62
+ /** Leading-whitespace width of the line at `pos` (indentation depth in characters). */
63
+ const indentAt = (state: EditorState, pos: number): number => {
64
+ const text = state.doc.lineAt(pos).text;
65
+ return text.length - text.trimStart().length;
66
+ };
67
+
68
+ /**
69
+ * Resolves the drop indentation for a single-item drag (see DESIGN "Drag reindent — drop level rules").
70
+ * The dragged subtree stays in the branch of the item above the drop slot by default, outdenting to a
71
+ * sibling of the (shallower) drop target only while the pointer is over that target's row. Returns the
72
+ * target leading-space count and the horizontal pixel offset that previews it. Exported for tests.
73
+ */
74
+ export const getDropIndent = (
75
+ view: EditorView,
76
+ sourceIndices: number[],
77
+ dropIndex: number,
78
+ pointerY: number,
79
+ ): { indent: number; offset: number } | null => {
80
+ // Multi-item group moves keep their own relative indentation (no single re-root).
81
+ if (sourceIndices.length !== 1) {
82
+ return null;
83
+ }
84
+
85
+ const { state } = view;
86
+ const items = flattenItems(state);
87
+ const count = items.length;
88
+ const unit = getIndentUnit(state);
89
+ const sourceItem = items[sourceIndices[0]];
90
+ const sourceIndent = indentAt(state, sourceItem.lineRange.from);
91
+
92
+ // The item directly above the drop slot, skipping the dragged subtree (the source's children are
93
+ // collapsed with it, so they cannot serve as the indent reference).
94
+ const [sourceFrom, sourceTo] = getRange(state.facet(treeFacet), sourceItem);
95
+ let aboveIndex = dropIndex - 1;
96
+ while (
97
+ aboveIndex >= 0 &&
98
+ items[aboveIndex].lineRange.from >= sourceFrom &&
99
+ items[aboveIndex].lineRange.from <= sourceTo
100
+ ) {
101
+ aboveIndex--;
102
+ }
103
+ const above = aboveIndex >= 0 ? items[aboveIndex] : null;
104
+ const target = dropIndex < count ? items[dropIndex] : null;
105
+
106
+ const aboveIndent = above ? indentAt(state, above.lineRange.from) : 0;
107
+ // Valid range for the slot: a sibling of the target (shallowest) up to a child of the item above.
108
+ const minIndent = target ? indentAt(state, target.lineRange.from) : 0;
109
+ const maxIndent = above ? aboveIndent + unit : minIndent;
110
+ const clamp = (value: number) => Math.max(minIndent, Math.min(maxIndent, value));
111
+
112
+ // Default: keep the dragged item at its own level (staying in its branch), clamped to the slot's range.
113
+ let indent = clamp(sourceIndent);
114
+ // Outdent to a sibling of the target only at a real branch boundary and while over the target row.
115
+ if (target && minIndent < indent) {
116
+ const top = view.coordsAtPos(target.lineRange.from)?.top;
117
+ if (top != null && pointerY >= top) {
118
+ indent = minIndent;
119
+ }
120
+ }
121
+
122
+ // Preview offset: one indentation width per indent unit of delta from the source's own indent.
123
+ const offset = ((indent - sourceIndent) / unit) * bulletListIndentationWidth;
124
+ return { indent, offset };
125
+ };
126
+
127
+ /**
128
+ * Re-roots `text` (a moved subtree) from `fromIndent` to `toIndent` by shifting every line's leading
129
+ * whitespace, preserving the subtree's internal relative indentation. Keeps the moved item valid at
130
+ * its new depth (e.g. dragging a nested task to the top level).
131
+ */
132
+ const reindent = (text: string, fromIndent: number, toIndent: number): string => {
133
+ const delta = toIndent - fromIndent;
134
+ if (delta === 0) {
135
+ return text;
136
+ }
137
+ return text
138
+ .split('\n')
139
+ .map((line) => {
140
+ const existing = line.length - line.trimStart().length;
141
+ const next = Math.max(0, existing + delta);
142
+ return ' '.repeat(next) + line.slice(existing);
143
+ })
144
+ .join('\n');
145
+ };
146
+
147
+ /**
148
+ * Moves the item at `sourceIndex` (with its whole subtree) to the slot before `dropIndex`, or to the
149
+ * end of the document when `dropIndex === items.length`. Expressed as a minimal delete + insert so it
150
+ * is a single undo step and syncs as a small edit.
151
+ */
152
+ const moveItem = (view: EditorView, sourceIndex: number, dropIndex: number, indent?: number): void => {
153
+ const { state } = view;
154
+ const tree: Tree = state.facet(treeFacet);
155
+ const items = flattenItems(state);
156
+ const count = items.length;
157
+ if (sourceIndex < 0 || sourceIndex >= count || dropIndex === sourceIndex) {
158
+ return;
159
+ }
160
+
161
+ const source = items[sourceIndex];
162
+ const [from, to] = getRange(tree, source);
163
+
164
+ // Insert before the drop-target item, or append at the end.
165
+ const insertAt = dropIndex < count ? items[dropIndex].lineRange.from : state.doc.length;
166
+ // Dropping inside the item's own subtree (including immediately after its own line) is a no-op.
167
+ if (insertAt > from && insertAt <= to) {
168
+ return;
169
+ }
170
+
171
+ const sourceIndent = indentAt(state, from);
172
+ // The drop level resolved live during the drag (`getDropIndent`), else the drop-target's own indent.
173
+ const targetIndent = indent ?? (dropIndex < count ? indentAt(state, items[dropIndex].lineRange.from) : 0);
174
+ const text = reindent(state.doc.sliceString(from, to), sourceIndent, targetIndent);
175
+
176
+ // Remove the subtree together with one adjacent newline so no blank line is orphaned.
177
+ const hasFollowing = to < state.doc.length;
178
+ const deleteFrom = hasFollowing ? from : Math.max(0, from - 1);
179
+ const deleteTo = hasFollowing ? Math.min(to + 1, state.doc.length) : to;
180
+ const insert = dropIndex < count ? `${text}\n` : `\n${text}`;
181
+
182
+ // Sort the two changes by position (CodeMirror requires ascending, non-overlapping specs).
183
+ const changes =
184
+ insertAt <= deleteFrom
185
+ ? [
186
+ { from: insertAt, insert },
187
+ { from: deleteFrom, to: deleteTo },
188
+ ]
189
+ : [
190
+ { from: deleteFrom, to: deleteTo },
191
+ { from: insertAt, insert },
192
+ ];
193
+
194
+ // Caret at the end of the moved item; keep the moved item selected (its new anchor is where the moved
195
+ // text starts — the subtree's first line).
196
+ const changeSet = state.changes(changes);
197
+ const textStart = changeSet.mapPos(insertAt, -1) + (dropIndex < count ? 0 : 1);
198
+ const caret = textStart + text.length;
199
+ view.dispatch({
200
+ changes,
201
+ selection: { anchor: Math.min(caret, changeSet.newLength) },
202
+ effects: setBlockSelection.of([textStart]),
203
+ userEvent: 'move.item',
204
+ });
205
+ };
206
+
207
+ // Merges sorted ranges, combining any that overlap or touch. Exported for the menu commands.
208
+ export const mergeRanges = (ranges: { from: number; to: number }[]): { from: number; to: number }[] => {
209
+ const sorted = [...ranges].sort((a, b) => a.from - b.from);
210
+ const merged: { from: number; to: number }[] = [];
211
+ for (const range of sorted) {
212
+ const last = merged.at(-1);
213
+ if (last && range.from <= last.to) {
214
+ last.to = Math.max(last.to, range.to);
215
+ } else {
216
+ merged.push({ ...range });
217
+ }
218
+ }
219
+ return merged;
220
+ };
221
+
222
+ // Delete ranges for the given items: each item's own lines plus its separating newline (the one below,
223
+ // down to the next item; for the last item the one above instead, so no trailing blank is orphaned),
224
+ // merged so adjacent selections form one range.
225
+ const deleteRanges = (state: EditorState, blocks: Block[], indices: number[]): { from: number; to: number }[] => {
226
+ const count = blocks.length;
227
+ return mergeRanges(
228
+ indices.map((index) => ({
229
+ from: index === count - 1 && index > 0 ? blocks[index - 1].to : blocks[index].from,
230
+ to: index + 1 < count ? blocks[index + 1].from : state.doc.length,
231
+ })),
232
+ );
233
+ };
234
+
235
+ /**
236
+ * Moves the items at `sourceIndices` to the slot before `dropIndex`. A single item moves with its whole
237
+ * subtree (re-indented — see `moveItem`); multiple items move by their own lines as a group (a
238
+ * best-effort for multi-selection reorder).
239
+ */
240
+ // Exported for tests (the drag path calls this via `outlinerBlockOps`).
241
+ export const moveBlocks = (view: EditorView, sourceIndices: number[], dropIndex: number, indent?: number): void => {
242
+ const { state } = view;
243
+ const items = flattenItems(state);
244
+ const count = items.length;
245
+ const sources = [...new Set(sourceIndices)].filter((index) => index >= 0 && index < count).sort((a, b) => a - b);
246
+ if (sources.length === 0 || sources.includes(dropIndex)) {
247
+ return;
248
+ }
249
+ if (sources.length === 1) {
250
+ // Single-item drags re-root the whole subtree to the drag's resolved drop level (`getDropIndent`).
251
+ moveItem(view, sources[0], dropIndex, indent);
252
+ return;
253
+ }
254
+
255
+ const blocks = getBlocks(state);
256
+ const text = sources.map((index) => state.doc.sliceString(blocks[index].from, blocks[index].to)).join('\n');
257
+ const deletes = deleteRanges(state, blocks, sources);
258
+ const insertAt = dropIndex < count ? blocks[dropIndex].from : state.doc.length;
259
+ if (deletes.some((range) => insertAt > range.from && insertAt < range.to)) {
260
+ return;
261
+ }
262
+
263
+ const insert = dropIndex < count ? `${text}\n` : `\n${text}`;
264
+ const changes = [...deletes.map((range) => ({ from: range.from, to: range.to })), { from: insertAt, insert }];
265
+ const changeSet = state.changes(changes);
266
+ const textStart = changeSet.mapPos(insertAt, -1) + (dropIndex < count ? 0 : 1);
267
+ // Keep the moved items selected: their new anchors are the start of each item within the inserted
268
+ // text, joined by a single newline (item length + 1). The caret lands at the end of the moved content.
269
+ const anchors: number[] = [];
270
+ let anchor = textStart;
271
+ for (const index of sources) {
272
+ anchors.push(anchor);
273
+ anchor += blocks[index].to - blocks[index].from + 1;
274
+ }
275
+ const caret = textStart + text.length;
276
+ view.dispatch({
277
+ changes,
278
+ selection: { anchor: Math.min(caret, changeSet.newLength) },
279
+ effects: setBlockSelection.of(anchors),
280
+ userEvent: 'move.item',
281
+ });
282
+ };
283
+
284
+ /** Removes the items at `indices`, optionally replacing the first slot with `text` (paste). */
285
+ // Exported for tests (the drag/clipboard path calls this via `outlinerBlockOps`).
286
+ export const replaceBlocks = (view: EditorView, indices: number[], text: string | null): void => {
287
+ const { state } = view;
288
+ const blocks = getBlocks(state);
289
+ const count = blocks.length;
290
+ const sources = [...new Set(indices)].filter((index) => index >= 0 && index < count).sort((a, b) => a - b);
291
+ if (sources.length === 0) {
292
+ return;
293
+ }
294
+
295
+ const deletes = deleteRanges(state, blocks, sources);
296
+ const changes = deletes.map(
297
+ (range) => ({ from: range.from, to: range.to }) as { from: number; to: number; insert?: string },
298
+ );
299
+ let anchor = deletes[0].from;
300
+ if (text != null && text.length > 0) {
301
+ const insert = deletes[0].to >= state.doc.length ? text : `${text}\n`;
302
+ changes[0] = { from: deletes[0].from, to: deletes[0].to, insert };
303
+ anchor = deletes[0].from + text.length;
304
+ }
305
+
306
+ view.dispatch({
307
+ changes,
308
+ selection: { anchor: Math.min(anchor, state.doc.length) },
309
+ effects: setBlockSelection.of([]),
310
+ userEvent: text == null ? 'delete.block' : 'input.paste',
311
+ });
312
+ };
313
+
314
+ const outlinerBlockOps: BlockOps = {
315
+ getBlocks,
316
+ moveBlocks,
317
+ serialize: (state, selected) => selected.map((block) => state.doc.sliceString(block.from, block.to)).join('\n'),
318
+ replaceBlocks,
319
+ };
320
+
321
+ /**
322
+ * Block selection, drag-to-reorder, and cut/copy/paste for outliner items. Single-item drag relocates
323
+ * the whole subtree (re-indented); the generic block-selection and clipboard from the `blocks` extensions
324
+ * operate on item lines. The grip is a floating overlay pinned to the content's left edge (see
325
+ * `createBlockDrag`), so it needs no outliner-specific gutter styling.
326
+ */
327
+ export const outlinerDnd = (): Extension => [
328
+ // The selection highlight is drawn by `outliner.ts` as a line decoration (aligned to the actual rows);
329
+ // the `blocks` RectangleMarker layer is skipped here because it drifts against the outliner's row CSS.
330
+ createBlockSelection(outlinerBlockOps),
331
+ createBlockDrag({ getBlocks, moveBlocks, getExtent, keepTrailingBreak: true, getDropIndent }),
332
+ ];
333
+
334
+ //
335
+ // Selection commands over the block selection (each item's line-start anchor). Replaces the outliner's
336
+ // former `selectionFacet`, so keyboard selection and the grip share one model.
337
+ //
338
+
339
+ const itemAnchors = (state: EditorState): number[] => flattenItems(state).map((item) => item.lineRange.from);
340
+
341
+ /** Select every item; pressing again (when all are selected) clears the selection. */
342
+ export const selectAllItems: Command = (view) => {
343
+ const anchors = itemAnchors(view.state);
344
+ const current = view.state.field(blockSelectionField, false) ?? [];
345
+ view.dispatch({ effects: setBlockSelection.of(current.length === anchors.length ? [] : anchors) });
346
+ return true;
347
+ };
348
+
349
+ /** Clear the block selection. */
350
+ export const selectNoneItems: Command = (view) => {
351
+ view.dispatch({ effects: setBlockSelection.of([]) });
352
+ return true;
353
+ };
354
+
355
+ // Extend the block selection by one item toward `delta`, moving the caret into the target item.
356
+ const extendSelection = (view: EditorView, delta: -1 | 1): boolean => {
357
+ const items = flattenItems(view.state);
358
+ const caret = view.state.selection.main.head;
359
+ const index = items.findIndex((item) => caret >= item.lineRange.from && caret <= item.lineRange.to);
360
+ if (index < 0) {
361
+ return false;
362
+ }
363
+ const target = Math.min(items.length - 1, Math.max(0, index + delta));
364
+ const anchors = new Set(view.state.field(blockSelectionField, false) ?? []);
365
+ anchors.add(items[index].lineRange.from);
366
+ anchors.add(items[target].lineRange.from);
367
+ view.dispatch({
368
+ selection: { anchor: items[target].contentRange.from },
369
+ effects: setBlockSelection.of([...anchors].sort((a, b) => a - b)),
370
+ });
371
+ return true;
372
+ };
373
+
374
+ export const selectUp: Command = (view) => extendSelection(view, -1);
375
+ export const selectDown: Command = (view) => extendSelection(view, 1);
@@ -0,0 +1,85 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
6
+ import { type ChangeSpec, EditorSelection, EditorState } from '@codemirror/state';
7
+ import { describe, test } from 'vitest';
8
+
9
+ import { editor } from './editor';
10
+ import { outlinerTree, treeFacet } from './tree';
11
+
12
+ const extensions = [markdown({ base: markdownLanguage }), outlinerTree(), editor()];
13
+
14
+ // Two items: item 1 content [6,7]='A'; line 2 starts at 8, item 2 marker [8,14), content [14,15]='B'.
15
+ const DOC = '- [ ] A\n- [ ] B';
16
+
17
+ // Resulting caret after moving it to `pos` (the transaction filter snaps out-of-range positions).
18
+ const snap = (doc: string, pos: number): number =>
19
+ EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(pos) }).state.selection.main.from;
20
+
21
+ // Resulting caret after moving from `prev` to `next` (the filter uses the delta to pick a direction).
22
+ const snapFrom = (doc: string, prev: number, next: number): number => {
23
+ const state = EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(prev) }).state;
24
+ return state.update({ selection: EditorSelection.cursor(next) }).state.selection.main.from;
25
+ };
26
+
27
+ // Resulting document after applying `spec` with the caret at `cursor`.
28
+ const applyChange = (doc: string, cursor: number, spec: ChangeSpec): string => {
29
+ const state = EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(cursor) }).state;
30
+ return state.update({ changes: spec }).state.doc.toString();
31
+ };
32
+
33
+ describe('editor', () => {
34
+ test('an empty document still has a tree', ({ expect }) => {
35
+ const state = EditorState.create({ extensions });
36
+ expect(state.facet(treeFacet)).to.exist;
37
+ });
38
+
39
+ describe('selection guard', () => {
40
+ test('snaps a caret placed inside the marker to the content start', ({ expect }) => {
41
+ for (const pos of [0, 1, 3, 5]) {
42
+ expect(snap('- [ ] ', pos)).to.eq(6);
43
+ }
44
+ // A caret already at the content start is left alone.
45
+ expect(snap('- [ ] ', 6)).to.eq(6);
46
+ });
47
+
48
+ test('snaps within a later item too', ({ expect }) => {
49
+ expect(snap(DOC, 9)).to.eq(14); // Inside item 2's marker.
50
+ expect(snap(DOC, 10)).to.eq(14);
51
+ expect(snap(DOC, 14)).to.eq(14); // Content start is valid.
52
+ });
53
+
54
+ test('moving down into an item lands at its content start', ({ expect }) => {
55
+ expect(snapFrom(DOC, 7, 10)).to.eq(14);
56
+ });
57
+
58
+ test('moving up into an item lands at its content end', ({ expect }) => {
59
+ expect(snapFrom(DOC, 14, 4)).to.eq(7);
60
+ });
61
+
62
+ test('moving left across the marker goes to the previous line end', ({ expect }) => {
63
+ expect(snapFrom(DOC, 14, 13)).to.eq(7);
64
+ });
65
+ });
66
+
67
+ describe('change validation', () => {
68
+ test('allows deleting a whole item line', ({ expect }) => {
69
+ expect(applyChange(DOC, 0, { from: 0, to: 8 })).to.eq('- [ ] B');
70
+ });
71
+
72
+ test('allows typing into the content', ({ expect }) => {
73
+ expect(applyChange('- [ ] ', 6, { from: 6, to: 6, insert: 'X' })).to.eq('- [ ] X');
74
+ });
75
+
76
+ test('allows editing within the content', ({ expect }) => {
77
+ expect(applyChange('- [ ] A', 7, { from: 6, to: 7 })).to.eq('- [ ] ');
78
+ });
79
+ });
80
+
81
+ // Task-marker atomicity (backspace on `- [ ] ` removing the whole marker, or inserting a continuation
82
+ // indent) is enforced by the markdown extension's keymap, which produces the specific change shapes the
83
+ // filter reacts to — not by raw `changes`. Covering it needs the full keymap dispatched through a view.
84
+ test.todo('task-marker atomicity (needs the markdown keymap, not a raw change)');
85
+ });
@@ -7,7 +7,6 @@ import { type EditorView, ViewPlugin } from '@codemirror/view';
7
7
 
8
8
  import { log } from '@dxos/log';
9
9
 
10
- import { getSelection } from './selection';
11
10
  import { treeFacet } from './tree';
12
11
 
13
12
  const LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
@@ -18,12 +17,17 @@ const LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
18
17
  const initialize = () => {
19
18
  return ViewPlugin.fromClass(
20
19
  class {
20
+ #timer: ReturnType<typeof setTimeout> | null = null;
21
+
21
22
  constructor(view: EditorView) {
22
23
  const first = view.state.doc.lineAt(0);
23
24
  const text = view.state.sliceDoc(first.from, first.to);
24
25
  const match = text.match(LIST_ITEM_REGEX);
25
26
  if (!match) {
26
- setTimeout(() => {
27
+ // Deferred so the dispatch runs after the view finishes constructing; cleared on destroy so it
28
+ // never fires on a torn-down view.
29
+ this.#timer = setTimeout(() => {
30
+ this.#timer = null;
27
31
  const insert = '- [ ] ';
28
32
  view.dispatch({
29
33
  changes: [{ from: 0, to: 0, insert }],
@@ -32,6 +36,12 @@ const initialize = () => {
32
36
  });
33
37
  }
34
38
  }
39
+
40
+ destroy() {
41
+ if (this.#timer != null) {
42
+ clearTimeout(this.#timer);
43
+ }
44
+ }
35
45
  },
36
46
  );
37
47
  };
@@ -49,7 +59,7 @@ export const editor = () => [
49
59
  // Check cursor is in a valid position.
50
60
  //
51
61
  if (!tr.docChanged) {
52
- const current = getSelection(tr.state).from;
62
+ const current = tr.state.selection.main.from;
53
63
  if (current != null) {
54
64
  const currentItem = tree.find(current);
55
65
  if (!currentItem) {
@@ -58,7 +68,7 @@ export const editor = () => [
58
68
 
59
69
  // Check if outside of editable range.
60
70
  if (current < currentItem.contentRange.from || current > currentItem.contentRange.to) {
61
- const prev = getSelection(tr.startState).from;
71
+ const prev = tr.startState.selection.main.from;
62
72
  const prevItem = prev != null ? tree.find(prev) : undefined;
63
73
  if (!prevItem) {
64
74
  return [{ selection: EditorSelection.cursor(currentItem.contentRange.from) }];
@@ -110,11 +120,6 @@ export const editor = () => [
110
120
  return;
111
121
  }
112
122
 
113
- // if (!deleteLine && (!startItem || fromA < startItem.contentRange.from || toA > startItem.contentRange.to)) {
114
- // cancel = true;
115
- // return;
116
- // }
117
-
118
123
  // Check valid item.
119
124
  const currentItem = tree.find(tr.state.selection.main.from);
120
125
  if (!currentItem?.contentRange) {
@@ -3,5 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './commands';
6
+ export * from './dnd';
6
7
  export * from './outliner';
7
8
  export * from './tree';
@@ -6,6 +6,13 @@ import { type Extension } from '@codemirror/state';
6
6
  import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
7
7
 
8
8
  import { type CleanupFn, addEventListener } from '@dxos/async';
9
+ import { Domino } from '@dxos/ui';
10
+
11
+ import { GUTTER_WIDTH } from '../blocks';
12
+
13
+ // Square trigger size (px), matching the drag grip (`dx-button` density `xs` + `aspect-square` → `size-6`).
14
+ // The right-hand strip (`GUTTER_WIDTH`, shared with the grip's left strip) centers the trigger within it.
15
+ const TRIGGER_SIZE = 24;
9
16
 
10
17
  export type MenuOptions = {
11
18
  icon?: string;
@@ -13,6 +20,7 @@ export type MenuOptions = {
13
20
  padding?: number;
14
21
  };
15
22
 
23
+ /** Floating action button positioned beside the active outliner line. */
16
24
  // TODO(burdon): Replace with popover.
17
25
  export const menu = (options: MenuOptions = {}): Extension => [
18
26
  ViewPlugin.fromClass(
@@ -31,14 +39,16 @@ export const menu = (options: MenuOptions = {}): Extension => [
31
39
  container.style.position = 'relative';
32
40
  }
33
41
 
34
- {
35
- const icon = document.createElement('dx-icon');
36
- icon.setAttribute('icon', options.icon ?? 'ph--dots-three-vertical--regular');
37
-
38
- this.tag = document.createElement('dx-anchor');
39
- this.tag.classList.add('cm-popover-trigger');
40
- this.tag.appendChild(icon);
41
- }
42
+ // Outer `dx-anchor` (fires `dx-anchor-activate`, driving the popover) styled as a `dx-button`;
43
+ // inner element holds the phosphor glyph. Mirrors the drag grip's construction.
44
+ this.tag = Domino.of('dx-anchor')
45
+ .classNames('dx-button aspect-square cm-popover-trigger')
46
+ .attributes({ 'data-variant': 'ghost', 'data-density': 'xs' })
47
+ .append(
48
+ Domino.of('div')
49
+ .classNames('cm-popover-trigger-icon')
50
+ .append(Domino.svg(options.icon ?? 'ph--dots-three-vertical--regular')),
51
+ ).root;
42
52
 
43
53
  container.appendChild(this.tag);
44
54
 
@@ -86,14 +96,18 @@ export const menu = (options: MenuOptions = {}): Extension => [
86
96
  const line = this.view.lineBlockAt(pos);
87
97
  const coords = this.view.coordsAtPos(line.from);
88
98
  if (!coords) {
99
+ // No resolvable line (e.g. the caret's item was just deleted): hide rather than leave the
100
+ // trigger stranded at its previous position.
101
+ this.tag.style.display = 'none';
89
102
  return;
90
103
  }
91
104
 
92
105
  const lineHeight = coords.bottom - coords.top;
93
- const dy = (lineHeight - (options.height ?? 32)) / 2;
106
+ const dy = (lineHeight - (options.height ?? TRIGGER_SIZE)) / 2;
94
107
 
95
108
  const offsetTop = coords.top + dy;
96
- const offsetLeft = x + width + (options.padding ?? 8);
109
+ // Center the trigger within the 3rem gutter immediately right of the content (mirrors the grip).
110
+ const offsetLeft = x + width + GUTTER_WIDTH / 2 - TRIGGER_SIZE / 2;
97
111
 
98
112
  this.tag.style.top = `${offsetTop}px`;
99
113
  this.tag.style.left = `${offsetLeft}px`;
@@ -110,19 +124,28 @@ export const menu = (options: MenuOptions = {}): Extension => [
110
124
  },
111
125
  ),
112
126
 
113
- EditorView.theme({
114
- '.cm-popover-trigger': {
115
- position: 'fixed',
116
- padding: '0',
117
- border: 'none',
118
- opacity: '0',
119
- display: 'grid',
120
- placeContent: 'center',
121
- width: '2rem',
122
- height: '2rem',
123
- },
124
- '&:focus-within .cm-popover-trigger': {
125
- opacity: '1',
126
- },
127
- }),
127
+ styles,
128
128
  ];
129
+
130
+ const styles = EditorView.theme({
131
+ // `dx-button` owns the box (size, hover); this only pins it and gates visibility on editor focus.
132
+ '.cm-popover-trigger': {
133
+ position: 'fixed',
134
+ opacity: '0',
135
+ cursor: 'pointer',
136
+ },
137
+ '.cm-popover-trigger-icon': {
138
+ display: 'grid',
139
+ placeContent: 'center',
140
+ fontSize: '16px',
141
+ color: 'var(--color-description, currentColor)',
142
+ },
143
+ '&:focus-within .cm-popover-trigger': {
144
+ opacity: '1',
145
+ },
146
+ // Hide the trigger while a block drag is in flight (the scroller carries `cm-blockDragging`).
147
+ '.cm-scroller.cm-blockDragging .cm-popover-trigger': {
148
+ opacity: '0 !important',
149
+ pointerEvents: 'none',
150
+ },
151
+ });