@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,8 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './blocks';
6
+ export * from './drag';
7
+ export * from './selection';
8
+ export * from './types';
@@ -0,0 +1,56 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
6
+ import { EditorState } from '@codemirror/state';
7
+ import { describe, test } from 'vitest';
8
+
9
+ import { findBlocks } from './blocks';
10
+ import { blockSelectionField, getSelectedBlocks, setBlockSelection, toggleBlockSelection } from './selection';
11
+
12
+ const extensions = [markdown({ base: markdownLanguage }), blockSelectionField];
13
+ const create = (doc: string) => EditorState.create({ doc, extensions });
14
+
15
+ describe('blockSelectionField', () => {
16
+ test('set replaces and toggle adds/removes, kept sorted', ({ expect }) => {
17
+ let state = create('A\n\nB\n\nC'); // anchors: A@0, B@3, C@6
18
+ state = state.update({ effects: setBlockSelection.of([6, 0]) }).state;
19
+ expect([...state.field(blockSelectionField)]).to.deep.eq([0, 6]);
20
+ state = state.update({ effects: toggleBlockSelection.of(0) }).state;
21
+ expect([...state.field(blockSelectionField)]).to.deep.eq([6]);
22
+ state = state.update({ effects: toggleBlockSelection.of(3) }).state;
23
+ expect([...state.field(blockSelectionField)]).to.deep.eq([3, 6]);
24
+ });
25
+
26
+ test('anchors map through edits', ({ expect }) => {
27
+ let state = create('A\n\nB\n\nC');
28
+ state = state.update({ effects: setBlockSelection.of([3, 6]) }).state;
29
+ // Insert two characters at the start; anchors shift by two.
30
+ state = state.update({ changes: { from: 0, insert: 'XY' } }).state;
31
+ expect([...state.field(blockSelectionField)]).to.deep.eq([5, 8]);
32
+ });
33
+
34
+ test('a caret move keeps the selection (extendable via keyboard)', ({ expect }) => {
35
+ let state = create('A\n\nB');
36
+ state = state.update({ effects: setBlockSelection.of([0]) }).state;
37
+ state = state.update({ selection: { anchor: 3 } }).state;
38
+ expect([...state.field(blockSelectionField)]).to.deep.eq([0]);
39
+ });
40
+
41
+ test('getSelectedBlocks resolves anchors to blocks in order', ({ expect }) => {
42
+ let state = create('A\n\nB\n\nC');
43
+ state = state.update({ effects: setBlockSelection.of([6, 3]) }).state;
44
+ const selected = getSelectedBlocks(state, findBlocks);
45
+ expect(selected.map((entry) => entry.index)).to.deep.eq([1, 2]);
46
+ expect(state.doc.sliceString(selected[0].block.from, selected[0].block.to)).to.eq('B');
47
+ });
48
+
49
+ test('getSelectedBlocks drops stale anchors', ({ expect }) => {
50
+ let state = create('A\n\nB\n\nC');
51
+ // Anchor 4 is not a block start.
52
+ state = state.update({ effects: setBlockSelection.of([0, 4]) }).state;
53
+ const selected = getSelectedBlocks(state, findBlocks);
54
+ expect(selected.map((entry) => entry.index)).to.deep.eq([0]);
55
+ });
56
+ });
@@ -0,0 +1,279 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type EditorState, type Extension, Prec, StateEffect, StateField } from '@codemirror/state';
6
+ import { EditorView, RectangleMarker, keymap, layer } from '@codemirror/view';
7
+
8
+ import { type Block, type BlockExtent } from './types';
9
+
10
+ // Amount (px) the highlight extends left of the content, matching the outline boxes and drag preview.
11
+ const BOX_INSET_X = 8;
12
+ const BOX_PADDING = 2;
13
+
14
+ /**
15
+ * Operations a document supplies so whole blocks can be selected, reordered, and cut/copy/pasted. The
16
+ * same contract backs markdown top-level blocks and outliner items — nothing here is markdown-specific.
17
+ */
18
+ export type BlockOps = {
19
+ /** Blocks in document order; ranges non-overlapping. Should be memoized per state. */
20
+ getBlocks: (state: EditorState) => Block[];
21
+ /**
22
+ * Moves the blocks at `sourceIndices` to the slot before `dropIndex` (the end when
23
+ * `dropIndex === blocks.length`), preserving their relative order, as a single edit.
24
+ */
25
+ moveBlocks: (view: EditorView, sourceIndices: number[], dropIndex: number) => void;
26
+ /** Serializes blocks to clipboard text (markdown joins with a blank line; the outliner with newlines). */
27
+ serialize: (state: EditorState, blocks: Block[]) => string;
28
+ /**
29
+ * Removes the blocks at `indices` and, when `text` is non-null, inserts it as block(s) at the first
30
+ * removed slot — as a single edit. `text === null` is a pure delete (cut); non-null is a replace (paste).
31
+ */
32
+ replaceBlocks: (view: EditorView, indices: number[], text: string | null) => void;
33
+ };
34
+
35
+ //
36
+ // State: the block selection is an ordered set of block anchors (each block's `from`), kept sorted.
37
+ // Positions — not indices — so the set survives edits (mapped through changes).
38
+ //
39
+
40
+ /** Replaces the whole block selection. */
41
+ export const setBlockSelection = StateEffect.define<readonly number[]>();
42
+
43
+ /** Adds/removes a single block anchor, keeping the rest. */
44
+ export const toggleBlockSelection = StateEffect.define<number>();
45
+
46
+ const hasSelectionEffect = (effects: readonly StateEffect<any>[]): boolean =>
47
+ effects.some((effect) => effect.is(setBlockSelection) || effect.is(toggleBlockSelection));
48
+
49
+ export const blockSelectionField = StateField.define<readonly number[]>({
50
+ create: () => [],
51
+ update: (value, tr) => {
52
+ // Our own effects take precedence and are the only way to grow the selection.
53
+ if (hasSelectionEffect(tr.effects)) {
54
+ let next = value;
55
+ for (const effect of tr.effects) {
56
+ if (effect.is(setBlockSelection)) {
57
+ next = [...effect.value].sort((a, b) => a - b);
58
+ } else if (effect.is(toggleBlockSelection)) {
59
+ const set = new Set(next);
60
+ set.has(effect.value) ? set.delete(effect.value) : set.add(effect.value);
61
+ next = [...set].sort((a, b) => a - b);
62
+ }
63
+ }
64
+ return next;
65
+ }
66
+
67
+ if (value.length === 0) {
68
+ return value;
69
+ }
70
+
71
+ // Keep anchors aligned through edits (a consumer re-resolves them against `getBlocks`). Caret moves
72
+ // (keyboard nav) keep the selection so it can be extended; a plain text click clears it (see below).
73
+ if (tr.docChanged) {
74
+ return value.map((pos) => tr.changes.mapPos(pos, -1));
75
+ }
76
+
77
+ return value;
78
+ },
79
+ });
80
+
81
+ /** Resolves the selection anchors to `{ block, index }` in document order, dropping stale anchors. */
82
+ export const getSelectedBlocks = (
83
+ state: EditorState,
84
+ getBlocks: BlockOps['getBlocks'],
85
+ ): { block: Block; index: number }[] => {
86
+ const anchors = state.field(blockSelectionField, false);
87
+ if (!anchors || anchors.length === 0) {
88
+ return [];
89
+ }
90
+ const byFrom = new Map(getBlocks(state).map((block, index) => [block.from, { block, index }]));
91
+ const result: { block: Block; index: number }[] = [];
92
+ for (const anchor of anchors) {
93
+ const match = byFrom.get(anchor);
94
+ if (match) {
95
+ result.push(match);
96
+ }
97
+ }
98
+ return result;
99
+ };
100
+
101
+ //
102
+ // Highlight (layer): a full-width tinted box behind each selected block, aligned with the outline boxes.
103
+ //
104
+
105
+ const buildSelectionMarkers = (
106
+ view: EditorView,
107
+ getBlocks: BlockOps['getBlocks'],
108
+ getExtent?: BlockExtent,
109
+ ): RectangleMarker[] => {
110
+ const selected = getSelectedBlocks(view.state, getBlocks);
111
+ if (selected.length === 0) {
112
+ return [];
113
+ }
114
+
115
+ const contentRect = view.contentDOM.getBoundingClientRect();
116
+ const gutterOffset = contentRect.left - view.scrollDOM.getBoundingClientRect().left;
117
+ const markers: RectangleMarker[] = [];
118
+ for (const { block } of selected) {
119
+ // The extent is the range the block visually occupies (its subtree for the outliner).
120
+ const extent = getExtent ? getExtent(view.state, block) : block;
121
+ const from = Math.max(extent.from, view.viewport.from);
122
+ const to = Math.min(extent.to, view.viewport.to);
123
+ if (from > to) {
124
+ continue;
125
+ }
126
+ const firstLine = view.lineBlockAt(from);
127
+ const lastLine = view.lineBlockAt(to);
128
+ markers.push(
129
+ new RectangleMarker(
130
+ 'cm-blockSelected',
131
+ gutterOffset - BOX_INSET_X,
132
+ firstLine.top - BOX_PADDING,
133
+ contentRect.width + BOX_INSET_X,
134
+ lastLine.bottom - firstLine.top + BOX_PADDING * 2,
135
+ ),
136
+ );
137
+ }
138
+ return markers;
139
+ };
140
+
141
+ const selectionChanged = (a: EditorState, b: EditorState): boolean =>
142
+ a.field(blockSelectionField, false) !== b.field(blockSelectionField, false);
143
+
144
+ const selectionTheme = EditorView.theme({
145
+ '.cm-blockSelectedLayer': {
146
+ pointerEvents: 'none',
147
+ },
148
+ '.cm-blockSelected': {
149
+ boxSizing: 'border-box',
150
+ backgroundColor: 'var(--color-cm-highlight-surface)',
151
+ },
152
+ });
153
+
154
+ //
155
+ // Clipboard: cut/copy/paste act on the block selection; when it is empty they fall through to native
156
+ // text behavior. Mod-C/X/V route through these same DOM events, so no separate keymap is needed.
157
+ //
158
+
159
+ const clipboardHandlers = (ops: BlockOps): Extension =>
160
+ EditorView.domEventHandlers({
161
+ copy: (event, view) => {
162
+ const selected = getSelectedBlocks(view.state, ops.getBlocks);
163
+ if (selected.length === 0) {
164
+ return false;
165
+ }
166
+ event.clipboardData?.setData(
167
+ 'text/plain',
168
+ ops.serialize(
169
+ view.state,
170
+ selected.map((entry) => entry.block),
171
+ ),
172
+ );
173
+ event.preventDefault();
174
+ return true;
175
+ },
176
+ cut: (event, view) => {
177
+ const selected = getSelectedBlocks(view.state, ops.getBlocks);
178
+ if (selected.length === 0) {
179
+ return false;
180
+ }
181
+ event.clipboardData?.setData(
182
+ 'text/plain',
183
+ ops.serialize(
184
+ view.state,
185
+ selected.map((entry) => entry.block),
186
+ ),
187
+ );
188
+ event.preventDefault();
189
+ // `replaceBlocks` clears the block selection within its own transaction.
190
+ ops.replaceBlocks(
191
+ view,
192
+ selected.map((entry) => entry.index),
193
+ null,
194
+ );
195
+ return true;
196
+ },
197
+ paste: (event, view) => {
198
+ const selected = getSelectedBlocks(view.state, ops.getBlocks);
199
+ if (selected.length === 0) {
200
+ return false;
201
+ }
202
+ const text = event.clipboardData?.getData('text/plain') ?? '';
203
+ event.preventDefault();
204
+ // Replace the selected blocks with the pasted text (`replaceBlocks` clears the block selection).
205
+ ops.replaceBlocks(
206
+ view,
207
+ selected.map((entry) => entry.index),
208
+ text,
209
+ );
210
+ return true;
211
+ },
212
+ });
213
+
214
+ // A plain press in the text clears the block selection (handle presses go through the gutter and don't
215
+ // hit `.cm-content`; shift-presses are left to extend the block selection via the gutter).
216
+ const clearOnTextPress = EditorView.domEventHandlers({
217
+ mousedown: (event, view) => {
218
+ if (event.shiftKey) {
219
+ return false;
220
+ }
221
+ const target = event.target;
222
+ const anchors = view.state.field(blockSelectionField, false);
223
+ if (anchors && anchors.length > 0 && target instanceof Node && view.contentDOM.contains(target)) {
224
+ view.dispatch({ effects: setBlockSelection.of([]) });
225
+ }
226
+ return false;
227
+ },
228
+ });
229
+
230
+ // Escape clears the block selection. High precedence so it wins over other Escape bindings while a
231
+ // selection is active, but falls through (returns false) when there is nothing selected.
232
+ const clearOnEscape = Prec.high(
233
+ keymap.of([
234
+ {
235
+ key: 'Escape',
236
+ run: (view) => {
237
+ const anchors = view.state.field(blockSelectionField, false);
238
+ if (!anchors || anchors.length === 0) {
239
+ return false;
240
+ }
241
+ view.dispatch({ effects: setBlockSelection.of([]) });
242
+ return true;
243
+ },
244
+ },
245
+ ]),
246
+ );
247
+
248
+ /**
249
+ * Draws a border/background box behind each selected block, in a below-text layer re-measured on edits,
250
+ * scrolling, and viewport changes. Separate from the selection state and clipboard so it can travel with
251
+ * the drag grip (which populates the selection) independently of `createBlockSelection`. The blocks are
252
+ * supplied by the caller (see `blockSelectionHighlight` for markdown top-level blocks).
253
+ */
254
+ export const createBlockSelectionHighlight = (getBlocks: BlockOps['getBlocks'], getExtent?: BlockExtent): Extension => [
255
+ blockSelectionField,
256
+ selectionTheme,
257
+ layer({
258
+ above: false,
259
+ class: 'cm-blockSelectedLayer',
260
+ update: (update) =>
261
+ update.docChanged ||
262
+ update.viewportChanged ||
263
+ update.geometryChanged ||
264
+ selectionChanged(update.startState, update.state),
265
+ markers: (view) => buildSelectionMarkers(view, getBlocks, getExtent),
266
+ }),
267
+ ];
268
+
269
+ /**
270
+ * Whole-block selection state and cut/copy/paste over the selected blocks. The border/background behind
271
+ * the selection is drawn by `createBlockSelectionHighlight`, and the gutter grip gestures that populate
272
+ * the selection live in `createBlockDrag` (the handle is a gutter marker) — compose all three together.
273
+ */
274
+ export const createBlockSelection = (ops: BlockOps): Extension => [
275
+ blockSelectionField,
276
+ clipboardHandlers(ops),
277
+ clearOnTextPress,
278
+ clearOnEscape,
279
+ ];
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type EditorState } from '@codemirror/state';
6
+
7
+ /**
8
+ * A contiguous document region that can be selected, dragged, and reordered as a unit. Lives in its own
9
+ * module so `drag.ts` and `selection.ts` can share it without importing from each other (a cycle that
10
+ * makes the dev bundler instantiate the shared state field twice).
11
+ */
12
+ export type Block = {
13
+ from: number;
14
+ to: number;
15
+ };
16
+
17
+ /**
18
+ * Maps a block to the document range it visually occupies for the selection highlight and drag
19
+ * (collapse/preview/placeholder). Defaults to the block's own range; a hierarchical consumer (the
20
+ * outliner) returns the block's whole subtree so selecting/dragging a parent covers its descendants.
21
+ */
22
+ export type BlockExtent = (state: EditorState, block: Block) => Block;
@@ -32,15 +32,17 @@ export const folding = (): Extension => {
32
32
  ).root;
