@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,101 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+ // @vitest-environment happy-dom
5
+
6
+ import { history, undo } from '@codemirror/commands';
7
+ import { EditorState } from '@codemirror/state';
8
+ import { EditorView } from '@codemirror/view';
9
+ import { describe, test } from 'vitest';
10
+
11
+ import { computeCharHunks } from './diff';
12
+ import { trackChanges } from './track-changes';
13
+
14
+ const MAIN = 'alpha\nbravo\ncharlie';
15
+
16
+ const mount = (doc: string, extensions = [trackChanges({ main: MAIN, colour: 'var(--dx-accent)' })]) =>
17
+ new EditorView({ state: EditorState.create({ doc, extensions }), parent: document.body });
18
+
19
+ describe('trackChanges', () => {
20
+ test('a multi-line (block) deletion renders a single phantom preserving the removed lines', ({ expect }) => {
21
+ // The branch removed the middle line "bravo\n"; the phantom must show it (with its newline), not
22
+ // collapse the block away.
23
+ const view = mount('alpha\ncharlie');
24
+ const phantoms = view.dom.querySelectorAll('.cm-track-delete');
25
+ expect(phantoms).toHaveLength(1);
26
+ // textContent carries the removed block including its line break (pre-wrap renders the break).
27
+ expect(phantoms[0].textContent).toBe('bravo\n');
28
+ view.destroy();
29
+ });
30
+
31
+ test('undo re-instates a deletion (un-delete) and clears the phantom', ({ expect }) => {
32
+ const view = mount(MAIN, [history(), trackChanges({ main: MAIN, colour: 'var(--dx-accent)' })]);
33
+ // Delete the "bravo\n" block on the branch → a phantom appears.
34
+ view.dispatch({ changes: { from: 6, to: 12, insert: '' } });
35
+ expect(view.dom.querySelector('.cm-track-delete')).not.toBeNull();
36
+ expect(view.state.doc.toString()).toBe('alpha\ncharlie');
37
+
38
+ // Native undo restores the deleted text (the deletion is an ordinary branch edit) and the phantom
39
+ // disappears.
40
+ undo(view);
41
+ expect(view.state.doc.toString()).toBe(MAIN);
42
+ expect(view.dom.querySelector('.cm-track-delete')).toBeNull();
43
+ view.destroy();
44
+ });
45
+
46
+ test('a changed line renders a gutter change-bar in the author colour', ({ expect }) => {
47
+ // Branch inserts " extra" on the first line → that line gets a change-bar; an unchanged line does not.
48
+ const view = mount('alpha extra\nbravo\ncharlie');
49
+ const bars = view.dom.querySelectorAll<HTMLElement>('.cm-change-bar');
50
+ expect(bars.length).toBe(1);
51
+ expect(bars[0].style.background).toBe('var(--dx-accent)');
52
+ view.destroy();
53
+ });
54
+
55
+ test('a new-paragraph insertion bars only the text line, not the trailing blank line', ({ expect }) => {
56
+ // The branch inserts a new first paragraph "intro\n\n" before "alpha"; the trailing paragraph break
57
+ // must not tag the blank line — exactly one bar, on the inserted text line.
58
+ const view = mount('intro\n\nalpha\nbravo\ncharlie');
59
+ expect(view.dom.querySelectorAll('.cm-change-bar')).toHaveLength(1);
60
+ view.destroy();
61
+ });
62
+
63
+ // A reader deleting a run of words must strike exactly those words: character diffing used to match
64
+ // stray letters across the run (`t` of `it` against `t` of `two`), splitting words at both edges.
65
+ test('deleting a run of words strikes whole words', ({ expect }) => {
66
+ const main = 'the revision it was written on, so two people can suggest changes';
67
+ const doc = main.replace('it was written on, so ', '');
68
+ const hunks = computeCharHunks(main, doc);
69
+ expect(hunks).toHaveLength(1);
70
+ expect(main.slice(hunks[0].fromA, hunks[0].toA)).toBe('it was written on, so ');
71
+ });
72
+
73
+ // Selections do not respect word edges. Cutting into the words at each end fuses the survivors, and a
74
+ // word-level hunk then reads as "delete both words, insert this fused one" — rendering a phantom of
75
+ // both whole words plus a bogus insertion (`writte` + inserted `ngest`) instead of a plain deletion.
76
+ test('a deletion that cuts into words at both ends strikes only what was removed', ({ expect }) => {
77
+ const main = 'the revision it was written on, so two people can suggest changes';
78
+ const cut = 'n on, so two people can sugge';
79
+ const doc = main.replace(cut, '');
80
+
81
+ const hunks = computeCharHunks(main, doc);
82
+ expect(hunks).toHaveLength(1);
83
+ expect(main.slice(hunks[0].fromA, hunks[0].toA)).toBe(cut);
84
+ // Nothing is reported as inserted: the reader only deleted.
85
+ expect(doc.slice(hunks[0].fromB, hunks[0].toB)).toBe('');
86
+ });
87
+
88
+ // Deleting `suggest change` from `suggest changes` leaves an `s`, and that survivor is equally the
89
+ // tail of `changes` or the head of `suggest`. The minimal edit is ambiguous, so the strike must be
90
+ // slid to the word boundary — otherwise it keeps the `s` of `suggest` and strikes `uggest changes`.
91
+ test('an ambiguous deletion strikes from the word boundary', ({ expect }) => {
92
+ const main = 'so two people can suggest changes to the same';
93
+ const cut = 'suggest change';
94
+ const doc = main.replace(cut, '');
95
+
96
+ const hunks = computeCharHunks(main, doc);
97
+ expect(hunks).toHaveLength(1);
98
+ expect(main.slice(hunks[0].fromA, hunks[0].toA)).toBe(cut);
99
+ expect(main[hunks[0].fromA - 1]).toBe(' ');
100
+ });
101
+ });
@@ -0,0 +1,215 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type EditorState, type Extension, RangeSet, StateEffect, StateField } from '@codemirror/state';
6
+ import { Decoration, type DecorationSet, EditorView, type GutterMarker, WidgetType, keymap } from '@codemirror/view';
7
+
8
+ import { Domino } from '@dxos/ui';
9
+
10
+ import { ChangeBarMarker, changeBars } from './change-bar';
11
+ import { computeCharHunks } from './diff';
12
+
13
+ export type TrackChangesOptions = {
14
+ /**
15
+ * The accepted base (`main`) the branch is diffed against. The editor's document is the branch, so
16
+ * insertions and deletions are computed relative to this. Replace it live with {@link setBase} (e.g.
17
+ * when a merge lands on main) rather than rebuilding the extension.
18
+ */
19
+ main: string;
20
+ /** The branch author's palette colour (e.g. `var(--color-cyan-text)`) applied to the tracked marks. */
21
+ colour: string;
22
+ };
23
+
24
+ /**
25
+ * Replaces the tracked base without rebuilding the extension, so a merge to main re-diffs the live
26
+ * document. The base is a {@link StateField} fed by this effect rather than a closure constant, matching
27
+ * the production shape (see the bind-to-branch spike).
28
+ */
29
+ export const setBase = StateEffect.define<string>();
30
+
31
+ /**
32
+ * Google-Docs-style "Suggesting mode" via bind-to-branch: the editor is bound to the branch document
33
+ * and edits accrue natively to it, while the diff against {@link TrackChangesOptions.main} is decorated
34
+ * over the live buffer — insertions as author-coloured underlined marks, deletions as phantom
35
+ * strikethrough widgets rendering the removed base text (which is absent from the document). A
36
+ * replacement renders both, `~~old~~new`. Single author; foreign authors compose via the separate
37
+ * `suggestions()` overlay.
38
+ */
39
+ export const trackChanges = ({ main, colour }: TrackChangesOptions): Extension => {
40
+ // The author colour is constant for the instance, so bind it to the insertion mark once. Structural
41
+ // styling lives in the theme class; only the dynamic attribution colour is inline (as in `suggest.ts`).
42
+ const insertMark = Decoration.mark({
43
+ class: 'cm-track-insert',
44
+ attributes: { style: `color:${colour};text-decoration-color:${colour}` },
45
+ });
46
+
47
+ // One-entry memo of the last (base, doc) → decorations. Rebuilding is a whole-document char diff, so
48
+ // reuse it when the same state recurs (undo/redo to a prior revision, or a doc-changing transaction
49
+ // that lands back on a cached buffer). The key is length-prefixed so no base/doc pair is ambiguous.
50
+ let cache: { base: string; doc: string; state: TrackState } | undefined;
51
+
52
+ const build = (base: string, state: EditorState): TrackState => {
53
+ const doc = state.doc.toString();
54
+ if (cache && cache.base === base && cache.doc === doc) {
55
+ return cache.state;
56
+ }
57
+ const decorations = [];
58
+ const phantomRanges = [];
59
+ const phantomPositions: number[] = [];
60
+ // Line starts carrying a change, for the gutter change-bar (deduped, one bar per line).
61
+ const changedLines = new Set<number>();
62
+ const markLines = (from: number, to: number) => {
63
+ // Trim a trailing newline so an inserted/removed paragraph break does not tag the following
64
+ // (empty) line — otherwise a new-paragraph suggestion draws a bar two lines deep.
65
+ let end = to;
66
+ while (end > from && state.doc.sliceString(end - 1, end) === '\n') {
67
+ end--;
68
+ }
69
+ for (let line = state.doc.lineAt(from).number; line <= state.doc.lineAt(end).number; line++) {
70
+ changedLines.add(state.doc.line(line).from);
71
+ }
72
+ };
73
+ for (const hunk of computeCharHunks(base, doc)) {
74
+ // Deletion: base text absent from the branch. Anchor the phantom at `fromB` with `side: -1` so it
75
+ // renders before a co-located insertion (giving `~~old~~new`).
76
+ if (hunk.toA > hunk.fromA) {
77
+ const widget = Decoration.widget({
78
+ widget: new PhantomDeleteWidget(base.slice(hunk.fromA, hunk.toA), colour),
79
+ side: -1,
80
+ }).range(hunk.fromB);
81
+ decorations.push(widget);
82
+ phantomRanges.push(widget);
83
+ phantomPositions.push(hunk.fromB);
84
+ markLines(hunk.fromB, hunk.fromB);
85
+ }
86
+ // Insertion: branch text absent from the base.
87
+ if (hunk.toB > hunk.fromB) {
88
+ decorations.push(insertMark.range(hunk.fromB, hunk.toB));
89
+ markLines(hunk.fromB, hunk.toB);
90
+ }
91
+ }
92
+ const result: TrackState = {
93
+ base,
94
+ // `sort: true` orders the interleaved widget/mark ranges (co-located phantom before its mark).
95
+ decorations: Decoration.set(decorations, true),
96
+ atomic: Decoration.set(phantomRanges, true),
97
+ phantoms: phantomPositions,
98
+ changeBars: RangeSet.of(
99
+ [...changedLines].sort((a, b) => a - b).map((from) => new ChangeBarMarker(colour).range(from)),
100
+ ),
101
+ };
102
+ cache = { base, doc, state: result };
103
+ return result;
104
+ };
105
+
106
+ const field = StateField.define<TrackState>({
107
+ create: (state) => build(main, state),
108
+ update: (value, transaction) => {
109
+ let base = value.base;
110
+ let baseChanged = false;
111
+ for (const effect of transaction.effects) {
112
+ if (effect.is(setBase)) {
113
+ base = effect.value;
114
+ baseChanged = true;
115
+ }
116
+ }
117
+ if (!transaction.docChanged && !baseChanged) {
118
+ return value;
119
+ }
120
+ return build(base, transaction.state);
121
+ },
122
+ provide: (self) => EditorView.decorations.from(self, (value) => value.decorations),
123
+ });
124
+
125
+ return [
126
+ field,
127
+ trackChangesKeymap(field),
128
+ trackChangesAtomic(field),
129
+ changeBars((state) => state.field(field).changeBars),
130
+ trackChangesTheme,
131
+ ];
132
+ };
133
+
134
+ type TrackState = {
135
+ base: string;
136
+ decorations: DecorationSet;
137
+ /** Phantom widgets only, for the atomic-range facet (they are zero-width, so this is inert today). */
138
+ atomic: DecorationSet;
139
+ /** Document offsets carrying a phantom, for the keymap guard. */
140
+ phantoms: readonly number[];
141
+ /** Per-changed-line gutter change-bar markers, tinted with the author colour. */
142
+ changeBars: RangeSet<GutterMarker>;
143
+ };
144
+
145
+ /**
146
+ * Phantom render of text present in `main` but deleted on the branch — struck through and
147
+ * non-editable, since the text has no extent in the document.
148
+ */
149
+ class PhantomDeleteWidget extends WidgetType {
150
+ #text: string;
151
+ #colour: string;
152
+
153
+ constructor(text: string, colour: string) {
154
+ super();
155
+ this.#text = text;
156
+ this.#colour = colour;
157
+ }
158
+
159
+ override eq(other: PhantomDeleteWidget): boolean {
160
+ return other.#text === this.#text && other.#colour === this.#colour;
161
+ }
162
+
163
+ override toDOM(): HTMLElement {
164
+ return (
165
+ Domino.of('span')
166
+ .classNames('cm-track-delete')
167
+ .attributes({ contenteditable: 'false' })
168
+ // Attribution colour is per-author (dynamic); the strikethrough itself is in the theme class.
169
+ .style({ color: this.#colour, textDecorationColor: this.#colour })
170
+ .text(this.#text).root
171
+ );
172
+ }
173
+
174
+ override ignoreEvent(): boolean {
175
+ return true;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * A phantom has zero document extent, so the caret can only sit beside it — a plain Backspace beside a
181
+ * phantom therefore deletes the real branch character hidden behind the strikethrough rather than the
182
+ * phantom. Swallow it so tracked deletions read as protected units. Arrow keys need no override: the
183
+ * zero-width phantom is stepped over in a single motion already.
184
+ */
185
+ const trackChangesKeymap = (field: StateField<TrackState>): Extension =>
186
+ keymap.of([
187
+ {
188
+ key: 'Backspace',
189
+ run: (view) => {
190
+ const selection = view.state.selection.main;
191
+ return selection.empty && view.state.field(field).phantoms.includes(selection.head);
192
+ },
193
+ },
194
+ ]);
195
+
196
+ /** Register the phantoms as atomic ranges so cursor motion treats each as one unit. */
197
+ const trackChangesAtomic = (field: StateField<TrackState>): Extension =>
198
+ EditorView.atomicRanges.of((view) => view.state.field(field, false)?.atomic ?? Decoration.none);
199
+
200
+ const trackChangesTheme = EditorView.baseTheme({
201
+ // Insertion: author-coloured (inline) and underlined so it reads as added, not merely tinted.
202
+ '& .cm-track-insert': {
203
+ textDecoration: 'underline',
204
+ textDecorationThickness: '1px',
205
+ textUnderlineOffset: '2px',
206
+ },
207
+ // Deletion: struck through and de-emphasised; the removed base text, rendered as a phantom.
208
+ // `pre-wrap` preserves newlines in a multi-line/block deletion (a plain span collapses them to a
209
+ // single space), so a removed paragraph reads with its line breaks intact.
210
+ '& .cm-track-delete': {
211
+ textDecoration: 'line-through',
212
+ opacity: 0.7,
213
+ whiteSpace: 'pre-wrap',
214
+ },
215
+ });
@@ -11,6 +11,7 @@ import { type EditorState, type Extension, StateEffect, StateField } from '@code
11
11
  */
12
12
  export const setBusy = StateEffect.define<boolean>();
13
13
 
14
+ /** Shared busy StateField. Included by {@link busy}; exported for direct reads (see {@link isBusy}). */
14
15
  export const busyState = StateField.define<boolean>({
15
16
  create: () => false,
16
17
  update: (value, tr) => {
@@ -7,6 +7,10 @@ import { EditorView } from '@codemirror/view';
7
7
 
8
8
  const focusEffect = StateEffect.define<boolean>();
9
9
 
10
+ /**
11
+ * Boolean StateField holding the editor's current focus state.
12
+ * Included by {@link focus}; exported for direct reads.
13
+ */
10
14
  export const focusField = StateField.define<boolean>({
11
15
  create: () => false,
12
16
  update: (value, tr) => {
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './busy';
6
+ export * from './focus';
7
+ export * from './modal';
8
+ export * from './selection';
@@ -9,7 +9,7 @@ import * as Schema from 'effect/Schema';
9
9
  import { debounce } from '@dxos/async';
10
10
  import { isTruthy } from '@dxos/util';
11
11
 
12
- import { singleValueFacet } from '../util';
12
+ import { singleValueFacet } from '../../util';
13
13
 
14
14
  /**
15
15
  * Currently edited document id as FQ string.
@@ -26,14 +26,14 @@ export type EditorSelectionState = {
26
26
  selection?: EditorSelection;
27
27
  };
28
28
 
29
- export const EditorSelectionSchema = Schema.Struct({
29
+ export const EditorSelection = Schema.Struct({
30
30
  anchor: Schema.Number,
31
31
  head: Schema.optional(Schema.Number),
32
32
  }).pipe(Schema.mutable);
33
33
 
34
34
  export const EditorSelectionStateSchema = Schema.Struct({
35
35
  scrollTo: Schema.optional(Schema.Number),
36
- selection: Schema.optional(EditorSelectionSchema),
36
+ selection: Schema.optional(EditorSelection),
37
37
  }).pipe(Schema.mutable);
38
38
 
39
39
  export type EditorStateStore = {
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './pending';
6
+ export * from './scrolling';
7
+ export * from './typewriter';
@@ -126,6 +126,7 @@ export class PendingTextStreamer {
126
126
  #postHandle: unknown = null;
127
127
  /** The in-flight post-process pass, tracked so `flush()` can await it (drain contract). */
128
128
  #postPromise: Promise<void> | null = null;
129
+ #disposed = false;
129
130
 
130
131
  constructor(sink: PendingTextSink, options: PendingTextStreamerOptions = {}) {
131
132
  this.#sink = sink;
@@ -200,6 +201,7 @@ export class PendingTextStreamer {
200
201
  }
201
202
 
202
203
  dispose(): void {
204
+ this.#disposed = true;
203
205
  [this.#bufferHandle, this.#wordHandle, this.#postHandle].forEach((handle) => this.#clear(handle));
204
206
  this.#bufferHandle = this.#wordHandle = this.#postHandle = null;
205
207
  }
@@ -248,11 +250,16 @@ export class PendingTextStreamer {
248
250
  }
249
251
 
250
252
  async #runPostProcess(): Promise<void> {
253
+ const { postProcess } = this.#options;
254
+ if (!postProcess) {
255
+ return;
256
+ }
251
257
  const input = this.#final;
252
258
  try {
253
- const result = await this.#options.postProcess!(input);
254
- // Skip if the buffer grew while processing — a later pass will cover the newer text.
255
- if (result !== input && this.#final === input) {
259
+ const result = await postProcess(input);
260
+ // Skip if disposed while processing (avoid writing into a torn-down sink / destroyed view), or
261
+ // if the buffer grew — a later pass will cover the newer text.
262
+ if (!this.#disposed && result !== input && this.#final === input) {
256
263
  this.#final = result;
257
264
  this.#sink.replaceFinal(result);
258
265
  }
@@ -6,7 +6,7 @@ import { EditorState } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { describe, test } from 'vitest';
8
8
 
9
- import { isBusy } from '../busy';
9
+ import { isBusy } from '../../state/busy';
10
10
  import {
11
11
  appendPendingText,
12
12
  cancelPending,
@@ -7,8 +7,8 @@ import { type Command, Decoration, EditorView, WidgetType, keymap } from '@codem
7
7
 
8
8
  import { Domino } from '@dxos/ui';
9
9
 
10
- import { busy, setBusy } from '../busy';
11
- import { type MarkerHue, markerButtons, markerText, markerTheme } from '../marker';
10
+ import { type MarkerHue, markerButtons, markerText, markerTheme } from '../../decoration/marker';
11
+ import { busy, setBusy } from '../../state/busy';
12
12
 
13
13
  //
14
14
  // State.
@@ -49,6 +49,7 @@ const emptyAt = (anchor: number, placeholder?: string): PendingTextState => ({
49
49
  placeholder,
50
50
  });
51
51
 
52
+ /** StateField holding the pending text buffer (anchor, final, interim, placeholder); building block of the pendingText extension. */
52
53
  export const pendingTextState = StateField.define<PendingTextState | null>({
53
54
  create: () => null,
54
55
  update: (value, tr) => {
@@ -221,23 +222,6 @@ const busyListener = EditorView.updateListener.of((update) => {
221
222
  });
222
223
  });
223
224
 
224
- const styles = EditorView.theme({
225
- // Keep the marker and affordances on a single line (it already sits on its own line at the anchor).
226
- '.cm-pending-text': {
227
- display: 'inline-flex',
228
- alignItems: 'center',
229
- gap: '0.5rem',
230
- },
231
- // The interim tail is distinguished from the finalized text only by reduced opacity.
232
- '.cm-pending-text-interim': {
233
- opacity: 0.6,
234
- },
235
- // Keep the "Recording…" indicator (text + spinner) on a single line.
236
- '.cm-pending-text-indicator': {
237
- whiteSpace: 'nowrap',
238
- },
239
- });
240
-
241
225
  //
242
226
  // Extension.
243
227
  //
@@ -262,3 +246,20 @@ export const pendingText = (): Extension => [
262
246
  { key: 'Escape', run: cancelPending },
263
247
  ]),
264
248
  ];
249
+
250
+ const styles = EditorView.theme({
251
+ // Keep the marker and affordances on a single line (it already sits on its own line at the anchor).
252
+ '.cm-pending-text': {
253
+ display: 'inline-flex',
254
+ alignItems: 'center',
255
+ gap: '0.5rem',
256
+ },
257
+ // The interim tail is distinguished from the finalized text only by reduced opacity.
258
+ '.cm-pending-text-interim': {
259
+ opacity: 0.6,
260
+ },
261
+ // Keep the "Recording…" indicator (text + spinner) on a single line.
262
+ '.cm-pending-text-indicator': {
263
+ whiteSpace: 'nowrap',
264
+ },
265
+ });
@@ -214,11 +214,7 @@ export const autoScroll = ({ scrollOnResize = true }: AutoScrollProps = {}) => {
214
214
  const button = Domino.of('button')
215
215
  .classNames('dx-button bg-accent-bg aspect-square')
216
216
  .attributes({ 'data-density': 'sm' })
217
- .append(
218
- Domino.of('dx-icon' as any)
219
- .classNames(getSize(4))
220
- .attributes({ icon: 'ph--arrow-down--regular' }),
221
- )
217
+ .append(Domino.of('dx-icon').classNames(getSize(4)).attributes({ icon: 'ph--arrow-down--regular' }))
222
218
  .on('click', () => {
223
219
  setPinned(true);
224
220
  view.dispatch({
@@ -235,7 +231,14 @@ export const autoScroll = ({ scrollOnResize = true }: AutoScrollProps = {}) => {
235
231
  .classNames('cm-scroll-button transition-opacity duration-300 opacity-0 z-1')
236
232
  .append(button).root as HTMLDivElement;
237
233
 
238
- view.scrollDOM.parentElement!.appendChild(buttonContainer);
234
+ // The button lives outside CM's managed DOM (the scroller's parent), so remove it on
235
+ // teardown; otherwise it is orphaned and duplicated when the editor is recreated.
236
+ view.scrollDOM.parentElement?.appendChild(buttonContainer);
237
+ }
238
+
239
+ destroy() {
240
+ buttonContainer?.remove();
241
+ buttonContainer = undefined;
239
242
  }
240
243
  },
241
244
  ),
@@ -26,6 +26,7 @@ const scrollPastEndPlugin = ViewPlugin.fromClass(
26
26
  },
27
27
  );
28
28
 
29
+ /** Adds bottom padding so the last line can scroll to the top of the viewport, accounting for the actual last-line height. */
29
30
  export const scrollPastEnd = (): Extension => [
30
31
  scrollPastEndPlugin,
31
32
  EditorView.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?._attrs ?? null),
@@ -47,15 +47,17 @@ export const scrollbarAutohide = ({ timeout = 800 }: ScrollbarAutohideOptions =
47
47
  };
48
48
  },
49
49
  ),
50
- EditorView.theme({
51
- // Reveal the thumb only while actively scrolling.
52
- '.cm-scroller.cm-scrolling::-webkit-scrollbar-thumb': {
53
- background: 'var(--color-scrollbar-thumb)',
54
- },
55
- // Suppress the base theme's hover-reveal (higher specificity via `:not(.cm-scrolling)`), so a
56
- // hovered/focused-but-idle editor keeps the thumb hidden.
57
- '&:hover .cm-scroller:not(.cm-scrolling)::-webkit-scrollbar-thumb': {
58
- background: 'transparent',
59
- },
60
- }),
50
+ styles,
61
51
  ];
52
+
53
+ const styles = EditorView.theme({
54
+ // Reveal the thumb only while actively scrolling.
55
+ '.cm-scroller.cm-scrolling::-webkit-scrollbar-thumb': {
56
+ background: 'var(--color-scrollbar-thumb)',
57
+ },
58
+ // Suppress the base theme's hover-reveal (higher specificity via `:not(.cm-scrolling)`), so a
59
+ // hovered/focused-but-idle editor keeps the thumb hidden.
60
+ '&:hover .cm-scroller:not(.cm-scrolling)::-webkit-scrollbar-thumb': {
61
+ background: 'transparent',
62
+ },
63
+ });
@@ -15,50 +15,7 @@ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate
15
15
 
16
16
  import { Domino } from '@dxos/ui';
17
17
 
18
- /** Annotate a transaction to bypass the typewriter buffer (content appears immediately). */
19
- export const typewriterBypass = Annotation.define<boolean>();
20
-
21
- /**
22
- * Public state effect signalling whether typewriter's drip queue is currently draining.
23
- * Other extensions can subscribe to coordinate behaviour with the typewriter (e.g. hide
24
- * a block-widget footer while text is being dripped to avoid scroll-measure conflicts).
25
- *
26
- * Dispatched at queue transitions:
27
- * - `true` — empty → non-empty (drain rAF/interval is starting).
28
- * - `false` — non-empty → empty (last char written, drain stopped).
29
- */
30
- export const typewriterDrainingEffect = StateEffect.define<boolean>();
31
-
32
- /**
33
- * Buffer state. The pending text is `text.slice(head)` — but `head` is advanced
34
- * without slicing on every drip to avoid O(n²) string copying. The prefix is
35
- * compacted lazily once it exceeds half the string or `COMPACT_HEAD_THRESHOLD`.
36
- */
37
- type BufferState = { text: string; head: number; insertAt: number };
38
-
39
- /** How long to show the cursor after motion has stopped. */
40
- const CURSOR_LINGER = 3_000;
41
-
42
- /** Per-frame time budget for draining the buffer. Stays well under a 16 ms frame. */
43
- const FRAME_BUDGET_MS = 4;
44
-
45
- /**
46
- * Visible characters of plain text emitted per animation frame. At 60 fps a value of 1
47
- * gives ~60 char/s — fast enough to keep up with most streams while still reading as a
48
- * smooth character-by-character drip. Atomic structures (XML elements, markdown links)
49
- * always flush whole regardless of this cap.
50
- */
51
- const CHARS_PER_FRAME = 5;
52
-
53
- /**
54
- * When the pending buffer exceeds this many characters, abandon typewriter pacing
55
- * and flush the entire buffer in one transaction. The visual benefit of typewriter
56
- * has negative value past human-reading rates.
57
- */
58
- const FLUSH_THRESHOLD = 2_000;
59
-
60
- /** Compaction trigger for the head-offset rope. */
61
- const COMPACT_HEAD_THRESHOLD = 4_096;
18
+ import { escapeRegExpSource } from '../../util';
62
19
 
63
20
  export type TypewriterOptions = {
64
21
  /** Show a blinking cursor at the insertion point while streaming. */
@@ -303,6 +260,51 @@ export const typewriter = (options: TypewriterOptions = {}): Extension => {
303
260
  ) as Extension[];
304
261
  };
305
262
 
263
+ /** Annotate a transaction to bypass the typewriter buffer (content appears immediately). */
264
+ export const typewriterBypass = Annotation.define<boolean>();
265
+
266
+ /**
267
+ * Public state effect signalling whether typewriter's drip queue is currently draining.
268
+ * Other extensions can subscribe to coordinate behaviour with the typewriter (e.g. hide
269
+ * a block-widget footer while text is being dripped to avoid scroll-measure conflicts).
270
+ *
271
+ * Dispatched at queue transitions:
272
+ * - `true` — empty → non-empty (drain rAF/interval is starting).
273
+ * - `false` — non-empty → empty (last char written, drain stopped).
274
+ */
275
+ export const typewriterDrainingEffect = StateEffect.define<boolean>();
276
+
277
+ /**
278
+ * Buffer state. The pending text is `text.slice(head)` — but `head` is advanced
279
+ * without slicing on every drip to avoid O(n²) string copying. The prefix is
280
+ * compacted lazily once it exceeds half the string or `COMPACT_HEAD_THRESHOLD`.
281
+ */
282
+ type BufferState = { text: string; head: number; insertAt: number };
283
+
284
+ /** How long to show the cursor after motion has stopped. */
285
+ const CURSOR_LINGER = 3_000;
286
+
287
+ /** Per-frame time budget for draining the buffer. Stays well under a 16 ms frame. */
288
+ const FRAME_BUDGET_MS = 4;
289
+
290
+ /**
291
+ * Visible characters of plain text emitted per animation frame. At 60 fps a value of 1
292
+ * gives ~60 char/s — fast enough to keep up with most streams while still reading as a
293
+ * smooth character-by-character drip. Atomic structures (XML elements, markdown links)
294
+ * always flush whole regardless of this cap.
295
+ */
296
+ const CHARS_PER_FRAME = 5;
297
+
298
+ /**
299
+ * When the pending buffer exceeds this many characters, abandon typewriter pacing
300
+ * and flush the entire buffer in one transaction. The visual benefit of typewriter
301
+ * has negative value past human-reading rates.
302
+ */
303
+ const FLUSH_THRESHOLD = 2_000;
304
+
305
+ /** Compaction trigger for the head-offset rope. */
306
+ const COMPACT_HEAD_THRESHOLD = 4_096;
307
+
306
308
  //
307
309
  // Cursor
308
310
  //
@@ -426,9 +428,6 @@ const OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
426
428
  /** Tag names are short — bound the slice we hand to the regex matcher. */
427
429
  const TAG_NAME_PROBE = 64;
428
430
 
429
- /** Escapes a string for safe embedding in RegExp source (tag names from the document). */
430
- const escapeRegExpSource = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
431
-
432
431
  type FlushResult = {
433
432
  count: number;
434
433
  /** Tag name to enter streaming mode for. */