33
33
  },
34
34
  }),
35
- EditorView.theme({
36
- '.cm-foldGutter': {
37
- opacity: 0.3,
38
- transition: 'opacity 0.3s',
39
- width: '32px',
40
- },
41
- '.cm-foldGutter:hover': {
42
- opacity: 1,
43
- },
44
- }),
35
+ styles,
45
36
  ];
46
37
  };
38
+
39
+ const styles = EditorView.theme({
40
+ '.cm-foldGutter': {
41
+ opacity: 0.3,
42
+ transition: 'opacity 0.3s',
43
+ width: '32px',
44
+ },
45
+ '.cm-foldGutter:hover': {
46
+ opacity: 1,
47
+ },
48
+ });
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './folding';
6
+ export * from './turns';
@@ -0,0 +1,72 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { EditorState } from '@codemirror/state';
6
+ import { describe, test } from 'vitest';
7
+
8
+ import { PROMPT_ELEMENT, createTurnSource } from './turns';
9
+
10
+ describe('createTurnSource', () => {
11
+ test('folds the response below a prompt', ({ expect }) => {
12
+ const turns = scan(['<prompt>Hi</prompt>', 'Answer.'].join('\n'));
13
+ expect(turns).toHaveLength(1);
14
+ expect(turns[0].head).toBe('<prompt>Hi</prompt>');
15
+ expect(turns[0].folded).toBe('Answer.');
16
+ });
17
+
18
+ test('anchors the gutter marker on the first line of a multi-line head', ({ expect }) => {
19
+ const doc = ['<prompt>line one', 'line two</prompt>', 'Answer.'].join('\n');
20
+ const turns = scan(doc);
21
+ expect(turns[0].headLine).toBe(0);
22
+ expect(turns[0].folded).toBe('Answer.');
23
+ });
24
+
25
+ // A toolbar rendered under the prompt belongs to the head: folding the response must not hide the
26
+ // control that folds it.
27
+ test('keeps a trailing self-closing toolbar tag out of the fold range', ({ expect }) => {
28
+ const doc = ['<prompt>Hi</prompt>', '', '<branch messageId="a" created="t" />', 'Answer.'].join('\n');
29
+ const turns = scan(doc);
30
+ expect(turns).toHaveLength(1);
31
+ expect(turns[0].head).toContain('<branch');
32
+ expect(turns[0].folded).toBe('Answer.');
33
+ // The marker still sits on the prompt, not on the toolbar.
34
+ expect(turns[0].headLine).toBe(0);
35
+ });
36
+
37
+ test('absorbs several trailing self-closing tags', ({ expect }) => {
38
+ const doc = ['<prompt>Hi</prompt>', '<branch messageId="a" />', '<other />', 'Answer.'].join('\n');
39
+ const turns = scan(doc);
40
+ expect(turns[0].folded).toBe('Answer.');
41
+ });
42
+
43
+ test('does not absorb a paired tag that opens the response', ({ expect }) => {
44
+ const doc = ['<prompt>Hi</prompt>', '<reasoning>Thinking</reasoning>', 'Answer.'].join('\n');
45
+ const turns = scan(doc);
46
+ expect(turns[0].head).toBe('<prompt>Hi</prompt>');
47
+ expect(turns[0].folded).toBe(['<reasoning>Thinking</reasoning>', 'Answer.'].join('\n'));
48
+ });
49
+
50
+ test('ends a turn where the next prompt begins, trailing whitespace trimmed', ({ expect }) => {
51
+ const doc = ['<prompt>One</prompt>', 'First.', '', '<prompt>Two</prompt>', 'Second.', ''].join('\n');
52
+ const turns = scan(doc);
53
+ expect(turns).toHaveLength(2);
54
+ expect(turns[0].folded).toBe('First.');
55
+ expect(turns[1].folded).toBe('Second.');
56
+ });
57
+
58
+ test('yields no turn for a prompt with an empty response', ({ expect }) => {
59
+ expect(scan('<prompt>Hi</prompt>')).toHaveLength(0);
60
+ expect(scan(['<prompt>Hi</prompt>', '<branch messageId="a" />'].join('\n'))).toHaveLength(0);
61
+ });
62
+ });
63
+
64
+ /** Resolves each turn into the text it treats as head vs. the text it would hide. */
65
+ const scan = (doc: string) => {
66
+ const state = EditorState.create({ doc });
67
+ return createTurnSource(PROMPT_ELEMENT)(state).map((turn) => ({
68
+ head: state.doc.sliceString(turn.headLineFrom, turn.from).trim(),
69
+ folded: state.doc.sliceString(turn.from, turn.to).trim(),
70
+ headLine: state.doc.lineAt(turn.headLineFrom).number - 1,
71
+ }));
72
+ };
@@ -0,0 +1,209 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { codeFolding, foldedRanges, foldEffect, unfoldEffect } from '@codemirror/language';
6
+ import { type EditorState, type Extension, RangeSetBuilder } from '@codemirror/state';
7
+ import { EditorView, GutterMarker, gutter } from '@codemirror/view';
8
+
9
+ import { Domino } from '@dxos/ui';
10
+
11
+ /**
12
+ * Turn folding.
13
+ *
14
+ * Makes each turn's response a foldable region. `codeFolding` (from `@codemirror/language`) hides the
15
+ * folded ranges and renders the placeholder, but folds are driven **only** by this extension's own
16
+ * gutter — never through CodeMirror's `foldable()`/`foldGutter`/`foldService`. That matters because
17
+ * `foldable()` unconditionally falls back to the language's `syntaxFolding` (markdown `foldNodeProp`
18
+ * — headings, fenced code, …) whenever no fold service matches a line; routing folds through
19
+ * `foldEffect` on our own ranges instead means only turns from the caller-provided {@link TurnSource}
20
+ * can ever fold. The section model (scan text, read a tree, derive from a message array) lives with
21
+ * the host and can change without touching the fold UI.
22
+ *
23
+ * Folding starts at the end of a turn's head, so the head (e.g. the user prompt) stays visible while
24
+ * its response collapses.
25
+ */
26
+
27
+ /** A foldable turn response. */
28
+ export type Turn = {
29
+ /** Fold range start — the end of the head line, so the head stays visible. */
30
+ from: number;
31
+ /** Fold range end — the end of the response that follows the head. */
32
+ to: number;
33
+ /** Start offset of the head line; the gutter marker anchors here. */
34
+ headLineFrom: number;
35
+ };
36
+
37
+ /** Yields the foldable turn ranges for a document. */
38
+ export type TurnSource = (state: EditorState) => Turn[];
39
+
40
+ export type TurnFoldingOptions = {
41
+ /** Source of foldable ranges (e.g. scanned from text or derived from the message model). */
42
+ source: TurnSource;
43
+ };
44
+
45
+ /** Turn-response folding driven by a custom gutter and TurnSource, bypassing markdown syntax folding. */
46
+ export const turnFolding = ({ source }: TurnFoldingOptions): Extension => {
47
+ // The gutter's `markers` runs per update; memoize the per-state turn scan.
48
+ const cache = new WeakMap<EditorState, Turn[]>();
49
+ const turnsOf = (state: EditorState): Turn[] => {
50
+ let turns = cache.get(state);
51
+ if (!turns) {
52
+ turns = source(state);
53
+ cache.set(state, turns);
54
+ }
55
+ return turns;
56
+ };
57
+
58
+ return [
59
+ // Hides folded ranges + renders the placeholder; folds are dispatched only by the gutter below.
60
+ codeFolding({
61
+ placeholderDOM: () => Domino.of('span').classNames('hidden').root,
62
+ }),
63
+ gutter({
64
+ class: 'cm-turn-gutter',
65
+ markers: (view) => {
66
+ const builder = new RangeSetBuilder<GutterMarker>();
67
+ for (const turn of turnsOf(view.state)) {
68
+ builder.add(turn.headLineFrom, turn.headLineFrom, new TurnMarker(isFolded(view.state, turn)));
69
+ }
70
+ return builder.finish();
71
+ },
72
+ // A fold toggle changes neither the doc nor the viewport, so re-measure on our fold effects
73
+ // to keep the caret direction in sync.
74
+ lineMarkerChange: (update) =>
75
+ update.transactions.some((tr) => tr.effects.some((effect) => effect.is(foldEffect) || effect.is(unfoldEffect))),
76
+ domEventHandlers: {
77
+ mousedown: (view, line) => {
78
+ const turn = turnsOf(view.state).find((candidate) => candidate.headLineFrom === line.from);
79
+ if (!turn) {
80
+ return false;
81
+ }
82
+ const effect = isFolded(view.state, turn) ? unfoldEffect : foldEffect;
83
+ view.dispatch({ effects: effect.of({ from: turn.from, to: turn.to }) });
84
+ return true;
85
+ },
86
+ },
87
+ }),
88
+ styles,
89
+ ];
90
+ };
91
+
92
+ /** Whether `turn`'s range is currently folded. */
93
+ const isFolded = (state: EditorState, turn: Turn): boolean => {
94
+ let folded = false;
95
+ foldedRanges(state).between(turn.from, turn.to, (from, to) => {
96
+ if (from <= turn.from && to >= turn.to) {
97
+ folded = true;
98
+ }
99
+ });
100
+ return folded;
101
+ };
102
+
103
+ /** Folds every turn from `source` (used for a host "collapse all"). Folds only turns, never syntax. */
104
+ export const foldTurns = (view: EditorView, source: TurnSource): void => {
105
+ const effects = source(view.state)
106
+ .filter((turn) => turn.to > turn.from)
107
+ .map((turn) => foldEffect.of({ from: turn.from, to: turn.to }));
108
+ if (effects.length > 0) {
109
+ view.dispatch({ effects });
110
+ }
111
+ };
112
+
113
+ /** Unfolds every turn from `source` (used for a host "expand all"). */
114
+ export const unfoldTurns = (view: EditorView, source: TurnSource): void => {
115
+ const effects = source(view.state).map((turn) => unfoldEffect.of({ from: turn.from, to: turn.to }));
116
+ if (effects.length > 0) {
117
+ view.dispatch({ effects });
118
+ }
119
+ };
120
+
121
+ /** Gutter marker for a turn head: a caret whose direction reflects the fold state. */
122
+ class TurnMarker extends GutterMarker {
123
+ #folded: boolean;
124
+
125
+ constructor(folded: boolean) {
126
+ super();
127
+ this.#folded = folded;
128
+ }
129
+
130
+ override eq(other: TurnMarker): boolean {
131
+ return this.#folded === other.#folded;
132
+ }
133
+
134
+ override toDOM(): HTMLElement {
135
+ return Domino.of('div')
136
+ .classNames('flex h-full items-center justify-center cursor-pointer')
137
+ .append(
138
+ Domino.of('svg', Domino.SVG)
139
+ .classNames('w-4 h-4')
140
+ .append(
141
+ Domino.of('use', Domino.SVG).attributes({
142
+ href: Domino.icon(this.#folded ? 'ph--caret-right--regular' : 'ph--caret-down--regular'),
143
+ }),
144
+ ),
145
+ ).root;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Default head element: a `<prompt>…</prompt>` element (content is escaped, so it never nests),
151
+ * plus any run of self-closing tags immediately following it.
152
+ *
153
+ * Those trailing tags are per-turn affordances belonging to the head — a toolbar rendered under the
154
+ * prompt, say — so they must stay visible when the response folds. Absorbing them into the head also
155
+ * keeps the fold starting below them, rather than swallowing the toolbar that triggers the fold.
156
+ */
157
+ export const PROMPT_ELEMENT = /<prompt\b[^>]*>[\s\S]*?<\/prompt>(?:\s*<[a-zA-Z][\w-]*\b[^>]*\/>)*/g;
158
+
159
+ /**
160
+ * Builds a {@link TurnSource} that scans the document for head elements matching `element`, folding
161
+ * the response after each — from the end of that head's line up to the next head (or the end of the
162
+ * document), trailing whitespace trimmed. `element` selects the turn head (e.g. {@link PROMPT_ELEMENT}
163
+ * for `<prompt>`); pass another pattern to key turns off a different tag.
164
+ */
165
+ export const createTurnSource = (element: RegExp): TurnSource => {
166
+ // Clone with the global flag so `exec` iterates (and never shares `lastIndex` with the caller).
167
+ const pattern = new RegExp(element.source, element.flags.includes('g') ? element.flags : `${element.flags}g`);
168
+ return (state) => {
169
+ const doc = state.doc;
170
+ const text = doc.toString();
171
+ const heads: number[] = [];
172
+ let match: RegExpExecArray | null;
173
+ pattern.lastIndex = 0;
174
+ while ((match = pattern.exec(text)) !== null) {
175
+ heads.push(match.index, match.index + match[0].length);
176
+ }
177
+
178
+ const turns: Turn[] = [];
179
+ for (let index = 0; index < heads.length; index += 2) {
180
+ const headStart = heads[index];
181
+ const headEnd = heads[index + 1];
182
+ // Fold starts after the head's last line (so the whole head stays visible), but the gutter
183
+ // marker anchors to the head's first line so the toggle sits at the top of a multi-line head.
184
+ const from = doc.lineAt(headEnd).to;
185
+ const headLineFrom = doc.lineAt(headStart).from;
186
+ const nextHeadStart = index + 2 < heads.length ? heads[index + 2] : doc.length;
187
+ let to = nextHeadStart;
188
+ while (to > from && /\s/.test(text[to - 1])) {
189
+ to--;
190
+ }
191
+ if (to > from) {
192
+ turns.push({ from, to, headLineFrom });
193
+ }
194
+ }
195
+ return turns;
196
+ };
197
+ };
198
+
199
+ const styles = EditorView.theme({
200
+ '.cm-turn-gutter': {
201
+ width: '1.5rem',
202
+ color: 'var(--dx-description, currentColor)',
203
+ opacity: '0.4',
204
+ transition: 'opacity 0.2s',
205
+ },
206
+ '.cm-turn-gutter:hover': {
207
+ opacity: '1',
208
+ },
209
+ });