@voithos-labs/aragonite 0.10.4 → 0.10.7

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 (1021) hide show
  1. package/README.md +2 -2
  2. package/dist/a11y-strings.d.ts +11 -5
  3. package/dist/a11y-strings.js +60 -5
  4. package/dist/action-contracts.d.ts +242 -155
  5. package/dist/action-contracts.js +2 -2
  6. package/dist/active-editor.d.ts +3 -3
  7. package/dist/active-editor.js +8 -9
  8. package/dist/ambient/ambient-dom.d.ts +2 -5
  9. package/dist/ambient/ambient-dom.js +16 -62
  10. package/dist/assert.js +6 -5
  11. package/dist/block-component.d.ts +135 -109
  12. package/dist/block-component.js +30 -22
  13. package/dist/block-id.d.ts +6 -10
  14. package/dist/block-id.js +25 -14
  15. package/dist/bounded-memo.d.ts +5 -5
  16. package/dist/bounded-memo.js +6 -6
  17. package/dist/components/BlockDragHandle.svelte +12 -21
  18. package/dist/components/BlockHost.svelte +56 -134
  19. package/dist/components/BlockList.svelte +22 -19
  20. package/dist/components/DecorationOverlay.svelte +9 -10
  21. package/dist/components/DecorationOverlay.svelte.d.ts +2 -2
  22. package/dist/components/Editor.svelte +603 -1126
  23. package/dist/components/Editor.svelte.d.ts +9 -20
  24. package/dist/components/GapCaret.svelte +26 -59
  25. package/dist/components/SelectionOverlay.svelte +26 -51
  26. package/dist/components/SelectionOverlay.svelte.d.ts +5 -3
  27. package/dist/components/TailInsert.svelte +8 -55
  28. package/dist/components/TailInsert.svelte.d.ts +4 -6
  29. package/dist/components/block-content-selector.d.ts +11 -11
  30. package/dist/components/block-content-selector.js +11 -11
  31. package/dist/components/block-el-lookup.d.ts +6 -0
  32. package/dist/components/block-el-lookup.js +27 -0
  33. package/dist/components/blocks/BlockquoteBlock.svelte +3 -3
  34. package/dist/components/blocks/ThematicBreakBlock.svelte +22 -63
  35. package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +0 -2
  36. package/dist/components/blocks/code/CodeBlock.svelte +255 -276
  37. package/dist/components/blocks/code/CodeBlock.svelte.d.ts +2 -3
  38. package/dist/components/blocks/code/CodeBlockRail.svelte +72 -87
  39. package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +9 -4
  40. package/dist/components/blocks/code/code-beforeinput.d.ts +1 -1
  41. package/dist/components/blocks/code/code-bootstrap.d.ts +3 -5
  42. package/dist/components/blocks/code/code-bootstrap.js +29 -34
  43. package/dist/components/blocks/code/code-context-actions.js +5 -4
  44. package/dist/components/blocks/code/code-enter.js +2 -2
  45. package/dist/components/blocks/code/code-fence-boundary.d.ts +29 -46
  46. package/dist/components/blocks/code/code-fence-boundary.js +40 -66
  47. package/dist/components/blocks/code/code-fence-exit.d.ts +6 -11
  48. package/dist/components/blocks/code/code-fence-exit.js +24 -25
  49. package/dist/components/blocks/code/code-indent.d.ts +2 -2
  50. package/dist/components/blocks/code/code-indent.js +4 -7
  51. package/dist/components/blocks/code/code-languages.d.ts +16 -12
  52. package/dist/components/blocks/code/code-languages.js +57 -39
  53. package/dist/components/blocks/code/code-paste-surface.d.ts +3 -2
  54. package/dist/components/blocks/code/code-paste-surface.js +9 -18
  55. package/dist/components/blocks/code/code-renderer.d.ts +21 -5
  56. package/dist/components/blocks/code/code-renderer.js +90 -125
  57. package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +5 -8
  58. package/dist/components/blocks/directive/activate-directives.d.ts +4 -4
  59. package/dist/components/blocks/directive/activate-directives.js +18 -13
  60. package/dist/components/blocks/editable-leaf.d.ts +50 -74
  61. package/dist/components/blocks/editable-leaf.js +141 -203
  62. package/dist/components/blocks/editable-surface.d.ts +96 -100
  63. package/dist/components/blocks/editable-surface.js +101 -109
  64. package/dist/components/blocks/list/ListBlock.svelte +45 -90
  65. package/dist/components/blocks/list/ListItemBlock.svelte +93 -147
  66. package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +1 -0
  67. package/dist/components/blocks/list/task-checkbox.d.ts +5 -2
  68. package/dist/components/blocks/list/task-checkbox.js +12 -7
  69. package/dist/components/blocks/plain-text-backend.d.ts +4 -20
  70. package/dist/components/blocks/plain-text-backend.js +9 -43
  71. package/dist/components/blocks/surface-wiring.svelte.d.ts +8 -9
  72. package/dist/components/blocks/surface-wiring.svelte.js +15 -34
  73. package/dist/components/blocks/table/TableActionMenu.svelte +19 -18
  74. package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +3 -1
  75. package/dist/components/blocks/table/TableBlock.svelte +100 -165
  76. package/dist/components/blocks/table/TableBlock.svelte.d.ts +3 -3
  77. package/dist/components/blocks/table/TableCellBlock.svelte +307 -385
  78. package/dist/components/blocks/table/TableCellBlock.svelte.d.ts +1 -0
  79. package/dist/components/blocks/table/TableRowBlock.svelte +60 -104
  80. package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -1
  81. package/dist/components/blocks/table/cell-keydown-plan.d.ts +4 -9
  82. package/dist/components/blocks/table/cell-keydown-plan.js +8 -12
  83. package/dist/components/blocks/table/cell-pointer.d.ts +18 -23
  84. package/dist/components/blocks/table/cell-pointer.js +49 -71
  85. package/dist/components/blocks/table/cell-render.d.ts +24 -29
  86. package/dist/components/blocks/table/cell-render.js +29 -31
  87. package/dist/components/blocks/table/selected-cells.d.ts +7 -7
  88. package/dist/components/blocks/table/selected-cells.js +9 -13
  89. package/dist/components/blocks/table/table-caret-at-point.d.ts +4 -4
  90. package/dist/components/blocks/table/table-caret-at-point.js +5 -5
  91. package/dist/components/blocks/table/table-cell-paste.d.ts +5 -11
  92. package/dist/components/blocks/table/table-cell-paste.js +27 -43
  93. package/dist/components/blocks/table/table-drag-hit-test.js +2 -1
  94. package/dist/components/blocks/table/table-menu-model.d.ts +4 -8
  95. package/dist/components/blocks/table/table-menu-model.js +6 -10
  96. package/dist/components/blocks/table/table-navigation.js +2 -2
  97. package/dist/components/blocks/text/TextEditableBlock.svelte +392 -310
  98. package/dist/components/blocks/text/TextEditableBlock.svelte.d.ts +2 -1
  99. package/dist/components/blocks/text/auto-pair-record.d.ts +23 -0
  100. package/dist/components/blocks/text/auto-pair-record.js +51 -0
  101. package/dist/components/blocks/text/click-snap-guard.d.ts +8 -6
  102. package/dist/components/blocks/text/click-snap-guard.js +10 -7
  103. package/dist/components/blocks/text/composition-seat.d.ts +17 -14
  104. package/dist/components/blocks/text/composition-seat.js +11 -11
  105. package/dist/components/blocks/text/construct-edge-delete.d.ts +20 -20
  106. package/dist/components/blocks/text/construct-edge-delete.js +50 -65
  107. package/dist/components/blocks/text/construct-reveal.d.ts +16 -21
  108. package/dist/components/blocks/text/construct-reveal.js +13 -14
  109. package/dist/components/blocks/text/delimiter-autopair.d.ts +51 -34
  110. package/dist/components/blocks/text/delimiter-autopair.js +87 -103
  111. package/dist/components/blocks/text/edge-policy-dispatch.d.ts +44 -46
  112. package/dist/components/blocks/text/edge-policy-dispatch.js +150 -181
  113. package/dist/components/blocks/text/edge-seat.d.ts +17 -26
  114. package/dist/components/blocks/text/edge-seat.js +43 -70
  115. package/dist/components/blocks/text/link-at-point.d.ts +9 -9
  116. package/dist/components/blocks/text/link-at-point.js +12 -15
  117. package/dist/components/blocks/text/live-join-seam.d.ts +6 -9
  118. package/dist/components/blocks/text/live-join-seam.js +75 -112
  119. package/dist/components/blocks/text/live-selection-edit.d.ts +14 -43
  120. package/dist/components/blocks/text/live-selection-edit.js +32 -108
  121. package/dist/components/blocks/text/live-split-rebalance.d.ts +9 -13
  122. package/dist/components/blocks/text/live-split-rebalance.js +50 -67
  123. package/dist/components/blocks/text/marker-completion.d.ts +7 -12
  124. package/dist/components/blocks/text/marker-completion.js +17 -10
  125. package/dist/components/blocks/text/pending-mark-insert.d.ts +11 -18
  126. package/dist/components/blocks/text/pending-mark-insert.js +30 -49
  127. package/dist/components/blocks/text/screen-diff.d.ts +5 -15
  128. package/dist/components/blocks/text/screen-diff.js +5 -19
  129. package/dist/components/blocks/text/text-clipboard.d.ts +23 -32
  130. package/dist/components/blocks/text/text-clipboard.js +27 -32
  131. package/dist/components/blocks/text/text-keydown.d.ts +20 -30
  132. package/dist/components/blocks/text/text-keydown.js +54 -51
  133. package/dist/components/blocks/text/text-render.d.ts +25 -35
  134. package/dist/components/blocks/text/text-render.js +82 -61
  135. package/dist/components/blocks/text/widget-adjacency.d.ts +9 -9
  136. package/dist/components/blocks/text/widget-adjacency.js +17 -17
  137. package/dist/components/blocks/text/widget-interaction.d.ts +67 -43
  138. package/dist/components/blocks/text/widget-interaction.js +212 -199
  139. package/dist/components/blocks/widget-portal.d.ts +20 -29
  140. package/dist/components/blocks/widget-portal.js +16 -14
  141. package/dist/components/built-in-blocks.d.ts +3 -4
  142. package/dist/components/built-in-blocks.js +17 -18
  143. package/dist/components/drag-handle.d.ts +12 -35
  144. package/dist/components/drag-handle.js +22 -68
  145. package/dist/components/editor-built-ins.d.ts +1 -0
  146. package/dist/components/editor-built-ins.js +16 -0
  147. package/dist/components/editor-root-clipboard.d.ts +6 -6
  148. package/dist/components/editor-root-clipboard.js +16 -27
  149. package/dist/components/editor-root-document-swap.d.ts +49 -0
  150. package/dist/components/editor-root-document-swap.js +54 -0
  151. package/dist/components/editor-root-focus.d.ts +5 -4
  152. package/dist/components/editor-root-focus.js +51 -22
  153. package/dist/components/editor-root-focused-surface.d.ts +35 -0
  154. package/dist/components/editor-root-focused-surface.js +65 -0
  155. package/dist/components/editor-root-geometry.d.ts +15 -24
  156. package/dist/components/editor-root-geometry.js +27 -31
  157. package/dist/components/editor-root-gestures.d.ts +39 -0
  158. package/dist/components/editor-root-gestures.js +189 -0
  159. package/dist/components/editor-root-keydown.d.ts +10 -25
  160. package/dist/components/editor-root-keydown.js +19 -43
  161. package/dist/components/editor-root-listeners.d.ts +22 -24
  162. package/dist/components/editor-root-listeners.js +49 -24
  163. package/dist/components/editor-root-menus.d.ts +51 -0
  164. package/dist/components/editor-root-menus.js +147 -0
  165. package/dist/components/editor-root-mode-flip.d.ts +16 -11
  166. package/dist/components/editor-root-mode-flip.js +36 -34
  167. package/dist/components/editor-root-scroll-host.d.ts +22 -0
  168. package/dist/components/editor-root-scroll-host.js +34 -0
  169. package/dist/components/editor-root-test-surface.d.ts +48 -0
  170. package/dist/components/editor-root-test-surface.js +6 -0
  171. package/dist/components/image/ImageOverlayHost.svelte +44 -35
  172. package/dist/components/image/ImageOverlayHost.svelte.d.ts +5 -6
  173. package/dist/components/image/ImageProperties.svelte +37 -41
  174. package/dist/components/image/ImageProperties.svelte.d.ts +9 -4
  175. package/dist/components/image/ImageResizeHandles.svelte +14 -26
  176. package/dist/components/image/image-crop.d.ts +5 -11
  177. package/dist/components/image/image-crop.js +5 -11
  178. package/dist/components/image/image-edit-commit.d.ts +19 -16
  179. package/dist/components/image/image-edit-commit.js +74 -38
  180. package/dist/components/image/image-resize.d.ts +2 -5
  181. package/dist/components/image/image-resize.js +3 -6
  182. package/dist/components/image/image-widget-editing.d.ts +3 -3
  183. package/dist/components/image/image-widget-editing.js +9 -9
  184. package/dist/components/image/widget-dom.d.ts +2 -3
  185. package/dist/components/image/widget-dom.js +19 -24
  186. package/dist/components/image/widget-selection-state.svelte.d.ts +10 -11
  187. package/dist/components/image/widget-selection-state.svelte.js +26 -19
  188. package/dist/components/kind-cue.svelte.d.ts +21 -0
  189. package/dist/components/kind-cue.svelte.js +35 -0
  190. package/dist/components/link-card/LinkCard.svelte +115 -64
  191. package/dist/components/link-card/LinkCard.svelte.d.ts +7 -5
  192. package/dist/components/link-card/LinkCardHost.svelte +42 -33
  193. package/dist/components/link-card/LinkCardHost.svelte.d.ts +10 -8
  194. package/dist/components/link-card/link-card-commit.d.ts +16 -19
  195. package/dist/components/link-card/link-card-commit.js +21 -30
  196. package/dist/components/link-card/link-card-entry.d.ts +13 -23
  197. package/dist/components/link-card/link-card-entry.js +16 -24
  198. package/dist/components/link-card/link-card-state.svelte.d.ts +17 -21
  199. package/dist/components/link-card/link-card-state.svelte.js +2 -2
  200. package/dist/components/lrd-map-gate.d.ts +4 -11
  201. package/dist/components/lrd-map-gate.js +5 -11
  202. package/dist/components/menu/BlockMenu.svelte +20 -81
  203. package/dist/components/menu/BlockMenu.svelte.d.ts +11 -9
  204. package/dist/components/menu/InlineMenuHost.svelte +191 -0
  205. package/dist/components/menu/InlineMenuHost.svelte.d.ts +13 -0
  206. package/dist/components/menu/MenuIcon.svelte +3 -103
  207. package/dist/components/menu/MenuIcon.svelte.d.ts +2 -44
  208. package/dist/components/menu/SelectionToolbar.svelte +30 -35
  209. package/dist/components/menu/SelectionToolbar.svelte.d.ts +3 -5
  210. package/dist/components/menu/clipboard-actions.d.ts +3 -4
  211. package/dist/components/menu/clipboard-actions.js +3 -4
  212. package/dist/components/menu/default-context-actions.d.ts +4 -13
  213. package/dist/components/menu/default-context-actions.js +49 -67
  214. package/dist/components/menu/flyout-placement.d.ts +2 -2
  215. package/dist/components/menu/flyout-placement.js +2 -2
  216. package/dist/components/menu/menu-presence.svelte.d.ts +12 -0
  217. package/dist/components/menu/menu-presence.svelte.js +19 -0
  218. package/dist/components/paste-image-arm.d.ts +6 -11
  219. package/dist/components/paste-image-arm.js +11 -18
  220. package/dist/components/portal.d.ts +2 -5
  221. package/dist/components/portal.js +4 -7
  222. package/dist/core/directive/activate.d.ts +4 -5
  223. package/dist/core/directive/activate.js +8 -9
  224. package/dist/core/directive/container-opener.js +13 -12
  225. package/dist/core/directive/grammar.d.ts +2 -5
  226. package/dist/core/directive/grammar.js +10 -10
  227. package/dist/core/directive/kinds.d.ts +4 -7
  228. package/dist/core/directive/kinds.js +14 -16
  229. package/dist/core/directive/registry.d.ts +12 -9
  230. package/dist/core/directive/registry.js +19 -20
  231. package/dist/core/directive/text-recognizer.d.ts +3 -2
  232. package/dist/core/directive/text-recognizer.js +5 -9
  233. package/dist/core/escapable.d.ts +1 -5
  234. package/dist/core/escapable.js +1 -5
  235. package/dist/core/inline/backticks.d.ts +3 -4
  236. package/dist/core/inline/backticks.js +3 -4
  237. package/dist/core/inline/character-refs.js +2 -1
  238. package/dist/core/inline/destination-bytes.d.ts +6 -3
  239. package/dist/core/inline/destination-bytes.js +50 -8
  240. package/dist/core/inline/entity-widget.d.ts +4 -3
  241. package/dist/core/inline/entity-widget.js +6 -6
  242. package/dist/core/inline/format-toggle.d.ts +22 -28
  243. package/dist/core/inline/format-toggle.js +97 -129
  244. package/dist/core/inline/html-entities.d.ts +2 -3
  245. package/dist/core/inline/html-entities.js +2 -3
  246. package/dist/core/inline/html-tag-grammar.js +6 -2
  247. package/dist/core/inline/image-dimensions.d.ts +2 -0
  248. package/dist/core/inline/image-dimensions.js +19 -2
  249. package/dist/core/inline/image-source-bytes.d.ts +11 -0
  250. package/dist/core/inline/image-source-bytes.js +71 -0
  251. package/dist/core/inline/index.d.ts +28 -21
  252. package/dist/core/inline/index.js +56 -34
  253. package/dist/core/inline/inline-cache.d.ts +12 -15
  254. package/dist/core/inline/inline-cache.js +11 -14
  255. package/dist/core/inline/inline-widgets.d.ts +54 -75
  256. package/dist/core/inline/inline-widgets.js +54 -62
  257. package/dist/core/inline/link-destination.d.ts +31 -0
  258. package/dist/core/inline/link-destination.js +129 -0
  259. package/dist/core/inline/link-reference-resolver.d.ts +8 -6
  260. package/dist/core/inline/link-reference-resolver.js +10 -6
  261. package/dist/core/inline/link-source-bytes.d.ts +31 -0
  262. package/dist/{components/blocks/text → core/inline}/link-source-bytes.js +43 -54
  263. package/dist/core/inline/live-edit/read-back.d.ts +29 -0
  264. package/dist/core/inline/live-edit/read-back.js +50 -0
  265. package/dist/core/inline/picture.d.ts +10 -0
  266. package/dist/core/inline/picture.js +40 -0
  267. package/dist/core/inline/scan/autolinks.d.ts +7 -8
  268. package/dist/core/inline/scan/autolinks.js +80 -59
  269. package/dist/core/inline/scan/brackets.d.ts +2 -2
  270. package/dist/core/inline/scan/brackets.js +6 -103
  271. package/dist/core/inline/scan/code-spans.d.ts +4 -0
  272. package/dist/core/inline/scan/code-spans.js +8 -0
  273. package/dist/core/inline/scan/emphasis.js +10 -14
  274. package/dist/core/inline/scan/index.d.ts +3 -1
  275. package/dist/core/inline/scan/index.js +32 -33
  276. package/dist/core/inline/scan/plugin-syntax.d.ts +28 -36
  277. package/dist/core/inline/scan/plugin-syntax.js +75 -89
  278. package/dist/core/inline/scan/scan-state.d.ts +2 -0
  279. package/dist/core/inline/scan/scan-state.js +1 -0
  280. package/dist/core/inline/scan/simple-nodes.d.ts +2 -5
  281. package/dist/core/inline/scan/simple-nodes.js +2 -5
  282. package/dist/core/inline/scan/triggers.d.ts +17 -0
  283. package/dist/core/inline/scan/triggers.js +26 -0
  284. package/dist/core/inline/scan/url.js +8 -10
  285. package/dist/core/inline/transparency.d.ts +2 -1
  286. package/dist/core/inline/transparency.js +31 -15
  287. package/dist/core/inline/visibility.d.ts +30 -49
  288. package/dist/core/inline/visibility.js +31 -49
  289. package/dist/core/inline/walk.d.ts +4 -10
  290. package/dist/core/inline/walk.js +4 -10
  291. package/dist/core/inline-render.d.ts +22 -27
  292. package/dist/core/inline-render.js +51 -60
  293. package/dist/core/lines.d.ts +67 -26
  294. package/dist/core/lines.js +164 -33
  295. package/dist/core/metadata-parity.d.ts +9 -0
  296. package/dist/core/metadata-parity.js +25 -0
  297. package/dist/core/node-views.d.ts +7 -6
  298. package/dist/core/node-views.js +5 -5
  299. package/dist/core/nodes.d.ts +34 -44
  300. package/dist/core/nodes.js +4 -12
  301. package/dist/core/parser.d.ts +27 -34
  302. package/dist/core/parser.js +53 -67
  303. package/dist/core/parsers/blockquote.d.ts +5 -8
  304. package/dist/core/parsers/blockquote.js +11 -10
  305. package/dist/core/parsers/built-in-openers.d.ts +2 -5
  306. package/dist/core/parsers/built-in-openers.js +5 -8
  307. package/dist/core/parsers/fence-syntax.d.ts +36 -7
  308. package/dist/core/parsers/fence-syntax.js +61 -11
  309. package/dist/core/parsers/fenced-code.d.ts +16 -0
  310. package/dist/core/parsers/fenced-code.js +16 -15
  311. package/dist/core/parsers/heading.d.ts +19 -2
  312. package/dist/core/parsers/heading.js +50 -2
  313. package/dist/core/parsers/html-block.d.ts +3 -6
  314. package/dist/core/parsers/html-block.js +10 -11
  315. package/dist/core/parsers/indented-code.d.ts +1 -1
  316. package/dist/core/parsers/indented-code.js +2 -8
  317. package/dist/core/parsers/link-reference.d.ts +5 -3
  318. package/dist/core/parsers/link-reference.js +116 -120
  319. package/dist/core/parsers/list.d.ts +12 -5
  320. package/dist/core/parsers/list.js +53 -36
  321. package/dist/core/parsers/paragraph.d.ts +3 -3
  322. package/dist/core/parsers/paragraph.js +14 -5
  323. package/dist/core/parsers/table-completion.d.ts +2 -5
  324. package/dist/core/parsers/table-completion.js +7 -10
  325. package/dist/core/parsers/table.d.ts +8 -8
  326. package/dist/core/parsers/table.js +23 -26
  327. package/dist/core/parsers/thematic-break.js +15 -14
  328. package/dist/core/paths.d.ts +13 -0
  329. package/dist/core/paths.js +31 -0
  330. package/dist/core/terminator-escalation.d.ts +0 -7
  331. package/dist/core/terminator-escalation.js +5 -8
  332. package/dist/core/url-policy.d.ts +2 -5
  333. package/dist/core/url-policy.js +8 -12
  334. package/dist/cursor/caret-memory.d.ts +34 -0
  335. package/dist/cursor/caret-memory.js +82 -0
  336. package/dist/cursor/coordinate-spaces.d.ts +26 -19
  337. package/dist/cursor/coordinate-spaces.js +32 -20
  338. package/dist/cursor/dom-walk.d.ts +7 -13
  339. package/dist/cursor/dom-walk.js +7 -13
  340. package/dist/cursor/edge-affinity.d.ts +7 -35
  341. package/dist/cursor/edge-affinity.js +13 -44
  342. package/dist/cursor/focused-caret.d.ts +6 -8
  343. package/dist/cursor/focused-caret.js +7 -16
  344. package/dist/cursor/height-model.d.ts +3 -3
  345. package/dist/cursor/height-model.js +5 -5
  346. package/dist/cursor/height-oracle.d.ts +10 -6
  347. package/dist/cursor/height-oracle.js +26 -74
  348. package/dist/cursor/observe-resize.d.ts +13 -0
  349. package/dist/cursor/observe-resize.js +43 -0
  350. package/dist/cursor/overlay-rects.d.ts +2 -5
  351. package/dist/cursor/overlay-rects.js +2 -5
  352. package/dist/cursor/overlay-remeasure.d.ts +6 -7
  353. package/dist/cursor/overlay-remeasure.js +10 -14
  354. package/dist/cursor/pending-marks.d.ts +7 -9
  355. package/dist/cursor/pending-marks.js +6 -27
  356. package/dist/cursor/point-offset.d.ts +16 -10
  357. package/dist/cursor/point-offset.js +60 -25
  358. package/dist/cursor/reveal-source.d.ts +7 -11
  359. package/dist/cursor/reveal-source.js +10 -18
  360. package/dist/cursor/scroll-ancestors.d.ts +9 -20
  361. package/dist/cursor/scroll-ancestors.js +14 -26
  362. package/dist/cursor/scroll-owner.d.ts +92 -0
  363. package/dist/cursor/scroll-owner.js +283 -0
  364. package/dist/cursor/scrollport.d.ts +14 -15
  365. package/dist/cursor/scrollport.js +7 -13
  366. package/dist/cursor/sticky-column.d.ts +6 -29
  367. package/dist/cursor/sticky-column.js +6 -49
  368. package/dist/cursor/sticky-measure.d.ts +14 -9
  369. package/dist/cursor/sticky-measure.js +99 -49
  370. package/dist/cursor/surface-backend.d.ts +41 -0
  371. package/dist/cursor/surface-backend.js +78 -0
  372. package/dist/cursor/typography-estimates.d.ts +3 -3
  373. package/dist/cursor/typography-estimates.js +3 -3
  374. package/dist/cursor/visual-lines.d.ts +25 -14
  375. package/dist/cursor/visual-lines.js +45 -46
  376. package/dist/cursor/widget-edge-snap.d.ts +10 -15
  377. package/dist/cursor/widget-edge-snap.js +13 -12
  378. package/dist/cursor/widget-offset.d.ts +110 -104
  379. package/dist/cursor/widget-offset.js +344 -179
  380. package/dist/debug/diagnostics-report.d.ts +6 -6
  381. package/dist/debug/diagnostics-report.js +5 -5
  382. package/dist/debug/dump-tree.js +2 -2
  383. package/dist/debug/editor-diagnostics.d.ts +15 -0
  384. package/dist/debug/editor-diagnostics.js +33 -0
  385. package/dist/debug/inspect.d.ts +2 -2
  386. package/dist/debug/inspect.js +4 -4
  387. package/dist/debug/interaction-trace.d.ts +14 -15
  388. package/dist/debug/interaction-trace.js +19 -20
  389. package/dist/debug/operations-log.d.ts +1 -1
  390. package/dist/debug/operations-log.js +1 -1
  391. package/dist/decorations/buckets.d.ts +6 -12
  392. package/dist/decorations/buckets.js +4 -11
  393. package/dist/decorations/decoration-state.svelte.d.ts +2 -3
  394. package/dist/decorations/decoration-state.svelte.js +24 -26
  395. package/dist/decorations/island-dom.d.ts +10 -18
  396. package/dist/decorations/island-dom.js +25 -50
  397. package/dist/decorations/reserved-attrs.d.ts +6 -8
  398. package/dist/decorations/reserved-attrs.js +30 -17
  399. package/dist/decorations/types.d.ts +6 -7
  400. package/dist/decorations/types.js +2 -1
  401. package/dist/decorations/use-block-decorations.svelte.d.ts +19 -0
  402. package/dist/decorations/use-block-decorations.svelte.js +71 -0
  403. package/dist/decorations/widget-dom.d.ts +3 -3
  404. package/dist/decorations/widget-dom.js +5 -5
  405. package/dist/dev-warn.d.ts +5 -2
  406. package/dist/dev-warn.js +16 -4
  407. package/dist/editor-actions/ancestry-folds.d.ts +9 -14
  408. package/dist/editor-actions/ancestry-folds.js +13 -18
  409. package/dist/editor-actions/block-edit-core.d.ts +49 -22
  410. package/dist/editor-actions/block-edit-core.js +279 -151
  411. package/dist/editor-actions/block-edit-scope.d.ts +63 -50
  412. package/dist/editor-actions/block-edit-scope.js +100 -48
  413. package/dist/editor-actions/block-edit.d.ts +2 -3
  414. package/dist/editor-actions/block-edit.js +14 -90
  415. package/dist/editor-actions/commit/history.d.ts +4 -4
  416. package/dist/editor-actions/commit/history.js +33 -34
  417. package/dist/editor-actions/commit/reading-write-gate.d.ts +18 -0
  418. package/dist/editor-actions/commit/reading-write-gate.js +56 -0
  419. package/dist/editor-actions/commit/text-batch.d.ts +7 -11
  420. package/dist/editor-actions/commit/text-batch.js +6 -7
  421. package/dist/editor-actions/commit/undo-controller.d.ts +6 -4
  422. package/dist/editor-actions/commit/undo-controller.js +346 -216
  423. package/dist/editor-actions/container-block-component.d.ts +32 -51
  424. package/dist/editor-actions/container-block-component.js +66 -92
  425. package/dist/editor-actions/container-edit.d.ts +4 -2
  426. package/dist/editor-actions/container-edit.js +14 -44
  427. package/dist/editor-actions/container-exit-overrides.d.ts +6 -4
  428. package/dist/editor-actions/container-exit-overrides.js +19 -21
  429. package/dist/editor-actions/deps.d.ts +25 -32
  430. package/dist/editor-actions/enter-completion.d.ts +12 -15
  431. package/dist/editor-actions/enter-completion.js +49 -51
  432. package/dist/editor-actions/focus/focus-dispatch.d.ts +24 -25
  433. package/dist/editor-actions/focus/focus-dispatch.js +28 -50
  434. package/dist/editor-actions/focus/focus-landing.d.ts +8 -11
  435. package/dist/editor-actions/focus/focus-landing.js +15 -14
  436. package/dist/editor-actions/focus/focus.d.ts +3 -2
  437. package/dist/editor-actions/focus/focus.js +42 -59
  438. package/dist/editor-actions/index.d.ts +4 -3
  439. package/dist/editor-actions/index.js +5 -4
  440. package/dist/editor-actions/inline-range-commit.d.ts +18 -16
  441. package/dist/editor-actions/inline-range-commit.js +45 -54
  442. package/dist/editor-actions/leaf-write.d.ts +11 -0
  443. package/dist/editor-actions/leaf-write.js +102 -0
  444. package/dist/editor-actions/list-context.d.ts +9 -10
  445. package/dist/editor-actions/list-context.js +115 -110
  446. package/dist/editor-actions/list-overrides.d.ts +13 -5
  447. package/dist/editor-actions/list-overrides.js +36 -12
  448. package/dist/editor-actions/merge-fallback.d.ts +10 -16
  449. package/dist/editor-actions/merge-fallback.js +11 -22
  450. package/dist/editor-actions/nested/container-actions.d.ts +37 -0
  451. package/dist/editor-actions/nested/container-actions.js +42 -0
  452. package/dist/editor-actions/nested/emptied-container.d.ts +5 -0
  453. package/dist/editor-actions/nested/emptied-container.js +5 -0
  454. package/dist/editor-actions/nested/nested-actions.d.ts +22 -25
  455. package/dist/editor-actions/nested/nested-actions.js +17 -15
  456. package/dist/editor-actions/nested/nested-block-edit.d.ts +3 -4
  457. package/dist/editor-actions/nested/nested-block-edit.js +40 -134
  458. package/dist/editor-actions/nested/nested-focus.d.ts +2 -3
  459. package/dist/editor-actions/nested/nested-focus.js +33 -21
  460. package/dist/editor-actions/paste-coordinator.d.ts +3 -2
  461. package/dist/editor-actions/paste-coordinator.js +17 -13
  462. package/dist/editor-actions/plugin/chrome-leaf.d.ts +11 -9
  463. package/dist/editor-actions/plugin/chrome-leaf.js +14 -13
  464. package/dist/editor-actions/plugin/container.d.ts +54 -82
  465. package/dist/editor-actions/plugin/container.js +105 -188
  466. package/dist/editor-actions/plugin/directive-container.d.ts +4 -4
  467. package/dist/editor-actions/plugin/directive-container.js +7 -8
  468. package/dist/editor-actions/reorder-action.d.ts +7 -12
  469. package/dist/editor-actions/reorder-action.js +32 -48
  470. package/dist/editor-actions/reorder-drag.d.ts +12 -8
  471. package/dist/editor-actions/reorder-drag.js +45 -41
  472. package/dist/editor-actions/replacement-focus.d.ts +13 -21
  473. package/dist/editor-actions/replacement-focus.js +32 -33
  474. package/dist/editor-actions/search-replace.js +71 -67
  475. package/dist/editor-actions/stored-caret.d.ts +7 -0
  476. package/dist/editor-actions/stored-caret.js +10 -0
  477. package/dist/editor-actions/table-context.d.ts +9 -11
  478. package/dist/editor-actions/table-context.js +70 -93
  479. package/dist/editor-actions/unwrap-strategies.d.ts +4 -5
  480. package/dist/editor-actions/unwrap-strategies.js +52 -63
  481. package/dist/editor-actions/whole-block-focus-surface.d.ts +19 -28
  482. package/dist/editor-actions/whole-block-focus-surface.js +43 -53
  483. package/dist/editor-events.d.ts +25 -29
  484. package/dist/editor-events.js +17 -28
  485. package/dist/editor-keys.d.ts +96 -104
  486. package/dist/editor-keys.js +11 -24
  487. package/dist/editor-props.d.ts +88 -71
  488. package/dist/editor-rects.d.ts +23 -34
  489. package/dist/editor-rects.js +9 -77
  490. package/dist/env.d.ts +6 -4
  491. package/dist/env.js +8 -4
  492. package/dist/index.d.ts +6 -2
  493. package/dist/index.js +5 -2
  494. package/dist/inline-menu/inline-menu-session.d.ts +37 -0
  495. package/dist/inline-menu/inline-menu-session.js +98 -0
  496. package/dist/inline-menu/inline-menu-state.svelte.d.ts +63 -0
  497. package/dist/inline-menu/inline-menu-state.svelte.js +409 -0
  498. package/dist/inline-menu/types.d.ts +77 -0
  499. package/dist/inline-menu/types.js +7 -0
  500. package/dist/invariants/child-id-parity.d.ts +23 -0
  501. package/dist/invariants/child-id-parity.js +39 -0
  502. package/dist/invariants/commit-paths.d.ts +4 -4
  503. package/dist/invariants/commit-paths.js +4 -4
  504. package/dist/invariants/commit-scope.d.ts +4 -4
  505. package/dist/invariants/commit-scope.js +9 -8
  506. package/dist/invariants/context-keys.d.ts +2 -5
  507. package/dist/invariants/context-keys.js +3 -6
  508. package/dist/invariants/descriptor.d.ts +2 -6
  509. package/dist/invariants/descriptor.js +2 -6
  510. package/dist/invariants/inline-transitions.d.ts +9 -16
  511. package/dist/invariants/inline-transitions.js +10 -18
  512. package/dist/invariants/install.d.ts +18 -27
  513. package/dist/invariants/install.js +29 -30
  514. package/dist/invariants/keeps-a-block.d.ts +7 -0
  515. package/dist/invariants/keeps-a-block.js +14 -0
  516. package/dist/invariants/landable-caret.d.ts +5 -7
  517. package/dist/invariants/landable-caret.js +8 -11
  518. package/dist/invariants/landing-focus-scroll.d.ts +7 -0
  519. package/dist/invariants/landing-focus-scroll.js +13 -0
  520. package/dist/invariants/landing-value.d.ts +13 -0
  521. package/dist/invariants/landing-value.js +27 -0
  522. package/dist/invariants/marker-css-parity.d.ts +5 -6
  523. package/dist/invariants/marker-css-parity.js +11 -12
  524. package/dist/invariants/measures-in-own-list.d.ts +6 -0
  525. package/dist/invariants/measures-in-own-list.js +13 -0
  526. package/dist/invariants/node-shape.d.ts +18 -37
  527. package/dist/invariants/node-shape.js +123 -79
  528. package/dist/invariants/open-tail.d.ts +7 -0
  529. package/dist/invariants/open-tail.js +45 -0
  530. package/dist/invariants/placement-ends-widget.d.ts +8 -0
  531. package/dist/invariants/placement-ends-widget.js +13 -0
  532. package/dist/invariants/registry.d.ts +27 -66
  533. package/dist/invariants/registry.js +61 -137
  534. package/dist/invariants/render-fidelity.d.ts +4 -4
  535. package/dist/invariants/render-fidelity.js +5 -5
  536. package/dist/invariants/selection-endpoints.d.ts +6 -7
  537. package/dist/invariants/selection-endpoints.js +18 -16
  538. package/dist/invariants/single-node-sink.d.ts +4 -5
  539. package/dist/invariants/single-node-sink.js +4 -5
  540. package/dist/invariants/snapshot-integrity.d.ts +5 -6
  541. package/dist/invariants/snapshot-integrity.js +1 -1
  542. package/dist/invariants/structural-descriptor.d.ts +6 -15
  543. package/dist/invariants/structural-descriptor.js +6 -15
  544. package/dist/menu-icons.d.ts +47 -0
  545. package/dist/menu-icons.js +113 -0
  546. package/dist/perf/instruments.d.ts +20 -5
  547. package/dist/perf/instruments.js +35 -15
  548. package/dist/perf/use-mount-gauge.svelte.js +4 -4
  549. package/dist/plugin.d.ts +37 -8
  550. package/dist/plugin.js +100 -77
  551. package/dist/plugins/admonitions/AdmonitionBlock.svelte +14 -11
  552. package/dist/plugins/admonitions/admonition-kind.js +12 -10
  553. package/dist/plugins/admonitions/convert-document.d.ts +5 -5
  554. package/dist/plugins/admonitions/convert-document.js +8 -7
  555. package/dist/plugins/admonitions/gh-alert.d.ts +8 -1
  556. package/dist/plugins/admonitions/gh-alert.js +21 -22
  557. package/dist/plugins/admonitions/github-alert-kind.d.ts +5 -7
  558. package/dist/plugins/admonitions/github-alert-kind.js +28 -37
  559. package/dist/plugins/admonitions/index.js +2 -2
  560. package/dist/plugins/admonitions/kinds.d.ts +1 -1
  561. package/dist/plugins/admonitions/register.js +11 -2
  562. package/dist/plugins/details/DetailsBlock.svelte +20 -20
  563. package/dist/plugins/details/details-disclosure.svelte.d.ts +5 -6
  564. package/dist/plugins/details/details-disclosure.svelte.js +6 -7
  565. package/dist/plugins/details/details-kind.d.ts +6 -7
  566. package/dist/plugins/details/details-kind.js +44 -59
  567. package/dist/plugins/emoji/emoji-recognizer.d.ts +4 -7
  568. package/dist/plugins/emoji/emoji-recognizer.js +7 -14
  569. package/dist/plugins/footnotes/FootnoteDefinition.svelte +33 -16
  570. package/dist/plugins/footnotes/FootnoteReference.svelte +39 -19
  571. package/dist/plugins/footnotes/constants.d.ts +2 -0
  572. package/dist/plugins/footnotes/constants.js +3 -0
  573. package/dist/plugins/footnotes/footnote-definition.d.ts +8 -6
  574. package/dist/plugins/footnotes/footnote-definition.js +69 -45
  575. package/dist/plugins/footnotes/footnote-lookup.d.ts +3 -3
  576. package/dist/plugins/footnotes/footnote-lookup.js +14 -26
  577. package/dist/plugins/footnotes/footnote-numbering.d.ts +11 -11
  578. package/dist/plugins/footnotes/footnote-numbering.js +22 -31
  579. package/dist/plugins/footnotes/footnote-reference.d.ts +4 -4
  580. package/dist/plugins/footnotes/footnote-reference.js +8 -13
  581. package/dist/plugins/footnotes/index.js +2 -3
  582. package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.d.ts +5 -8
  583. package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.js +8 -3
  584. package/dist/plugins/highlight-occurrences/occurrence-source.d.ts +10 -12
  585. package/dist/plugins/highlight-occurrences/occurrence-source.js +21 -18
  586. package/dist/plugins/highlight-occurrences/occurrences.d.ts +11 -11
  587. package/dist/plugins/highlight-occurrences/occurrences.js +14 -36
  588. package/dist/plugins/highlight-occurrences/word-char.d.ts +1 -0
  589. package/dist/plugins/highlight-occurrences/word-char.js +4 -0
  590. package/dist/plugins/latex/BlockMath.svelte +58 -75
  591. package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -4
  592. package/dist/plugins/latex/MathInline.svelte +10 -9
  593. package/dist/plugins/latex/flanking.d.ts +1 -0
  594. package/dist/plugins/latex/flanking.js +3 -0
  595. package/dist/plugins/latex/index.d.ts +1 -1
  596. package/dist/plugins/latex/latex-kind.d.ts +4 -4
  597. package/dist/plugins/latex/latex-kind.js +78 -67
  598. package/dist/plugins/latex/math-completion.d.ts +1 -1
  599. package/dist/plugins/latex/math-completion.js +5 -8
  600. package/dist/plugins/latex/math-layout.d.ts +2 -8
  601. package/dist/plugins/latex/math-layout.js +0 -9
  602. package/dist/plugins/latex/math-renderer.d.ts +17 -20
  603. package/dist/plugins/latex/math-renderer.js +21 -38
  604. package/dist/plugins/latex/math-source.d.ts +4 -8
  605. package/dist/plugins/latex/math-source.js +36 -81
  606. package/dist/plugins/latex/register.d.ts +10 -11
  607. package/dist/plugins/latex/register.js +24 -10
  608. package/dist/plugins/latex/renderer.d.ts +9 -10
  609. package/dist/plugins/latex/renderer.js +9 -24
  610. package/dist/plugins/mermaid/MermaidBlock.svelte +51 -45
  611. package/dist/plugins/mermaid/index.js +1 -1
  612. package/dist/plugins/mermaid/mermaid-kind.d.ts +4 -7
  613. package/dist/plugins/mermaid/mermaid-kind.js +30 -47
  614. package/dist/plugins/mermaid/mermaid-renderer.d.ts +9 -16
  615. package/dist/plugins/mermaid/mermaid-renderer.js +12 -31
  616. package/dist/plugins/mermaid/register.d.ts +3 -3
  617. package/dist/plugins/mermaid/register.js +5 -5
  618. package/dist/plugins/mermaid/renderer.d.ts +5 -5
  619. package/dist/plugins/mermaid/renderer.js +8 -11
  620. package/dist/plugins/parrot/ParrotBlock.svelte +15 -6
  621. package/dist/plugins/parrot/ParrotBlock.svelte.d.ts +2 -2
  622. package/dist/plugins/parrot/parrot-plugin.js +3 -3
  623. package/dist/plugins/slash-commands/filter.d.ts +23 -0
  624. package/dist/plugins/slash-commands/filter.js +31 -0
  625. package/dist/plugins/slash-commands/index.d.ts +2 -0
  626. package/dist/plugins/slash-commands/index.js +2 -0
  627. package/dist/plugins/slash-commands/slash-commands-plugin.d.ts +12 -0
  628. package/dist/plugins/slash-commands/slash-commands-plugin.js +40 -0
  629. package/dist/plugins/slash-commands/slash-source.d.ts +36 -0
  630. package/dist/plugins/slash-commands/slash-source.js +108 -0
  631. package/dist/plugins/toc/TocBlock.svelte +23 -22
  632. package/dist/plugins/toc/TocBlock.svelte.d.ts +2 -3
  633. package/dist/plugins/toc/heading-outline.d.ts +7 -12
  634. package/dist/plugins/toc/heading-outline.js +22 -36
  635. package/dist/plugins/toc/navigation-queue.d.ts +2 -4
  636. package/dist/plugins/toc/toc-plugin.d.ts +5 -5
  637. package/dist/plugins/toc/toc-plugin.js +20 -13
  638. package/dist/presentation-mode.d.ts +15 -23
  639. package/dist/presentation-mode.js +19 -25
  640. package/dist/reactivity/block-list-state.svelte.d.ts +9 -11
  641. package/dist/reactivity/block-list-state.svelte.js +8 -9
  642. package/dist/reactivity/block-window.svelte.d.ts +12 -6
  643. package/dist/reactivity/block-window.svelte.js +44 -17
  644. package/dist/reactivity/child-list.d.ts +34 -0
  645. package/dist/reactivity/child-list.js +48 -0
  646. package/dist/reactivity/content-version.svelte.d.ts +4 -7
  647. package/dist/reactivity/content-version.svelte.js +3 -5
  648. package/dist/reactivity/hold-across.d.ts +39 -0
  649. package/dist/reactivity/hold-across.js +57 -0
  650. package/dist/reactivity/layout-state.svelte.d.ts +27 -0
  651. package/dist/reactivity/layout-state.svelte.js +51 -0
  652. package/dist/reactivity/list-tree.d.ts +39 -0
  653. package/dist/reactivity/list-tree.js +132 -0
  654. package/dist/reactivity/list-windowing.svelte.d.ts +50 -71
  655. package/dist/reactivity/list-windowing.svelte.js +141 -310
  656. package/dist/reactivity/measure-batch.d.ts +6 -8
  657. package/dist/reactivity/measure-batch.js +5 -6
  658. package/dist/reactivity/publish-ref.svelte.d.ts +16 -41
  659. package/dist/reactivity/publish-ref.svelte.js +21 -85
  660. package/dist/reactivity/scope-geometry.d.ts +6 -13
  661. package/dist/reactivity/scope-geometry.js +7 -14
  662. package/dist/reactivity/state-registry.d.ts +5 -5
  663. package/dist/reactivity/state-registry.js +11 -15
  664. package/dist/reactivity/use-container-windowing.svelte.d.ts +13 -26
  665. package/dist/reactivity/use-container-windowing.svelte.js +64 -74
  666. package/dist/reactivity/use-measured-child.svelte.d.ts +10 -0
  667. package/dist/reactivity/use-measured-child.svelte.js +41 -0
  668. package/dist/reactivity/use-window-floor.svelte.d.ts +2 -0
  669. package/dist/reactivity/use-window-floor.svelte.js +27 -0
  670. package/dist/reactivity/window-slice.d.ts +4 -4
  671. package/dist/reactivity/window-slice.js +0 -2
  672. package/dist/renderer-slot.d.ts +48 -0
  673. package/dist/renderer-slot.js +85 -0
  674. package/dist/scan-index.d.ts +4 -4
  675. package/dist/scan-index.js +4 -4
  676. package/dist/schema/block-commands.d.ts +69 -54
  677. package/dist/schema/block-commands.js +91 -104
  678. package/dist/schema/block-completions.d.ts +16 -19
  679. package/dist/schema/block-completions.js +25 -32
  680. package/dist/schema/block-component-registry.d.ts +13 -14
  681. package/dist/schema/block-component-registry.js +19 -17
  682. package/dist/schema/block-kind-descriptor.d.ts +234 -168
  683. package/dist/schema/block-kind-descriptor.js +151 -66
  684. package/dist/schema/block-openers.d.ts +48 -39
  685. package/dist/schema/block-openers.js +73 -83
  686. package/dist/schema/built-in-descriptors.d.ts +3 -5
  687. package/dist/schema/built-in-descriptors.js +96 -80
  688. package/dist/schema/child-spans.d.ts +12 -10
  689. package/dist/schema/child-spans.js +68 -34
  690. package/dist/schema/closure.d.ts +18 -16
  691. package/dist/schema/closure.js +24 -14
  692. package/dist/schema/command-id.d.ts +6 -8
  693. package/dist/schema/command-id.js +20 -25
  694. package/dist/schema/commands.d.ts +78 -84
  695. package/dist/schema/commands.js +152 -117
  696. package/dist/schema/container-raw.d.ts +51 -11
  697. package/dist/schema/container-raw.js +143 -14
  698. package/dist/schema/container-rebuilders.d.ts +17 -15
  699. package/dist/schema/container-rebuilders.js +30 -22
  700. package/dist/schema/context-actions.d.ts +25 -13
  701. package/dist/schema/context-actions.js +32 -17
  702. package/dist/schema/define-plugin-block.d.ts +8 -6
  703. package/dist/schema/define-plugin-block.js +6 -4
  704. package/dist/schema/fenced-code-raw.d.ts +30 -17
  705. package/dist/schema/fenced-code-raw.js +155 -107
  706. package/dist/schema/global-commands.d.ts +6 -6
  707. package/dist/schema/global-commands.js +14 -22
  708. package/dist/schema/height-estimates.d.ts +31 -0
  709. package/dist/schema/height-estimates.js +64 -0
  710. package/dist/schema/inline-construct-policy.d.ts +42 -55
  711. package/dist/schema/inline-construct-policy.js +36 -35
  712. package/dist/schema/insert-catalogue.d.ts +30 -0
  713. package/dist/schema/insert-catalogue.js +95 -0
  714. package/dist/schema/keybinding-overrides.d.ts +9 -11
  715. package/dist/schema/keybinding-overrides.js +1 -1
  716. package/dist/schema/keybindings.d.ts +15 -7
  717. package/dist/schema/keybindings.js +21 -9
  718. package/dist/schema/merge-rules.d.ts +5 -7
  719. package/dist/schema/merge-rules.js +7 -9
  720. package/dist/schema/opener-priorities.d.ts +4 -4
  721. package/dist/schema/opener-priorities.js +4 -4
  722. package/dist/schema/operations.d.ts +8 -10
  723. package/dist/schema/operations.js +3 -3
  724. package/dist/schema/page-role.d.ts +14 -0
  725. package/dist/schema/page-role.js +19 -0
  726. package/dist/schema/plugin-activation.d.ts +4 -6
  727. package/dist/schema/plugin-activation.js +12 -10
  728. package/dist/schema/plugin-editor-context.d.ts +25 -10
  729. package/dist/schema/plugin-editor-context.js +40 -9
  730. package/dist/schema/plugin-install.d.ts +49 -17
  731. package/dist/schema/plugin-install.js +51 -25
  732. package/dist/schema/plugin-kind.d.ts +12 -8
  733. package/dist/schema/plugin-kind.js +35 -42
  734. package/dist/schema/plugin-name.js +3 -3
  735. package/dist/schema/plugin-registry.d.ts +54 -0
  736. package/dist/schema/plugin-registry.js +70 -0
  737. package/dist/schema/reading.d.ts +23 -0
  738. package/dist/schema/reading.js +7 -0
  739. package/dist/schema/register-once.d.ts +3 -5
  740. package/dist/schema/register-once.js +7 -16
  741. package/dist/schema/registration-checks.d.ts +12 -16
  742. package/dist/schema/registration-checks.js +35 -51
  743. package/dist/schema/registration-pairs.d.ts +54 -0
  744. package/dist/schema/registration-pairs.js +48 -0
  745. package/dist/schema/registration-pending.d.ts +8 -12
  746. package/dist/schema/registration-pending.js +9 -9
  747. package/dist/schema/registry-reset.d.ts +8 -4
  748. package/dist/schema/registry-reset.js +13 -26
  749. package/dist/schema/registry-view.d.ts +25 -13
  750. package/dist/schema/registry-view.js +26 -20
  751. package/dist/schema/reserved-chords.d.ts +9 -10
  752. package/dist/schema/reserved-chords.js +63 -80
  753. package/dist/schema/reserved-chrome.d.ts +12 -11
  754. package/dist/schema/reserved-chrome.js +15 -11
  755. package/dist/schema/setext-raw.d.ts +13 -0
  756. package/dist/schema/setext-raw.js +33 -0
  757. package/dist/schema/stored-as.d.ts +20 -0
  758. package/dist/schema/stored-as.js +6 -0
  759. package/dist/schema/table-cell-raw.d.ts +18 -7
  760. package/dist/schema/table-cell-raw.js +28 -7
  761. package/dist/schema/whole-block-unit.d.ts +5 -4
  762. package/dist/schema/whole-block-unit.js +5 -4
  763. package/dist/search/document-scan.d.ts +3 -3
  764. package/dist/search/document-scan.js +2 -2
  765. package/dist/search/matcher.d.ts +1 -1
  766. package/dist/search/regex-executor.d.ts +5 -6
  767. package/dist/search/regex-executor.js +6 -8
  768. package/dist/search/replace.d.ts +2 -5
  769. package/dist/search/replace.js +2 -5
  770. package/dist/search/search-state.svelte.d.ts +5 -5
  771. package/dist/search/search-state.svelte.js +12 -16
  772. package/dist/selection/autoscroll.d.ts +6 -9
  773. package/dist/selection/block-hit-test.d.ts +16 -24
  774. package/dist/selection/block-hit-test.js +29 -22
  775. package/dist/selection/caret-doors.d.ts +15 -14
  776. package/dist/selection/caret-doors.js +37 -26
  777. package/dist/selection/caret-landing.d.ts +62 -0
  778. package/dist/selection/caret-landing.js +157 -0
  779. package/dist/selection/caret-restore.d.ts +17 -10
  780. package/dist/selection/caret-restore.js +10 -22
  781. package/dist/selection/caret-target.d.ts +30 -0
  782. package/dist/selection/caret-target.js +84 -0
  783. package/dist/selection/char-endpoint-snap.d.ts +6 -10
  784. package/dist/selection/char-endpoint-snap.js +8 -14
  785. package/dist/selection/clipboard-text.d.ts +5 -12
  786. package/dist/selection/clipboard-text.js +69 -172
  787. package/dist/selection/covered-block.d.ts +6 -8
  788. package/dist/selection/covered-block.js +8 -19
  789. package/dist/selection/cross-block/clipboard.d.ts +3 -2
  790. package/dist/selection/cross-block/clipboard.js +13 -4
  791. package/dist/selection/cross-block/dispatch.d.ts +47 -43
  792. package/dist/selection/cross-block/dispatch.js +12 -23
  793. package/dist/selection/cross-block/format-range.d.ts +18 -21
  794. package/dist/selection/cross-block/format-range.js +104 -103
  795. package/dist/selection/cross-block/format-toggle.d.ts +10 -15
  796. package/dist/selection/cross-block/format-toggle.js +28 -33
  797. package/dist/selection/cross-block/keydown.d.ts +4 -4
  798. package/dist/selection/cross-block/keydown.js +92 -125
  799. package/dist/selection/cross-block/ops.d.ts +19 -34
  800. package/dist/selection/cross-block/ops.js +110 -95
  801. package/dist/selection/cross-block/paste.d.ts +2 -3
  802. package/dist/selection/cross-block/paste.js +59 -96
  803. package/dist/selection/cross-block/pointer.d.ts +17 -15
  804. package/dist/selection/cross-block/pointer.js +74 -25
  805. package/dist/selection/cross-block/type-replace.d.ts +4 -6
  806. package/dist/selection/cross-block/type-replace.js +51 -134
  807. package/dist/selection/dead-space-caret.d.ts +19 -30
  808. package/dist/selection/dead-space-caret.js +70 -88
  809. package/dist/selection/drag-pointer.d.ts +7 -9
  810. package/dist/selection/drag-pointer.js +23 -33
  811. package/dist/selection/gap-caret.d.ts +17 -28
  812. package/dist/selection/gap-caret.js +14 -26
  813. package/dist/selection/grid-selection.d.ts +12 -0
  814. package/dist/selection/grid-selection.js +22 -0
  815. package/dist/selection/keyboard-extend.d.ts +27 -40
  816. package/dist/selection/keyboard-extend.js +94 -117
  817. package/dist/selection/multi-click.d.ts +13 -18
  818. package/dist/selection/multi-click.js +32 -38
  819. package/dist/selection/native-bridge.d.ts +29 -39
  820. package/dist/selection/native-bridge.js +78 -127
  821. package/dist/selection/nearest-block.d.ts +17 -12
  822. package/dist/selection/nearest-block.js +37 -18
  823. package/dist/selection/path-lookup.d.ts +25 -16
  824. package/dist/selection/path-lookup.js +101 -18
  825. package/dist/selection/path-math.d.ts +9 -11
  826. package/dist/selection/path-math.js +5 -7
  827. package/dist/selection/pointer-gesture.d.ts +5 -5
  828. package/dist/selection/pointer-gesture.js +5 -5
  829. package/dist/selection/pointer-session.d.ts +10 -16
  830. package/dist/selection/pointer-session.js +6 -7
  831. package/dist/selection/primitives.d.ts +64 -40
  832. package/dist/selection/primitives.js +57 -67
  833. package/dist/selection/range-coverage.d.ts +94 -0
  834. package/dist/selection/range-coverage.js +365 -0
  835. package/dist/selection/range-delete-ceremony.d.ts +28 -74
  836. package/dist/selection/range-delete-ceremony.js +100 -141
  837. package/dist/selection/range-delete-chrome.d.ts +20 -33
  838. package/dist/selection/range-delete-chrome.js +53 -78
  839. package/dist/selection/range-delete-table-coverage.d.ts +11 -25
  840. package/dist/selection/range-delete-table-coverage.js +42 -112
  841. package/dist/selection/range-delete-table.d.ts +14 -12
  842. package/dist/selection/range-delete-table.js +106 -310
  843. package/dist/selection/range-delete.d.ts +18 -23
  844. package/dist/selection/range-delete.js +83 -123
  845. package/dist/selection/round-trip-restore.d.ts +18 -0
  846. package/dist/selection/round-trip-restore.js +19 -0
  847. package/dist/selection/selection-announcer.d.ts +17 -0
  848. package/dist/selection/selection-announcer.js +25 -0
  849. package/dist/selection/selection-description.d.ts +4 -3
  850. package/dist/selection/selection-description.js +5 -4
  851. package/dist/selection/selection-drop.d.ts +34 -16
  852. package/dist/selection/selection-drop.js +159 -95
  853. package/dist/selection/selection-restore.d.ts +20 -33
  854. package/dist/selection/selection-restore.js +21 -44
  855. package/dist/selection/selection-state.svelte.d.ts +50 -42
  856. package/dist/selection/selection-state.svelte.js +144 -98
  857. package/dist/selection/shared-keydown.d.ts +22 -27
  858. package/dist/selection/shared-keydown.js +25 -45
  859. package/dist/selection/table-endpoint-snap.d.ts +17 -41
  860. package/dist/selection/table-endpoint-snap.js +54 -80
  861. package/dist/selection/table-rect-extend.d.ts +3 -6
  862. package/dist/selection/table-rect-extend.js +19 -14
  863. package/dist/styles/editor-theme.css +60 -53
  864. package/dist/styles/editor.css +201 -143
  865. package/dist/testing/conformance-core.d.ts +50 -18
  866. package/dist/testing/conformance-core.js +88 -50
  867. package/dist/testing/container-conformance.d.ts +48 -84
  868. package/dist/testing/container-conformance.js +159 -285
  869. package/dist/testing/headless-actions.d.ts +34 -19
  870. package/dist/testing/headless-actions.js +116 -65
  871. package/dist/testing/headless-block-list.svelte.d.ts +16 -0
  872. package/dist/testing/headless-block-list.svelte.js +22 -0
  873. package/dist/testing/inline-conformance.d.ts +15 -22
  874. package/dist/testing/inline-conformance.js +131 -148
  875. package/dist/testing/kind-conformance.d.ts +19 -24
  876. package/dist/testing/kind-conformance.js +204 -117
  877. package/dist/testing/kit-reading.d.ts +7 -0
  878. package/dist/testing/kit-reading.js +16 -0
  879. package/dist/testing/mount-dom-stubs.d.ts +4 -5
  880. package/dist/testing/mount-dom-stubs.js +8 -5
  881. package/dist/testing/parse-convergence.d.ts +10 -10
  882. package/dist/testing/parse-convergence.js +14 -49
  883. package/dist/testing.d.ts +13 -9
  884. package/dist/testing.js +28 -38
  885. package/dist/tree-operations/blockquote.d.ts +8 -14
  886. package/dist/tree-operations/blockquote.js +25 -45
  887. package/dist/tree-operations/chain-rebuild.d.ts +34 -25
  888. package/dist/tree-operations/chain-rebuild.js +115 -52
  889. package/dist/tree-operations/children.d.ts +5 -9
  890. package/dist/tree-operations/children.js +7 -11
  891. package/dist/tree-operations/cleanup.d.ts +6 -7
  892. package/dist/tree-operations/cleanup.js +14 -11
  893. package/dist/tree-operations/clone.js +5 -4
  894. package/dist/tree-operations/container-lift.d.ts +10 -5
  895. package/dist/tree-operations/container-lift.js +20 -14
  896. package/dist/tree-operations/container-offsets.d.ts +22 -0
  897. package/dist/tree-operations/container-offsets.js +89 -0
  898. package/dist/tree-operations/content-write.d.ts +52 -29
  899. package/dist/tree-operations/content-write.js +155 -110
  900. package/dist/tree-operations/index.d.ts +4 -4
  901. package/dist/tree-operations/index.js +4 -4
  902. package/dist/tree-operations/keep-one-block.d.ts +10 -0
  903. package/dist/tree-operations/keep-one-block.js +19 -0
  904. package/dist/tree-operations/leaf-range.d.ts +33 -0
  905. package/dist/tree-operations/leaf-range.js +67 -0
  906. package/dist/tree-operations/list/empty-check.d.ts +2 -3
  907. package/dist/tree-operations/list/empty-check.js +4 -4
  908. package/dist/tree-operations/list/exit-replacement.d.ts +4 -6
  909. package/dist/tree-operations/list/exit-replacement.js +4 -9
  910. package/dist/tree-operations/list/item-partition.d.ts +2 -2
  911. package/dist/tree-operations/list/item-partition.js +2 -2
  912. package/dist/tree-operations/list/list-builders.d.ts +18 -16
  913. package/dist/tree-operations/list/list-builders.js +59 -30
  914. package/dist/tree-operations/list/ordered-markers.d.ts +7 -16
  915. package/dist/tree-operations/list/ordered-markers.js +7 -16
  916. package/dist/tree-operations/list/reconcile-task.d.ts +16 -7
  917. package/dist/tree-operations/list/reconcile-task.js +80 -23
  918. package/dist/tree-operations/list/sublist-separator.d.ts +7 -7
  919. package/dist/tree-operations/list/sublist-separator.js +20 -27
  920. package/dist/tree-operations/list/task-paragraph.d.ts +25 -0
  921. package/dist/tree-operations/list/task-paragraph.js +54 -0
  922. package/dist/tree-operations/list/unwrap-merge.d.ts +8 -16
  923. package/dist/tree-operations/list/unwrap-merge.js +44 -79
  924. package/dist/tree-operations/node-ops.d.ts +30 -52
  925. package/dist/tree-operations/node-ops.js +152 -218
  926. package/dist/tree-operations/node-primitives.d.ts +44 -31
  927. package/dist/tree-operations/node-primitives.js +84 -60
  928. package/dist/tree-operations/open-tail.d.ts +25 -0
  929. package/dist/tree-operations/open-tail.js +100 -0
  930. package/dist/tree-operations/parse-block.d.ts +15 -2
  931. package/dist/tree-operations/parse-block.js +19 -4
  932. package/dist/tree-operations/paste/apply.d.ts +7 -8
  933. package/dist/tree-operations/paste/apply.js +33 -63
  934. package/dist/tree-operations/paste/body-write.d.ts +8 -6
  935. package/dist/tree-operations/paste/body-write.js +13 -14
  936. package/dist/tree-operations/paste/container-match.d.ts +7 -10
  937. package/dist/tree-operations/paste/container-match.js +76 -58
  938. package/dist/tree-operations/paste/container-paste.d.ts +3 -3
  939. package/dist/tree-operations/paste/container-paste.js +3 -3
  940. package/dist/tree-operations/paste/dispatch.d.ts +26 -29
  941. package/dist/tree-operations/paste/dispatch.js +68 -53
  942. package/dist/tree-operations/paste/focus-target.d.ts +11 -14
  943. package/dist/tree-operations/paste/focus-target.js +13 -18
  944. package/dist/tree-operations/paste/hooks.d.ts +7 -12
  945. package/dist/tree-operations/paste/hooks.js +49 -46
  946. package/dist/tree-operations/paste/line-ending.d.ts +11 -0
  947. package/dist/tree-operations/paste/line-ending.js +32 -0
  948. package/dist/tree-operations/paste/list-absorb.d.ts +8 -11
  949. package/dist/tree-operations/paste/list-absorb.js +17 -19
  950. package/dist/tree-operations/paste/list-break-out.d.ts +12 -11
  951. package/dist/tree-operations/paste/list-break-out.js +24 -32
  952. package/dist/tree-operations/paste/parent-scope.d.ts +8 -12
  953. package/dist/tree-operations/paste/parent-scope.js +9 -16
  954. package/dist/tree-operations/paste/paste-deps.d.ts +14 -9
  955. package/dist/tree-operations/paste/paste-replacement.d.ts +20 -3
  956. package/dist/tree-operations/paste/paste-replacement.js +73 -42
  957. package/dist/tree-operations/paste/paste-transforms.d.ts +5 -7
  958. package/dist/tree-operations/paste/paste-transforms.js +18 -27
  959. package/dist/tree-operations/paste/replacement-parse.d.ts +9 -7
  960. package/dist/tree-operations/paste/replacement-parse.js +12 -10
  961. package/dist/tree-operations/paste/strategy.d.ts +1 -1
  962. package/dist/tree-operations/paste/strategy.js +1 -1
  963. package/dist/tree-operations/paste/table-slice.js +15 -10
  964. package/dist/tree-operations/paste-surfaces.d.ts +18 -29
  965. package/dist/tree-operations/paste-surfaces.js +14 -10
  966. package/dist/tree-operations/path-mutate.d.ts +7 -8
  967. package/dist/tree-operations/path-mutate.js +4 -4
  968. package/dist/tree-operations/reorder-unit.d.ts +4 -5
  969. package/dist/tree-operations/reorder-unit.js +5 -6
  970. package/dist/tree-operations/reorder.d.ts +7 -9
  971. package/dist/tree-operations/reorder.js +39 -40
  972. package/dist/tree-operations/settle.d.ts +42 -53
  973. package/dist/tree-operations/settle.js +241 -162
  974. package/dist/tree-operations/sharing.d.ts +5 -5
  975. package/dist/tree-operations/splice-many.d.ts +5 -5
  976. package/dist/tree-operations/splice-many.js +5 -5
  977. package/dist/tree-operations/stored-as.d.ts +13 -0
  978. package/dist/tree-operations/stored-as.js +48 -0
  979. package/dist/tree-operations/structural-change.d.ts +11 -15
  980. package/dist/tree-operations/structural-change.js +10 -14
  981. package/dist/tree-operations/structural-suffix.d.ts +22 -0
  982. package/dist/tree-operations/structural-suffix.js +61 -0
  983. package/dist/tree-operations/sub-table-copy.d.ts +7 -0
  984. package/dist/tree-operations/sub-table-copy.js +40 -36
  985. package/dist/tree-operations/table-grid-clipboard.d.ts +2 -7
  986. package/dist/tree-operations/table-grid-clipboard.js +11 -36
  987. package/dist/tree-operations/table-mutations.d.ts +7 -3
  988. package/dist/tree-operations/table-mutations.js +45 -9
  989. package/dist/tree-operations/unshare.d.ts +11 -15
  990. package/dist/tree-operations/unshare.js +15 -17
  991. package/dist/undo/manager.js +1 -1
  992. package/dist/undo/types.d.ts +2 -2
  993. package/docs/guide/consumer-guide.md +327 -205
  994. package/docs/guide/directives.md +7 -7
  995. package/docs/guide/plugin-api.md +253 -185
  996. package/docs/guide/plugin-guide.md +332 -235
  997. package/docs/guide/plugin-testing.md +96 -82
  998. package/package.json +12 -16
  999. package/dist/ambient/ambient-cursor.d.ts +0 -41
  1000. package/dist/ambient/ambient-cursor.js +0 -120
  1001. package/dist/components/blocks/code/code-paste.d.ts +0 -24
  1002. package/dist/components/blocks/code/code-paste.js +0 -21
  1003. package/dist/components/blocks/table/cell-clipboard.d.ts +0 -33
  1004. package/dist/components/blocks/table/cell-clipboard.js +0 -67
  1005. package/dist/components/blocks/text/hidden-suffix.d.ts +0 -10
  1006. package/dist/components/blocks/text/hidden-suffix.js +0 -15
  1007. package/dist/components/blocks/text/link-source-bytes.d.ts +0 -37
  1008. package/dist/components/image/image-source-bytes.d.ts +0 -15
  1009. package/dist/components/image/image-source-bytes.js +0 -80
  1010. package/dist/cursor/content-offsets.d.ts +0 -29
  1011. package/dist/cursor/content-offsets.js +0 -151
  1012. package/dist/cursor/reveal-anchor.d.ts +0 -30
  1013. package/dist/cursor/reveal-anchor.js +0 -31
  1014. package/dist/cursor/scroll-hold.d.ts +0 -10
  1015. package/dist/cursor/scroll-hold.js +0 -22
  1016. package/dist/invariants/split-landing.d.ts +0 -8
  1017. package/dist/invariants/split-landing.js +0 -15
  1018. package/dist/tree-operations/list/terminator.d.ts +0 -17
  1019. package/dist/tree-operations/list/terminator.js +0 -60
  1020. package/dist/tree-operations/paste/replace-block-at-parent.d.ts +0 -31
  1021. package/dist/tree-operations/paste/replace-block-at-parent.js +0 -78
@@ -3,7 +3,12 @@
3
3
  import '../styles/editor.css';
4
4
  import type { BlockComponent } from '../block-component';
5
5
  import type { AnyBlockKind, Document } from '../core/nodes';
6
- import type { EditorProps, EditorInstance, EditorDiagnostics } from '../editor-props';
6
+ import type {
7
+ EditorProps,
8
+ EditorInstance,
9
+ EditorDiagnostics,
10
+ InsertMarkdownOptions
11
+ } from '../editor-props';
7
12
  import type { EditorEvents } from '../editor-events';
8
13
  import {
9
14
  BLOCK_EDIT_KEY,
@@ -16,60 +21,39 @@
16
21
  type BlockElLookup,
17
22
  type DocumentGetter,
18
23
  type EditorDoc,
19
- type LinkReferenceResolverRef,
20
24
  type EditorPolicies,
21
25
  type EditorServices,
22
26
  type PluginEditorLookup,
23
27
  type ResolveImageUrl,
24
28
  type ResolveLinkUrl
25
29
  } from '../editor-keys';
26
- import { createStickyColumnState } from '../cursor/sticky-column';
27
- import { createEdgeAffinityState } from '../cursor/edge-affinity';
28
- import { createPendingMarksState } from '../cursor/pending-marks';
29
- import { createRevealAnchorState } from '../cursor/reveal-anchor';
30
- import { createHeightOracle } from '../cursor/height-oracle';
31
- import { HEIGHT_ESTIMATES } from '../cursor/typography-estimates';
32
- import {
33
- clippingAncestors,
34
- userScrollportFor,
35
- type UserScrollport
36
- } from '../cursor/scroll-ancestors';
37
- import { createScrollport, type Scrollport } from '../cursor/scrollport';
38
- import { createDeadSpaceCaret } from '../selection/dead-space-caret';
39
- import { resetForPointerDown } from '../selection/cross-block/pointer';
40
- import { installDragListener } from '../selection/drag-pointer';
41
- import { installMultiClickSelect } from '../selection/multi-click';
42
- import { claimsPointerGesture } from '../selection/pointer-gesture';
43
- import { installSelectionDrop } from '../selection/selection-drop';
30
+ import { createCaretMemory } from '../cursor/caret-memory';
31
+ import { docPathFrom } from '../cursor/coordinate-spaces';
32
+ import { createAutoPairRecord } from './blocks/text/auto-pair-record';
33
+ import { createScrollOwner } from '../cursor/scroll-owner';
34
+ import { createScrollHostResolution } from './editor-root-scroll-host';
35
+ import { installSelectionDrop, type DropCaretRect } from '../selection/selection-drop';
44
36
  import { createContentVersion } from '../reactivity/content-version.svelte';
45
- import { useContainerWindowing } from '../reactivity/use-container-windowing.svelte';
46
- import { refSlotsOver, replaceRefs, revealChildOrWait } from '../reactivity/publish-ref.svelte';
37
+ import { useRootWindowing } from '../reactivity/use-container-windowing.svelte';
38
+ import { createListTree } from '../reactivity/list-tree';
39
+ import { createLayoutState } from '../reactivity/layout-state.svelte';
40
+ import { refSlotsOver, replaceRefs } from '../reactivity/publish-ref.svelte';
41
+ import { componentAt, type ChildList } from '../reactivity/child-list';
47
42
  import { createSelectionState } from '../selection/selection-state.svelte';
43
+ import { coverRange, rangeCoverage } from '../selection/range-coverage';
48
44
  import { createSelectionDescription } from '../selection/selection-description';
49
- import {
50
- BLOCK_ACTIONS_LABEL,
51
- BLOCK_MENU_LABEL,
52
- EDITOR_LABEL,
53
- movedBlockToPosition
54
- } from '../a11y-strings';
45
+ import { EDITOR_LABEL } from '../a11y-strings';
55
46
  import TailInsert from './TailInsert.svelte';
56
- import BlockMenu, {
57
- insertFlyoutEntries,
58
- insertMenuEntries,
59
- insertSnippets,
60
- type MenuEntry
61
- } from './menu/BlockMenu.svelte';
62
- import { runClipboardAction, type ClipboardAction } from './menu/clipboard-actions';
63
- import { blockContextActionsFor, type BlockContextAction } from '../schema/context-actions';
64
- import { isProseBackground, registerDefaultContextActions } from './menu/default-context-actions';
47
+ import BlockMenu from './menu/BlockMenu.svelte';
48
+ import { createMenuPresence } from './menu/menu-presence.svelte';
65
49
  import type { EditorSelection } from '../selection/primitives';
66
50
  import { createWidgetSelectionState } from './image/widget-selection-state.svelte';
67
- import { bootstrapCodeLanguages } from './blocks/code/code-bootstrap';
51
+ import { imageAtTarget } from './image/image-edit-commit';
52
+ import type { SelectedWidgetHandle } from '../selection/primitives';
68
53
  import { assignIds } from '../block-id';
69
- import { ensureEditableContainers, emptyParagraph } from '../tree-operations';
70
- import { blockNodeAt, isBlockNode, nodeAt } from '../tree-operations/node-primitives';
54
+ import { createDocumentSwap, initDocument } from './editor-root-document-swap';
55
+ import { blockNodeAt } from '../tree-operations/node-primitives';
71
56
  import { serialize } from '../core/serializer';
72
- import { parse } from '../core/parser';
73
57
  import { defaultLinkActivation } from '../core/url-policy';
74
58
  import { advanceSignatureEpoch, lrdMapCouldChange } from './lrd-map-gate';
75
59
  import {
@@ -86,21 +70,14 @@
86
70
  import { createDecorationEngine } from '../decorations/decoration-state.svelte';
87
71
  import type { DecorationRegistry } from '../decorations/types';
88
72
  import { createEditorRects, type EditorRects } from '../editor-rects';
73
+ import { createCaretLanding } from '../selection/caret-landing';
89
74
  import { installReorderDrag } from '../editor-actions/reorder-drag';
90
75
  import { createPasteCoordinator } from '../editor-actions/paste-coordinator';
91
76
  import { createOperationsLog } from '../debug/operations-log';
92
- import { dumpInteractionTrace, dumpOperationsLog } from '../debug/inspect';
93
- import { buildDiagnosticsReport } from '../debug/diagnostics-report';
94
- import {
95
- enableInteractionTrace,
96
- disableInteractionTrace,
97
- isInteractionTraceEnabled,
98
- interactionTraceSnapshot
99
- } from '../debug/interaction-trace';
77
+ import { createEditorDiagnostics } from '../debug/editor-diagnostics';
100
78
  import { readCurrentSelection } from '../selection/native-bridge';
101
- import { restoreSelection, type SelectionRestoreOutcome } from '../selection/selection-restore';
102
- import { findSurfacePathForElement } from '../selection/path-lookup';
103
- import { createCaretRestore } from '../selection/caret-restore';
79
+ import { createCaretRestore, type CaretRestoreDeps } from '../selection/caret-restore';
80
+ import { createRoundTripRestore } from '../selection/round-trip-restore';
104
81
  import { createCrossBlockHandlers } from '../selection/cross-block/dispatch';
105
82
  import { createCrossBlockCommands } from '../selection/cross-block/format-toggle';
106
83
  import { normalizeKeybindingOverrides } from '../schema/keybinding-overrides';
@@ -108,16 +85,24 @@
108
85
  import { createEditorRootClipboard } from './editor-root-clipboard';
109
86
  import { createModeFlip } from './editor-root-mode-flip';
110
87
  import { createFocusAttribution } from './editor-root-focus';
88
+ import { createRootGestures } from './editor-root-gestures';
89
+ import { createRootMenus, type BlockMenuModel } from './editor-root-menus';
90
+ import { createFocusedSurface } from './editor-root-focused-surface';
91
+ import type { EditorTestSurface } from './editor-root-test-surface';
111
92
  import {
112
93
  installHeaderSlotCompensation,
113
94
  installTypeScaleProbe,
114
95
  installViewportHeightWatcher,
115
96
  installWidthWatcher
116
97
  } from './editor-root-geometry';
98
+ import { createSelectionAnnouncer } from '../selection/selection-announcer';
99
+ import { createKindCue } from './kind-cue.svelte';
117
100
  import {
118
101
  installEditorBlurAnnouncer,
119
102
  installModActiveTracker,
103
+ installRevealAnchorRelease,
120
104
  installSelectionChangeBridge,
105
+ installUndoStepEnd,
121
106
  onRoot,
122
107
  removeAll
123
108
  } from './editor-root-listeners';
@@ -134,38 +119,38 @@
134
119
  isCommandActiveById,
135
120
  runCommandById,
136
121
  type CommandDispatchContext,
137
- type CommandErrorSink,
138
- type KindCommandTarget
122
+ type CommandErrorSink
139
123
  } from '../schema/block-commands';
140
124
  import type { AnyCommandId } from '../schema/command-id';
141
125
  import { installPlugins, normalizePluginEntries } from '../schema/plugin-install';
142
- import {
143
- activationFor,
144
- everyInstalledPlugin,
145
- kindEnablementFor
146
- } from '../schema/plugin-activation';
126
+ import { activationFor, everyInstalledPlugin } from '../schema/plugin-activation';
147
127
  import { createEditorPluginContexts, mintEditorId } from '../schema/plugin-editor-context';
148
- import { bothEnable, createRegistryView, type KindEnablement } from '../schema/registry-view';
128
+ import { insertCatalogue, type InsertEntry } from '../schema/insert-catalogue';
129
+ import { createRegistryView, type KindEnablement } from '../schema/registry-view';
130
+ import type { Reading } from '../schema/reading';
131
+ import { hidesDelimitersAtCaret, type PresentationMode } from '../presentation-mode';
149
132
  import BlockList from './BlockList.svelte';
150
133
  import SearchBar from './SearchBar.svelte';
151
134
  import SelectionToolbar from './menu/SelectionToolbar.svelte';
152
135
  import ImageOverlayHost from './image/ImageOverlayHost.svelte';
153
136
  import LinkCardHost from './link-card/LinkCardHost.svelte';
137
+ import InlineMenuHost from './menu/InlineMenuHost.svelte';
138
+ import { createInlineMenuState } from '../inline-menu/inline-menu-state.svelte';
139
+ import type { InlineMenuRegistry } from '../inline-menu/types';
140
+ import { createInlineRangeCommit } from '../editor-actions/inline-range-commit';
141
+ import { replaceBlockRaw } from '../editor-actions/block-edit-core';
154
142
  import { createLinkCardState } from './link-card/link-card-state.svelte';
155
- import { LINK_ELEMENT_SELECTOR, resolveLinkAtPoint } from './blocks/text/link-at-point';
156
143
  import { runStartupInvariantChecks } from '../invariants/install';
157
144
  import { assertInvariant } from '../assert';
158
145
  import { checkMarkerCssParity } from '../invariants/marker-css-parity';
159
- import { registerBuiltInBlocks } from './built-in-blocks';
160
- import { BLOCK_CONTENT_SELECTOR } from './block-content-selector';
146
+ import { registerEditorBuiltIns } from './editor-built-ins';
147
+ import { blockContentElAt } from './block-el-lookup';
161
148
 
162
- registerBuiltInBlocks();
163
- bootstrapCodeLanguages();
164
- registerDefaultContextActions();
149
+ registerEditorBuiltIns();
165
150
  runStartupInvariantChecks();
166
151
 
167
- // `__registryEnablement` is a harness-only door; the intersection keeps it off the
168
- // public EditorProps type.
152
+ // `__registryEnablement` is for tests only; the intersection type keeps it off the
153
+ // public EditorProps.
169
154
  let {
170
155
  source = '',
171
156
  resolveImageUrl,
@@ -185,72 +170,40 @@
185
170
  presentationMode = 'source',
186
171
  scrollMode = 'self',
187
172
  plugins,
173
+ syntax,
188
174
  __registryEnablement
189
175
  }: EditorProps & { __registryEnablement?: KindEnablement } = $props();
190
176
 
191
- // Snapshotted, not read live: set-once by contract, and a live read inside the
192
- // windowing scopes' derived would make `scrollMode` a dependency of the hottest path.
177
+ // Read once: `scrollMode` is fixed at mount, and a live read would join windowing's hottest path.
193
178
  // svelte-ignore state_referenced_locally
194
179
  const hostScroll = scrollMode === 'host';
195
180
 
196
- // Host mode asks two different questions one walk cannot serve (`cursor/
197
- // scroll-ancestors` header): what a drag autoscrolls, and what bounds the visible
198
- // region. Memoized on first read, so a host that swaps its scroller must remount.
199
- let resolvedScrollHost: UserScrollport | null = null;
200
- let resolvedClipBounds: HTMLElement[] = [];
201
- let hostResolved = false;
202
- function resolveHost(): void {
203
- if (hostResolved || !editorEl) return;
204
- resolvedScrollHost = userScrollportFor(editorEl);
205
- resolvedClipBounds = clippingAncestors(editorEl);
206
- hostResolved = true;
207
- }
208
- /** What a drag autoscrolls: the root in self mode, the nearest scrollable ancestor
209
- * in host mode. Null only before the root mounts. */
210
- function getScrollHost(): UserScrollport | null {
211
- if (!hostScroll) return editorEl ?? null;
212
- resolveHost();
213
- return resolvedScrollHost;
214
- }
215
- /** Every clipping ancestor; their intersection with the viewport is what a reveal
216
- * must land inside. */
217
- function getClipBounds(): HTMLElement[] {
218
- if (!hostScroll) return [];
219
- resolveHost();
220
- return resolvedClipBounds;
221
- }
222
-
223
- // The scrollport every windowing scope measures and writes, over the SAME scroller the
224
- // autoscroll seam resolves — so the mode picks the target and nothing downstream branches
225
- // on it. Memoized with that resolution.
226
- let scrollport: Scrollport | null = null;
227
- function getScrollport(): Scrollport | null {
228
- if (!scrollport) {
229
- const target = getScrollHost();
230
- if (target) scrollport = createScrollport(target);
231
- }
232
- return scrollport;
233
- }
181
+ const { getScrollHost, getClipBounds } = createScrollHostResolution({
182
+ get editorEl() {
183
+ return editorEl;
184
+ },
185
+ hostScroll
186
+ });
234
187
 
235
- // Install before initDocument parses `source`, so plugin openers/directives are live
236
- // for the seed grammar. Set-once by contract — a later prop change is ignored.
188
+ // Installed before the first parse of `source`; a later change to `plugins` is ignored.
237
189
  // svelte-ignore state_referenced_locally
238
190
  const pluginEntries = plugins?.length ? normalizePluginEntries(plugins) : undefined;
239
191
  if (pluginEntries) installPlugins(pluginEntries.plugins);
240
192
 
241
- // The prop IS the enablement set: this editor activates exactly what it listed. No prop
242
- // means no restriction, so everything installed in the process stays active here.
193
+ // The editor activates exactly the plugins it lists; with no `plugins` prop, every installed
194
+ // plugin stays active.
243
195
  const activePlugins = pluginEntries
244
196
  ? activationFor(pluginEntries.plugins.map((p) => p.name))
245
197
  : everyInstalledPlugin;
246
198
 
247
199
  const overridesMap = $derived(normalizeKeybindingOverrides(keybindings));
248
200
 
249
- // The one mode every door reports (root attribute, context getter, plugin contexts,
250
- // events), and the seam an effective-vs-requested divergence would land in.
201
+ // The one mode every reader reports, and the one place effective and requested could differ.
251
202
  const effectiveMode = $derived(presentationMode);
252
- // Replace is an edit, so it never engages in reading mode. One predicate feeds the
253
- // write sites, the render gate, and the replace closures.
203
+ // Set only while a mode switch commits the outgoing mode's pending edits, so they land in the
204
+ // mode they were typed in.
205
+ let outgoingMode = $state<PresentationMode | null>(null);
206
+ // Replace is an edit, so reading mode never offers it (the commit refuses the write anyway).
254
207
  const canReplace = $derived(effectiveMode !== 'reading');
255
208
 
256
209
  const resolveImageUrlImpl: ResolveImageUrl = (u) => (resolveImageUrl ? resolveImageUrl(u) : u);
@@ -262,95 +215,84 @@
262
215
 
263
216
  // ── State ───────────────────────────────────────────────────────────
264
217
 
265
- function initDocument(src: string): {
266
- doc: Document;
267
- resolver: LinkReferenceResolver;
268
- signature: string;
269
- } {
270
- const d = parse(src, { scope: 'document' });
271
- if (d.children.length === 0) {
272
- // Only the empty source parses to zero blocks — a blank line is a block of its
273
- // own — so there is no authored ending to inherit and LF is the whole answer.
274
- d.children.push(emptyParagraph('', '\n'));
275
- }
276
- for (const child of d.children) {
277
- ensureEditableContainers(child);
278
- }
279
- const refMap = buildLinkReferenceMap(d.children);
280
- return { doc: d, resolver: refMap.resolve, signature: refMap.signature };
281
- }
218
+ // This editor's view of the global registries; `__registryEnablement` narrows it in tests.
219
+ // svelte-ignore state_referenced_locally
220
+ const registryView = createRegistryView({
221
+ plugins: pluginEntries ? activePlugins : undefined,
222
+ isEnabled: __registryEnablement,
223
+ syntax
224
+ });
282
225
 
283
- // doc/blockIds are mutable state structural ops write through directly, so they
284
- // cannot be $derived: snapshot at mount, re-sync via the $effect below.
226
+ // Plain state, not $derived: structural edits write `doc` and `blockIds` directly.
285
227
  // svelte-ignore state_referenced_locally
286
- const initial = initDocument(source);
287
- let doc = $state<Document>(initial.doc);
228
+ const initial = initDocument(source, registryView.grammar);
229
+ let doc: Document = $state(initial.doc);
288
230
  // svelte-ignore state_referenced_locally
289
231
  let blockIds = $state<string[]>(assignIds(doc.children));
290
- // Bumped by every byte-writing door. Inline widgets derive on it directly, and the
291
- // decoration engine's `editEpoch` rides it a tick later.
232
+ // Bumped by every path that writes bytes. Inline widgets derive on it directly, and the
233
+ // decoration engine's `editEpoch` follows it a tick later.
292
234
  const contentVersion = createContentVersion();
293
235
  let currentResolver = $state<LinkReferenceResolver>(initial.resolver);
294
236
  let currentSignature = $state<string>(initial.signature);
295
237
  // Reference-bearing render memos key on this instead of the whole (~MB) signature.
296
238
  let signatureEpoch = $state<number>(0);
297
- /** One ref for every reader — the block components through context and the action bundles
298
- * through their deps — so a post-commit rebuild reaches both without re-binding either. */
299
- const linkRefView: LinkReferenceResolverRef = {
300
- get current(): LinkReferenceResolver {
239
+ /** This editor's one reading context, shared by the block components (through context) and
240
+ * the action bundles (through their deps), so a post-commit rebuild reaches both. */
241
+ const reading: Reading = {
242
+ grammar: registryView.grammar,
243
+ get resolver(): LinkReferenceResolver {
301
244
  return currentResolver;
302
245
  },
303
- get signature(): string {
246
+ get resolverSignature(): string {
304
247
  return currentSignature;
305
248
  },
306
- get epoch(): number {
249
+ get resolverEpoch(): number {
307
250
  return signatureEpoch;
308
- }
251
+ },
252
+ mode: () => outgoingMode ?? effectiveMode,
253
+ hidesDelimitersAtCaret: () => hidesDelimitersAtCaret(outgoingMode ?? effectiveMode)
309
254
  };
310
- // Plain, not `$state`: the top-level scope's slot storage (see `refSlotsOver`).
255
+ // The root list's child component refs, plain rather than `$state` (see `refSlotsOver`).
311
256
  const blockRefs: (BlockComponent | undefined)[] = [];
312
257
  const blockRefSlots = refSlotsOver(blockRefs);
313
258
  let editorEl: HTMLDivElement | undefined = $state();
314
- // Inside a themed host the editor sits under no opt-in class, and the portaled search
315
- // bar must not carry one either: the class's defaults would shadow the host tokens the
316
- // anchor already inherits.
259
+ // A portaled search bar inside a themed host skips the theme class, whose defaults would
260
+ // shadow the host's tokens.
317
261
  const inThemedScope = $derived(!!editorEl?.closest('.aragonite-editor-theme'));
318
262
  let headerEl: HTMLDivElement | undefined = $state();
319
263
  let typeScaleProbeEl: HTMLDivElement | undefined = $state();
264
+ let widthProbeEl: HTMLDivElement | undefined = $state();
320
265
  const undoManager = createUndoManager();
321
266
  const sharing = createSharingState();
322
- const stickyColumn = createStickyColumnState();
323
- // Composed, not wired seam by seam: the marks are ephemeral caret state with the affinity's
324
- // exact lifetime, so every door that invalidates the arrival side drops them by construction.
325
- const pendingMarks = createPendingMarksState();
326
- const edgeAffinity = createEdgeAffinityState({ onInvalidate: pendingMarks.reset });
327
- const revealAnchor = createRevealAnchorState();
267
+ const caretMemory = createCaretMemory();
268
+ const autoPairs = createAutoPairRecord();
328
269
  const operationsLog = createOperationsLog();
329
270
  const events = createEditorEvents();
330
- // getSelection is function-hoisted below — callback reads the fresh snapshot each time.
271
+ // getSelection is function-hoisted below, so every read here is a fresh snapshot.
272
+ const selectionAnnouncer = createSelectionAnnouncer({
273
+ read: () => getSelection(),
274
+ emit: (selection) => events.emit('selectionChange', selection)
275
+ });
331
276
  const selectionState = createSelectionState({
332
- onChange: () => {
333
- // The other half of the mutual exclusion `onSelect` carries: a range opened while a
334
- // widget is selected (select-all declines to the widget's own keydown) would leave
335
- // both live, and every dispatch keyed on "a widget is selected" would answer for
336
- // the document-wide selection the user is looking at.
337
- if (selectionState.isCrossBlock) widgetSelection.clear();
338
- events.emit('selectionChange', getSelection());
277
+ onChange: ({ placementOnly }) => {
278
+ if (placementOnly) selectionAnnouncer.announceIfMoved();
279
+ else selectionAnnouncer.announce();
339
280
  },
340
- getDoc: () => doc
281
+ getDoc: () => doc,
282
+ // Read off the live document, since an image's own commits move its end byte.
283
+ widgetSpan: (target) => imageAtTarget(doc, target, reading)
341
284
  });
342
- const widgetSelection = createWidgetSelectionState({
343
- onSelect: () => {
344
- window.getSelection()?.removeAllRanges();
345
- // Announced like the `source` swap: dropping the native range ends the document
346
- // caret without moving a field the clear guards on, and the native `selectionchange`
347
- // it fires bails on the empty range before it reaches the channel.
348
- selectionState.batch(() => {
349
- selectionState.clear();
350
- selectionState.announceSelection();
351
- });
352
- }
285
+ // With no live range this reads no document bytes, so a keystroke at a caret costs nothing here.
286
+ const coverage = $derived.by(() => {
287
+ const { anchor, focus } = selectionState;
288
+ if (!selectionState.isCustomRendered || !anchor || !focus) return null;
289
+ return rangeCoverage(doc, coverRange(doc, anchor, focus));
353
290
  });
291
+ const widgetSelection = createWidgetSelectionState(selectionState);
292
+ const selectedWidget: SelectedWidgetHandle = {
293
+ range: () => selectionState.widgetRange(),
294
+ clear: widgetSelection.clear
295
+ };
354
296
 
355
297
  let selectionDescription = $derived(
356
298
  selectionState.isCrossBlock && selectionState.anchor && selectionState.focus
@@ -358,14 +300,21 @@
358
300
  : ''
359
301
  );
360
302
 
361
- // Its own polite region: clobbering selectionDescription would drop the move from
362
- // the a11y tree.
363
- let reorderAnnouncement = $state('');
303
+ // The edit live region, its own: sharing `selectionDescription`'s would drop a move's
304
+ // announcement. Only the undo controller speaks here, from a commit that wrote.
305
+ let editAnnouncement = $state('');
306
+ const announceEdit = async (message: string) => {
307
+ // Clear first: Svelte skips the DOM write on a ===-equal assignment, which drops
308
+ // the second of two identical announcements.
309
+ editAnnouncement = '';
310
+ await tick();
311
+ editAnnouncement = message;
312
+ };
364
313
 
365
- // Single elements, not per-block, so the hover/drag path adds no cost per mounted
366
- // component.
314
+ // One element each for the whole editor, so dragging costs nothing per mounted block.
367
315
  let reorderGhost = $state<{ clientX: number; clientY: number; label: string } | null>(null);
368
316
  let reorderLine = $state<{ left: number; top: number; width: number } | null>(null);
317
+ let dropCaret = $state<DropCaretRect | null>(null);
369
318
 
370
319
  $effect(() => {
371
320
  const dispose = events.on('edit', (e) => {
@@ -374,9 +323,8 @@
374
323
  path: e.path,
375
324
  detail: ('detail' in e ? e.detail : undefined) ?? {}
376
325
  });
377
- // The shell maintains only the LRD resolver (inline content computes lazily
378
- // on read — core/inline/inline-cache), and hands out a fresh identity only on
379
- // a real signature change: one per edit re-renders every block that read it.
326
+ // A fresh resolver only on a real signature change: one per edit would re-render
327
+ // every block that read it.
380
328
  if (lrdMapCouldChange(doc, e)) {
381
329
  const newMap = buildLinkReferenceMap(doc.children);
382
330
  const next = advanceSignatureEpoch(currentSignature, signatureEpoch, newMap.signature);
@@ -390,10 +338,8 @@
390
338
  return () => dispose();
391
339
  });
392
340
 
393
- // The one bump site for `editEpoch`. It rides the content version rather than the `edit`
394
- // event, whose `input` is batched across a typing burst and would leave every source a
395
- // pause behind the bytes; the tick keeps a source off a half-applied tree, and the
396
- // no-source skip keeps an undecorated editor at zero keystroke work (perf:check).
341
+ // The one place `editEpoch` is bumped, off the content version because the `edit` event
342
+ // batches a typing burst; the tick keeps decoration sources off a half-applied tree.
397
343
  let notifiedVersion = contentVersion.read();
398
344
  $effect(() => {
399
345
  const version = contentVersion.read();
@@ -402,265 +348,73 @@
402
348
  if (decorationEngine.sourceCount > 0) void tick().then(() => decorationEngine.notifyEdit());
403
349
  });
404
350
 
405
- // Counts whole-document REPLACEMENTS, which the edit epoch cannot tell from a
406
- // keystroke. Deliberately NOT $state: its readers run inside decoration `provide`,
407
- // which must register no reactive dependency.
408
- let documentGeneration = 0;
409
-
410
- // `source !== lastSource` guard is load-bearing — see `docs/design/editor.md` § Reactive state plumbing.
411
- // svelte-ignore state_referenced_locally
412
- let lastSource = source;
413
- $effect(() => {
414
- if (source !== lastSource) {
415
- // A pending typing batch addresses the OUTGOING document, so it flushes
416
- // while its path still resolves; left armed, the timer fires note A's path
417
- // against note B.
418
- controller.flushDebouncedCheckpoint();
419
- lastSource = source;
420
- const reset = initDocument(source);
421
- doc = reset.doc;
422
- contentVersion.bump();
351
+ const documentSwap = createDocumentSwap({
352
+ grammar: registryView.grammar,
353
+ // Built below; a swap runs post-init, so the closures read past the TDZ.
354
+ flushDebouncedCheckpoint: () => controller.flushDebouncedCheckpoint(),
355
+ noteTreeSwap: () => caretLanding.noteTreeSwap(),
356
+ adoptDocument: (next) => {
357
+ doc = next;
423
358
  blockIds = assignIds(doc.children);
359
+ },
360
+ bumpContentVersion: contentVersion.bump,
361
+ clearBlockRefs: () => {
424
362
  blockRefs.length = 0;
425
- // Block ids never recur, so every measured height now keys a block that cannot
426
- // come back: the scopes reseed from estimates exactly as they do on first load.
427
- heightOracle.dropMeasured();
428
- undoManager.clear();
429
- stickyColumn.reset();
430
- edgeAffinity.reset();
431
- // Announced, not left to the clear: the swap usually arrives on a plain caret, which
432
- // is native-only, so nothing editor-owned moves and subscribers would keep painting
433
- // the outgoing document's selection. Batched, so a real range still emits once.
434
- selectionState.batch(() => {
435
- selectionState.clear();
436
- selectionState.announceSelection();
437
- });
438
- documentGeneration++;
439
- // The resolver refreshes unconditionally — the old one closes over the
440
- // swapped-out doc — but the epoch bumps only on a differing LRD signature.
441
- const next = advanceSignatureEpoch(currentSignature, signatureEpoch, reset.signature);
442
- currentResolver = reset.resolver;
363
+ },
364
+ get layout() {
365
+ return layout;
366
+ },
367
+ undoManager,
368
+ caretMemory,
369
+ closeMenus,
370
+ widgetSelection,
371
+ selection: selectionState,
372
+ // The counter bumps only when the link-reference signature differs; the resolver
373
+ // refreshes regardless.
374
+ adoptLinkReferences: (resolver, signature) => {
375
+ const next = advanceSignatureEpoch(currentSignature, signatureEpoch, signature);
376
+ currentResolver = resolver;
443
377
  currentSignature = next.signature;
444
378
  signatureEpoch = next.epoch;
445
- }
379
+ },
380
+ events
446
381
  });
447
382
 
448
- /**
449
- * The host's own chrome, mounted inside this root. Every rule meaning "this is the
450
- * editor's own CONTENT" asks here rather than carrying its own `contains` copy,
451
- * which is how one gets missed. The focusout guards keep using `contains` — for
452
- * "did focus leave the whole widget", the slot IS part of the editor.
453
- */
383
+ // The equality check is required: `docs/design/editor.md` § Reactive state plumbing.
384
+ // svelte-ignore state_referenced_locally
385
+ let lastSource = source;
386
+ $effect(() => {
387
+ if (source === lastSource) return;
388
+ lastSource = source;
389
+ documentSwap.swapTo(source);
390
+ });
391
+
392
+ /** Whether `node` is in the host's header; every "is this editor content" check asks here.
393
+ * Focus tracking uses `contains` instead, since the header counts as part of the editor. */
454
394
  function isHostChrome(node: Node | null): boolean {
455
395
  return !!node && !!headerEl && headerEl.contains(node);
456
396
  }
457
397
 
458
- // ── Dead-space caret ────────────────────────────────────────────────
459
-
460
- // A click in the root's padding or below the last block places a caret rather than
461
- // doing nothing. `getBlockComponent` is hoisted; the reset closure defers its reads.
462
- const deadSpaceCaret = createDeadSpaceCaret({
463
- getBlockComponent,
464
- resetSelectionForClick: () =>
465
- resetForPointerDown(selectionState, stickyColumn, edgeAffinity, false),
466
- gapScope: {
467
- getDoc: () => doc,
468
- selection: selectionState,
469
- getPresentationMode: () => effectiveMode
470
- },
471
- lastBlockIndex: () => doc.children.length - 1,
472
- revealBlock: (index) => revealPath([index])
473
- });
474
-
475
398
  // ── Block menu ──────────────────────────────────────────────────────
476
399
 
477
- // Opened by the bottom `+` and by a right-click on prose with nothing selected; a right-click
478
- // over a selection leaves the formatting popover (the host's) in charge and only suppresses
479
- // the native menu. Tables run their own cell menu and have prevented the default first.
480
- let blockMenu = $state<{
481
- x: number;
482
- y: number;
483
- anchor: () => { x: number; y: number };
484
- items: MenuEntry[];
485
- label: string;
486
- pick: (id: string) => void;
487
- } | null>(null);
488
-
489
- // Open/close transitions only, never the mount, so a subscriber's first news is a real menu.
400
+ // Opened by a right-click on prose with nothing selected; over a selection the host's
401
+ // formatting popover stays in charge, and tables run their own cell menu.
402
+ let blockMenu = $state<BlockMenuModel | null>(null);
403
+
404
+ // Emits on open/close transitions only, so a subscriber's first event is a real menu.
405
+ const menuPresence = createMenuPresence();
490
406
  let menuWasOpen = false;
491
407
  $effect(() => {
492
- const open = blockMenu !== null;
408
+ const open = menuPresence.isOpen;
493
409
  if (open === menuWasOpen) return;
494
410
  menuWasOpen = open;
495
411
  events.emit('menuChange', open);
496
412
  });
497
413
 
498
- function anchorOn(el: Element, point: { x: number; y: number }): () => { x: number; y: number } {
499
- const rect = el.getBoundingClientRect();
500
- const dx = point.x - rect.left;
501
- const dy = point.y - rect.top;
502
- return () => {
503
- const now = el.getBoundingClientRect();
504
- return { x: now.left + dx, y: now.top + dy };
505
- };
506
- }
507
-
508
- // A right-click on a BLOCK — a fence, an equation, an image — is that block's context menu:
509
- // its kind's registered actions, then the defaults. Prose is the page's background and keeps
510
- // the browser's own menu (spelling, the platform's clipboard), as does a selection, whose
511
- // affordance is the host's formatting popover. The margin shows nothing at all.
512
- function onRootContextMenu(e: MouseEvent): void {
513
- if (e.defaultPrevented || effectiveMode === 'reading' || !editorEl) return;
514
- const target = e.target instanceof Element ? e.target : null;
515
- if (!target || isHostChrome(target)) return;
516
- e.preventDefault();
517
- // The keyboard's contextmenu event (Windows fires it on the ContextMenu key's release)
518
- // lands on the item the keydown's own menu already focused: that menu is the answer.
519
- if (target.closest('.md-menu')) return;
520
- const host = target.closest<HTMLElement>('.block-host[data-block-path]');
521
- const path = pathOf(host);
522
- if (!host || !path) return;
523
- const point = { x: e.clientX, y: e.clientY };
524
- const native = window.getSelection();
525
- const selected = !!native && !native.isCollapsed && editorEl.contains(native.anchorNode);
526
- // A selection's menu is the clipboard's, over the selection as it stands. So is prose's
527
- // (the page's background), at a caret placed where the press landed; a nested block (a
528
- // fence inside a list item) takes the same for now.
529
- const node = path.length === 1 ? doc.children[path[0]] : undefined;
530
- if (selected || !node || isProseBackground(node)) {
531
- if (!selected) placeCaretAtPoint(e.clientX, e.clientY);
532
- // Top-level prose is where a sibling block makes sense; a nested block or a selection
533
- // gets the clipboard alone.
534
- const insertAfter = !selected && node && isProseBackground(node) ? path[0] : null;
535
- openClipboardMenu(point, host, insertAfter);
536
- return;
537
- }
538
- const index = path[0];
539
- const actions = blockContextActionsFor(node, path);
540
- if (actions.length === 0) return;
541
- const ctx = {
542
- node,
543
- path,
544
- deleteBlock: async () => {
545
- await blockEdit.deleteBlock(index);
546
- },
547
- replaceRaw: async (raw: string) => {
548
- await blockEdit.updateBlockContent(index, raw);
549
- }
550
- };
551
- blockMenu = {
552
- ...point,
553
- anchor: anchorOn(host, point),
554
- items: actions.map(({ id, label, icon, danger }) => ({
555
- id,
556
- label,
557
- icon: icon as MenuEntry['icon'],
558
- danger
559
- })),
560
- label: BLOCK_ACTIONS_LABEL,
561
- pick: (id) => {
562
- blockMenu = null;
563
- const action = actions.find((a: BlockContextAction) => a.id === id);
564
- if (action) void action.run(ctx);
565
- }
566
- };
567
- }
414
+ // ── Link card ───────────────────────────────────────────────────────
568
415
 
569
- // The editing surface a paste goes to: whatever editable holds focus inside the root.
570
- function focusedEditable(): HTMLElement | null {
571
- const active = document.activeElement;
572
- return active instanceof HTMLElement && editorEl?.contains(active) && active.isContentEditable
573
- ? active
574
- : null;
575
- }
576
-
577
- function clipboardRows(): MenuEntry[] {
578
- const selected = !!focusedEditable() && !(window.getSelection()?.isCollapsed ?? true);
579
- return [
580
- { id: 'clip.cut', label: 'Cut', icon: 'scissors', disabled: !selected },
581
- { id: 'clip.copy', label: 'Copy', icon: 'copy', disabled: !selected },
582
- { id: 'clip.paste', label: 'Paste', icon: 'clipboard' },
583
- { id: 'clip.paste-plain', label: 'Paste as plain text', icon: 'type' }
584
- ];
585
- }
586
-
587
- function runClipboardRow(id: string): boolean {
588
- if (!id.startsWith('clip.')) return false;
589
- void runClipboardAction(id.slice('clip.'.length) as ClipboardAction, focusedEditable());
590
- return true;
591
- }
592
-
593
- /** `insertAfter` names the top-level block an "Insert block" flyout mints an empty sibling
594
- * after; null leaves the menu to the clipboard rows alone. */
595
- function openClipboardMenu(
596
- point: { x: number; y: number },
597
- anchorEl: Element,
598
- insertAfter: number | null = null
599
- ): void {
600
- const insert: MenuEntry[] =
601
- insertAfter === null
602
- ? []
603
- : [
604
- { id: 'sep', label: '', divider: true },
605
- { id: 'insert', label: 'Insert block', icon: 'plus', children: insertFlyoutEntries() }
606
- ];
607
- blockMenu = {
608
- ...point,
609
- anchor: anchorOn(anchorEl, point),
610
- items: [...clipboardRows(), ...insert],
611
- label: BLOCK_ACTIONS_LABEL,
612
- pick: (id) => {
613
- blockMenu = null;
614
- if (runClipboardRow(id)) return;
615
- const md = insertSnippets().get(id);
616
- if (md && insertAfter !== null) void insertBlockAfter(insertAfter, md);
617
- }
618
- };
619
- }
620
-
621
- // The same two steps the tail's `+` takes: mint the empty paragraph, which lands the caret in
622
- // it, then hand the snippet to the surface that now holds focus.
623
- async function insertBlockAfter(index: number, md: string): Promise<void> {
624
- await blockEdit.insertParagraph(index + 1, '');
625
- insertMarkdown(md);
626
- }
627
-
628
- function pathOf(host: HTMLElement | null): number[] | null {
629
- const raw = host?.dataset.blockPath;
630
- if (!raw) return null;
631
- try {
632
- const parsed: unknown = JSON.parse(raw);
633
- return Array.isArray(parsed) && parsed.every((n) => typeof n === 'number') ? parsed : null;
634
- } catch {
635
- return null;
636
- }
637
- }
638
-
639
- async function onTailPlus(button: HTMLElement): Promise<void> {
640
- await blockEdit.insertParagraph(doc.children.length, '');
641
- const rect = button.getBoundingClientRect();
642
- const point = { x: rect.left, y: rect.bottom + 4 };
643
- const snippets = insertSnippets();
644
- blockMenu = {
645
- ...point,
646
- anchor: anchorOn(button, point),
647
- items: [...insertMenuEntries(), { id: 'sep', label: '', divider: true }, ...clipboardRows()],
648
- label: BLOCK_MENU_LABEL,
649
- pick: (id) => {
650
- blockMenu = null;
651
- if (runClipboardRow(id)) return;
652
- const md = snippets.get(id);
653
- if (md) insertMarkdown(md);
654
- }
655
- };
656
- }
657
-
658
- // ── Link card door ──────────────────────────────────────────────────
659
-
660
- // The caret snapshot and the entry guards ride the STATE, not the callers, so entry path N+1
661
- // cannot forget them. All three decline a cross-block range absolutely; on a same-block one
662
- // they split by gesture: an unasked-for click must not interrupt it, the create gesture wraps
663
- // it, and the chord has already resolved it against the construct it opens.
416
+ // The caret snapshot and entry checks live on the link card state so no entry path skips
417
+ // them. Each refuses a cross-block range; a click also refuses a selection, which create needs.
664
418
  const linkCard = createLinkCardState({
665
419
  onOpen: () => linkCardCaret.saveCurrent(),
666
420
  canOpen: () => !selectionState.isCrossBlock && window.getSelection()?.isCollapsed !== false,
@@ -669,29 +423,21 @@
669
423
  !selectionState.isCrossBlock && window.getSelection()?.isCollapsed === false
670
424
  });
671
425
 
672
- /** Open the card on the link `el` renders, or report that nothing there is one. The caret has
673
- * already landed from mousedown, which is the one the state snapshots. */
674
- function openLinkCard(el: Element): boolean {
675
- const path = findSurfacePathForElement(el);
676
- if (!path) return false;
677
- const block = nodeAt(doc, path);
678
- if (block === null || !isBlockNode(block)) return false;
679
- const contentEl = getBlockElByPath(path);
680
- if (!contentEl) return false;
681
- const hit = resolveLinkAtPoint({ contentEl, block, path, linkRef: linkRefView });
682
- if (!hit) return false;
683
- return linkCard.open(hit.target);
426
+ function closeMenus(): void {
427
+ blockMenu = null;
428
+ inlineMenu.close();
429
+ linkCard.close();
684
430
  }
685
431
 
686
- // The card belongs to live mode alone; any other mode paints the destination bytes already.
432
+ // A menu opened in one mode offers that mode's edits, so a mode change closes every menu.
687
433
  $effect(() => {
688
- if (effectiveMode !== 'live') linkCard.close();
434
+ void effectiveMode;
435
+ untrack(closeMenus);
689
436
  });
690
437
 
691
- // ── Hidden-run class probe ──────────────────────────────────────────
438
+ // ── Hidden-run class check ──────────────────────────────────────────
692
439
 
693
- // Once per mode change, the probe pays the getComputedStyle the per-keystroke hidden-run
694
- // predicate cannot afford, so the two class vocabularies cannot drift silently.
440
+ // Once per mode change, so the getComputedStyle cost stays off the keystroke path.
695
441
  $effect(() => {
696
442
  void effectiveMode;
697
443
  if (!editorEl) return;
@@ -699,198 +445,44 @@
699
445
  assertInvariant('marker-css-parity', () => checkMarkerCssParity(root));
700
446
  });
701
447
 
702
- // ── Root gesture listeners ──────────────────────────────────────────
703
-
704
- $effect(() => {
705
- if (!editorEl) return;
706
- const root = editorEl;
707
- const handleClick = (e: MouseEvent) => {
708
- const target = e.target as Element | null;
709
- // Ahead of the anchor arm: a blocked-scheme link renders as a SPAN, and it is exactly
710
- // the link a user opens the card to fix. Mod-click still activates, below.
711
- if (effectiveMode === 'live' && !e.ctrlKey && !e.metaKey) {
712
- const linkEl = target?.closest(LINK_ELEMENT_SELECTOR);
713
- if (linkEl && !isHostChrome(linkEl) && openLinkCard(linkEl)) {
714
- e.preventDefault();
715
- return;
716
- }
717
- }
718
- const anchor = target?.closest('a[href]') as HTMLAnchorElement | null;
719
- // The helper claims only clicks whose target IS the root, so nothing the
720
- // editor renders is touched. A margin drag's release arrives as a root click too (the
721
- // press and release targets meet at the root); with a range just painted, it is no
722
- // click to answer.
723
- if (!anchor) {
724
- // A multi-click places no caret: the ladder painted its range over this press.
725
- if (e.detail >= 2) {
726
- marginDrag = false;
727
- return;
728
- }
729
- const pressed = marginDrag;
730
- const dragged =
731
- pressed &&
732
- (Math.abs(e.clientX - marginDown.x) > 3 || Math.abs(e.clientY - marginDown.y) > 3);
733
- marginDrag = false;
734
- if (dragged) return;
735
- if (deadSpaceCaret.handleClick(root, e)) return;
736
- // A press the editor took on a block (a host's own padding beside a table, a rule, a
737
- // folded equation's face) that did not move is a click on it: the click helper
738
- // claims only dead space, so the same landing is resolved here.
739
- if (pressed && !deadSpaceCaret.isDeadSpaceTarget(root, e.target)) {
740
- if (placeCaretAtPoint(e.clientX, e.clientY)) return;
741
- }
742
- // Declined everywhere: a click on nothing still LEAVES what was being edited (a
743
- // revealed equation folds on blur), since the margin press suppressed the blur the
744
- // browser would have done.
745
- if (pressed) blurEditingSurface(root);
746
- return;
747
- }
748
- // Host chrome follows the page's link behaviour, not plain-click-edits.
749
- if (isHostChrome(anchor)) return;
750
- const href = anchor.getAttribute('href');
751
- if (!href) return;
752
- // Reading mode has no caret for a plain click to place, so links behave as
753
- // in a rendered document.
754
- if (e.ctrlKey || e.metaKey || effectiveMode === 'reading') {
755
- e.preventDefault();
756
- activateLink(href, e);
757
- } else {
758
- // Suppress the browser's link navigation; cursor placement comes from
759
- // mousedown and is unaffected.
760
- e.preventDefault();
761
- }
762
- };
763
- // Dead space, and the parts of a block that are neither editable nor controls: a rendered
764
- // equation, a diagram, a card's rendered face. A press on any of them cannot grow a native
765
- // selection, so the editor's drag runs from it.
766
- // A whole-block input proxy is an editable in name only (it catches IME for a block with no
767
- // text), so a press on it starts the editor's drag like a press on the block itself.
768
- const NOT_A_DRAG_START =
769
- '[contenteditable="true"]:not([data-whole-block-input]), button, input, textarea, select, ' +
770
- 'a, summary, [role="checkbox"], ' +
771
- '.code-rail, .table-add-zone, .editor-tail, .md-menu, .block-drag-handle';
772
- const dragStartsHere = (rootEl: HTMLElement, target: EventTarget | null): boolean => {
773
- if (claimsPointerGesture(target)) return false;
774
- if (deadSpaceCaret.isDeadSpaceTarget(rootEl, target)) return true;
775
- if (!(target instanceof Element) || !rootEl.contains(target)) return false;
776
- return target.closest(NOT_A_DRAG_START) === null;
777
- };
778
- // A drag that STARTS in the margin: the browser cannot grow a selection from a
779
- // non-editable press into an editable block, so the editor runs the drag itself, anchored
780
- // where a click there would land. The mousedown's default is suppressed so the browser
781
- // starts no selection of its own to fight it; `click` still fires for the caret placement.
782
- let marginDrag = false;
783
- let marginDown = { x: 0, y: 0 };
784
- let marginSession: { dispose(): void } | null = null;
785
- const startMarginDrag = (e: PointerEvent) => {
786
- marginDrag = false;
787
- marginDown = { x: e.clientX, y: e.clientY };
788
- if (e.button !== 0 || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
789
- if (effectiveMode === 'reading' || !dragStartsHere(root, e.target)) return;
790
- const anchor = deadSpaceCaret.anchorAtPoint(root, e.clientX, e.clientY);
791
- if (!anchor) return;
792
- marginDrag = true;
793
- resetForPointerDown(selectionState, stickyColumn, edgeAffinity, false);
794
- // A block that runs its own drag from a nearby press (a table's cell rectangle) takes
795
- // it; the generic drag is for blocks that have none.
796
- if (!('offset' in anchor)) {
797
- const component = getBlockComponent(anchor.path);
798
- if (component?.startDragAtPoint?.(e.clientX, e.clientY, e)) return;
799
- }
800
- marginSession = installDragListener(
801
- {
802
- editorRoot: root,
803
- scrollContainer: getScrollHost() ?? root,
804
- selection: selectionState,
805
- getBlockElByPath,
806
- lifetimeSignal: lifetimeController.signal,
807
- paintSameBlock: true
808
- },
809
- anchor,
810
- e
811
- );
812
- };
813
- return removeAll(
814
- onRoot(root, 'click', handleClick),
815
- installMultiClickSelect({
816
- editorRoot: root,
817
- selection: selectionState,
818
- getBlockElByPath,
819
- getScrollContainer: () => getScrollHost() ?? root,
820
- lifetimeSignal: lifetimeController.signal,
821
- marginBlockAt: (target, x, y) =>
822
- effectiveMode !== 'reading' && dragStartsHere(root, target)
823
- ? deadSpaceCaret.blockPathNearPoint(root, x, y)
824
- : null
825
- }),
826
- installSelectionDrop({
827
- editorRoot: root,
828
- getDoc: () => doc,
829
- controller,
830
- coordinator: pasteCoordinator,
831
- getPresentationMode: () => effectiveMode,
832
- linkRef: linkRefView,
833
- grammar: registryView.grammar,
834
- activePlugins,
835
- events,
836
- isReadOnly: () => effectiveMode === 'reading'
837
- }),
838
- onRoot(root, 'pointerdown', startMarginDrag),
839
- onRoot(root, 'mousedown', (e: MouseEvent) => {
840
- deadSpaceCaret.notePress(root, e);
841
- // The second press of a click run is the ladder's, which runs its own drag.
842
- if (marginDrag && e.detail >= 2) {
843
- marginSession?.dispose();
844
- marginSession = null;
845
- marginDrag = false;
846
- }
847
- if (marginDrag) e.preventDefault();
848
- })
849
- );
850
- });
448
+ // ── Root listeners ──────────────────────────────────────────────────
851
449
 
852
- // Release on the next user-intent gesture, so the anchor holds only through the post-reveal
853
- // settle. NOT on `scroll`: a programmatic correctAnchor write fires `scroll` itself and
854
- // would self-release mid-settle. On the resolved PORT, not the root — the pin fights
855
- // whoever scrolls the port, and in host mode that gesture lands outside the editor.
450
+ // On the resolved scroll container, not the root: in host mode the user scrolls outside the
451
+ // editor, and that scroll must release the block held in place.
856
452
  $effect(() => {
857
453
  if (!editorEl) return;
858
454
  const target = getScrollHost();
859
455
  if (!target) return;
860
- const release = () => revealAnchor.releaseAll();
861
- return removeAll(
862
- onRoot(target, 'keydown', release),
863
- onRoot(target, 'pointerdown', release),
864
- onRoot(target, 'wheel', release, { passive: true })
865
- );
456
+ return installRevealAnchorRelease(target, scrollOwner.release);
866
457
  });
867
458
 
868
459
  $effect(() => {
869
460
  if (!editorEl) return;
870
461
  const root = editorEl;
871
- // focusout bubbles, so reset only when focus leaves the editor entirely.
462
+ // focusout bubbles, so reset only when focus leaves the editor's content: for somewhere
463
+ // outside it, or for the host's header, which holds no caret of the editor's.
872
464
  return onRoot(root, 'focusout', (e: FocusEvent) => {
873
465
  const next = e.relatedTarget as Node | null;
874
- if (next && root.contains(next)) return;
875
- stickyColumn.reset();
876
- edgeAffinity.reset();
466
+ if (next && root.contains(next) && !isHostChrome(next)) return;
467
+ caretMemory.forget();
877
468
  });
878
469
  });
879
470
 
880
- // Its own effect: this reads no root binding, so pairing it with the focusout one
881
- // would make it wait for the bind and re-install on every root change.
471
+ // Its own effect, since it needs no root and would otherwise re-install on every root change.
882
472
  $effect(() =>
883
473
  onRoot(document, 'visibilitychange', () => {
884
474
  if (document.visibilityState === 'hidden') {
885
- stickyColumn.reset();
886
- edgeAffinity.reset();
475
+ caretMemory.forget();
887
476
  }
888
477
  })
889
478
  );
890
479
 
891
- // Register as a body-chord claimant so the document-level keydown handler routes a
892
- // body-level chord to exactly one instance: a lone editor claims unconditionally,
893
- // among several the last-interacted one wins.
480
+ $effect(() => {
481
+ if (!editorEl) return;
482
+ return installUndoStepEnd(editorEl, () => controller.endUndoStep());
483
+ });
484
+
485
+ // A chord that reaches <body> goes to one editor: the only one, or the last one interacted with.
894
486
  $effect(() => {
895
487
  if (!editorEl) return;
896
488
  const root = editorEl;
@@ -905,75 +497,45 @@
905
497
 
906
498
  // ── Block element and component lookup ──────────────────────────────
907
499
 
908
- // The measurement surface for cross-block caret math. Table cells carry no
909
- // data-block-path (they render without BlockHost), so a deep cell path resolves
910
- // the table wrapper and walks into the cell DOM.
911
- const getBlockElByPath: BlockElLookup = (path) => {
912
- if (!editorEl) return null;
913
- const directWrapper = editorEl.querySelector(`[data-block-path='${JSON.stringify(path)}']`);
914
- if (directWrapper) {
915
- return directWrapper.querySelector(BLOCK_CONTENT_SELECTOR) as HTMLElement | null;
500
+ const getBlockElByPath: BlockElLookup = (path) =>
501
+ editorEl ? blockContentElAt(editorEl, path) : null;
502
+
503
+ // The root list as a descent reads it; the window's calls go through arrows because
504
+ // `topWindowing` is declared further down.
505
+ const rootList: ChildList = {
506
+ count: () => doc.children.length,
507
+ refs: blockRefSlots,
508
+ windowing: {
509
+ revealChild: (index) => topWindowing.revealChild(index),
510
+ isInWindow: (index) => topWindowing.isInWindow(index)
916
511
  }
917
- if (path.length < 3) return null;
918
- const tablePath = path.slice(0, -2);
919
- const rowIdx = path[path.length - 2];
920
- const colIdx = path[path.length - 1];
921
- const tableWrapper = editorEl.querySelector(`[data-block-path='${JSON.stringify(tablePath)}']`);
922
- if (!tableWrapper) return null;
923
- const tableEl = tableWrapper.querySelector(':scope > [role="table"]');
924
- if (!tableEl) return null;
925
- const rowEl = tableEl.querySelector(`:scope > [data-table-row-idx='${rowIdx}']`);
926
- if (!rowEl) return null;
927
- const cells = rowEl.querySelectorAll(':scope > [role="cell"]');
928
- return (cells[colIdx] as HTMLElement | undefined) ?? null;
929
512
  };
930
513
 
931
- // The non-scrolling sibling of revealPath, and the one descent both the rect API
932
- // and the test surface consume — a second closure would drift from it.
514
+ // The component already mounted at `path`, mounting nothing; shared by the rect API and the
515
+ // test hooks.
933
516
  function getBlockComponent(path: number[]): BlockComponent | null {
934
- if (path.length === 0) return null;
935
- const [first, ...rest] = path;
936
- const ref = blockRefs[first];
937
- if (!ref) return null;
938
- if (rest.length === 0) return ref;
939
- return ref.getBlockComponentByPath?.(rest) ?? null;
517
+ return componentAt(rootList, path);
940
518
  }
941
519
 
942
520
  // ── Action Bundles ──────────────────────────────────────────────────
943
521
 
944
- // Hoisted so the deps literal below can reference it before the VR state it reads
945
- // is declared; the body runs only at call time, post-init.
946
- async function revealPath(path: number[]): Promise<BlockComponent | null> {
947
- if (path.length === 0) return null;
948
- const top = path[0];
949
- // The shared reveal-and-wait gate skips an already-mounted block, re-checks after
950
- // a spurious cross-level wake, and — load-bearing for VR-5 — degrades instead of
951
- // hanging when a stale model left `top` outside the recomputed window.
952
- await revealChildOrWait(top, {
953
- slots: blockRefSlots,
954
- childCount: doc.children.length,
955
- revealChild: topWindowing.revealChild,
956
- isInWindow: topWindowing.isInWindow
957
- });
958
- const ref = blockRefs[top];
959
- if (!ref) return null;
960
- if (path.length === 1) return ref;
961
- return ref.revealByPath
962
- ? await ref.revealByPath(path.slice(1))
963
- : (ref.getBlockComponentByPath?.(path.slice(1)) ?? null);
964
- }
522
+ const scrollOwner = createScrollOwner({
523
+ getScrollHost,
524
+ editorCorrects: ownsScrollCorrection,
525
+ getBlockElByPath,
526
+ getEditorRoot: () => editorEl ?? null,
527
+ isHostScroll: () => hostScroll,
528
+ getClipBounds
529
+ });
965
530
 
966
- // The instance's resolution over the global block definitions: an unlisted plugin's kind
967
- // resolves no component here and its opener leaves this grammar. A prop-less editor reads
968
- // the global registry verbatim.
969
- // The harness door composes rather than replaces: widening past what the prop activated
970
- // would prove a resolution the shipped path cannot reach.
971
- // svelte-ignore state_referenced_locally
972
- const registryView = createRegistryView({
973
- isEnabled: bothEnable(
974
- pluginEntries ? kindEnablementFor(activePlugins) : undefined,
975
- __registryEnablement
976
- )
531
+ const caretLanding = createCaretLanding({
532
+ getDoc: () => doc,
533
+ root: rootList,
534
+ selectionState,
535
+ caretMemory,
536
+ getBlockElByPath,
537
+ getEditorRoot: () => editorEl ?? null,
538
+ scroll: scrollOwner
977
539
  });
978
540
 
979
541
  const editorActionsDeps: EditorActionsDeps = {
@@ -997,27 +559,21 @@
997
559
  setBlockRefs: (v) => replaceRefs(blockRefs, v),
998
560
  undoManager,
999
561
  sharing,
1000
- stickyColumn,
1001
- edgeAffinity,
562
+ caretMemory,
1002
563
  selectionState,
1003
564
  getBlockElByPath,
1004
- revealPath,
565
+ caretLanding,
1005
566
  events,
1006
- grammar: registryView.grammar,
1007
- getPresentationMode: () => effectiveMode,
1008
- get linkRef() {
1009
- return linkRefView;
1010
- }
567
+ reading
1011
568
  };
1012
- const { blockEdit, focus, history, containerEdit, controller } =
1013
- createEditorActions(editorActionsDeps);
569
+ const { blockEdit, focus, history, containerEdit, controller } = createEditorActions(
570
+ editorActionsDeps,
571
+ announceEdit
572
+ );
1014
573
 
1015
- // A getter, so block components read the live doc at keystroke time rather than
1016
- // the snapshot they mounted with.
574
+ // A getter, so block components read the live doc rather than the one they mounted with.
1017
575
  const getDoc: DocumentGetter = () => doc;
1018
576
 
1019
- // Ahead of the plugin contexts because the plugin door hands its registry into
1020
- // createEditorPluginContexts below.
1021
577
  const decorationEngine = createDecorationEngine({
1022
578
  getDoc,
1023
579
  onSourceError: (source, error) =>
@@ -1025,26 +581,36 @@
1025
581
  });
1026
582
  const decorations: DecorationRegistry = { addSource: decorationEngine.addSource };
1027
583
 
1028
- // Reuses revealPath/getBlockElByPath/getBlockComponent so nothing measures through
1029
- // a second closure.
1030
584
  const rects = createEditorRects({
1031
585
  getBlockElByPath,
1032
- getBlockComponentByPath: getBlockComponent,
1033
- revealPath,
586
+ getBlockComponent,
587
+ revealPath: (path) => caretLanding.mount(path, { openCollapsed: true }),
1034
588
  getEditorRoot: () => editorEl ?? null,
1035
- isHostScroll: () => hostScroll,
1036
- getClipBounds,
589
+ scroll: scrollOwner,
1037
590
  isCrossBlock: () => selectionState.isCrossBlock,
1038
591
  isHostChrome,
1039
- revealAnchor,
1040
- // A navigation holds its pin, unlike the consumer restore door: nothing follows
1041
- // it that wants the viewport back, and the band should outlive a late decode.
1042
- landCaretAt: landCaretAtOffset
592
+ landCaretAt: navigateCaret
1043
593
  });
1044
594
 
1045
- // After getDoc so it reuses that one live-doc closure: a second getDoc would be a
1046
- // TDZ reference here, and the rule is one getter, never a captured value.
1047
595
  const editorId = mintEditorId();
596
+
597
+ // The typed-trigger menus (`#tag`, `[[link`); a pick commits as one undo step, like the
598
+ // link card's edits.
599
+ const inlineRange = createInlineRangeCommit({ deps: editorActionsDeps, controller });
600
+ const inlineMenu = createInlineMenuState({
601
+ getDoc,
602
+ getSelection,
603
+ getMode: reading.mode,
604
+ events,
605
+ editorId,
606
+ reading,
607
+ commitRange: (path, start, end, bytes, caretAfter) =>
608
+ inlineRange.commitInlineRange(path, start, end, bytes, caretAfter, { landCaret: true }),
609
+ undoStep: (path, offset, run) => controller.undoStep({ path: docPathFrom(path), offset }, run)
610
+ });
611
+ const inlineMenus: InlineMenuRegistry = inlineMenu.registry;
612
+ $effect(() => () => inlineMenu.dispose());
613
+
1048
614
  const pluginContexts = createEditorPluginContexts({
1049
615
  editorId,
1050
616
  getDoc,
@@ -1052,21 +618,24 @@
1052
618
  optionsFor: (name) => pluginEntries?.optionsByName.get(name),
1053
619
  decorations,
1054
620
  rects,
1055
- // The one injection point of the mode into the dispatch tiers; they read it back
1056
- // through the pluginEditor lookup they already thread.
1057
- getPresentationMode: () => effectiveMode,
621
+ inlineMenus,
622
+ getDocumentGeneration: documentSwap.generation,
623
+ // The one place the mode enters command dispatch, read back through `pluginEditor`.
624
+ getPresentationMode: reading.mode,
1058
625
  getTheme: () => theme,
1059
- activation: activePlugins
626
+ activation: activePlugins,
627
+ // Called at use, never here: both read state declared further down this component.
628
+ insertMarkdown: (md, options) => insertMarkdown(md, options),
629
+ runCommand: (commandId, arg) => runCommand(commandId, arg),
630
+ reading
1060
631
  });
1061
632
 
1062
- // One definition, threaded by every dispatch tier that can reach a plugin-global
1063
- // handler, so leaf, cross-block and editor-root route identically.
633
+ // One lookup for every dispatch level, so block, cross-block and root route plugins alike.
1064
634
  const pluginEditorLookup: PluginEditorLookup = (name) => pluginContexts.get(name);
1065
635
  const commandErrorSink: CommandErrorSink = (report) => emitCommandError(events, report);
1066
636
 
1067
- // onMount, NEVER a plain $effect: attachAll synchronously runs plugin callbacks that
1068
- // read reactive state, so an effect would take doc.children as a dependency and the
1069
- // first structural edit would dispose every subscription (casebook.md's re-init scar).
637
+ // onMount, not $effect: plugin callbacks read reactive state, and an effect would tear
638
+ // every subscription down on the first structural edit.
1070
639
  onMount(() => {
1071
640
  pluginContexts.attachAll(({ plugin, error }) =>
1072
641
  events.emit('error', { origin: 'subscriber', error, context: { plugin } })
@@ -1077,8 +646,7 @@
1077
646
  // Aborted on unmount; document-level listeners observe it to cancel mid-operation work.
1078
647
  const lifetimeController = new AbortController();
1079
648
  $effect(() => () => {
1080
- // Same reason as the source swap: a timer outliving the component emits into
1081
- // subscribers the host still holds, for a document that is gone.
649
+ // A pending checkpoint timer would otherwise emit for a document that is gone.
1082
650
  controller.flushDebouncedCheckpoint();
1083
651
  lifetimeController.abort();
1084
652
  });
@@ -1087,65 +655,79 @@
1087
655
  // other's broken-state recompute.
1088
656
  const brokenImageUrls = new Set<string>();
1089
657
 
1090
- const pasteCoordinator = createPasteCoordinator(controller, revealPath);
658
+ const pasteCoordinator = createPasteCoordinator(editorActionsDeps, controller);
1091
659
 
1092
- // The document caret while chrome holds focus (selection/caret-restore.ts). One instance PER
1093
- // consumer: a card opened over an open search bar would otherwise overwrite the pre-search
1094
- // caret with its own, and closing the bar would land the user at the link.
1095
- const searchCaret = createCaretRestore(() => editorEl ?? null);
1096
- const linkCardCaret = createCaretRestore(() => editorEl ?? null);
660
+ // The find bar, the link card and a mode switch put their saved selection back through here.
661
+ const roundTripRestore = createRoundTripRestore({
662
+ selection: selectionState,
663
+ landing: caretLanding,
664
+ getBlockElByPath
665
+ });
1097
666
 
1098
- const searchReplace = createSearchReplace(editorActionsDeps, controller);
1099
- // Find stays live in reading mode; replace is an edit and no-ops at this seam.
1100
- const gatedSearchReplace: typeof searchReplace = {
1101
- replaceOne: (match, template) =>
1102
- canReplace ? searchReplace.replaceOne(match, template) : Promise.resolve(0),
1103
- replaceAll: (matches, template) =>
1104
- canReplace ? searchReplace.replaceAll(matches, template) : Promise.resolve(0)
667
+ // The document caret while the search bar or link card holds focus; one each, so a card
668
+ // opened over the search bar cannot overwrite the caret the bar restores.
669
+ const caretRestoreDeps: CaretRestoreDeps = {
670
+ getEditorEl: () => editorEl ?? null,
671
+ read: () => getSelection(),
672
+ restore: (selection) => roundTripRestore(selection, { reveal: 'into-view' })
1105
673
  };
674
+ const searchCaret = createCaretRestore(caretRestoreDeps);
675
+ const linkCardCaret = createCaretRestore(caretRestoreDeps);
676
+
677
+ const searchReplace = createSearchReplace(editorActionsDeps, controller);
678
+ // Find stays live in reading mode; replace is an edit and does nothing here.
1106
679
  const searchState = createSearchState({
1107
680
  getDoc,
1108
- getDocumentGeneration: () => documentGeneration,
681
+ getDocumentGeneration: documentSwap.generation,
1109
682
  decorations,
1110
- replace: gatedSearchReplace,
1111
- // Rides the one public seam, which also owns the reveal anchor (top-pinned by
1112
- // default, which is what search wants), so the band's async image-decode churn
1113
- // can't clamp the reveal off the match.
683
+ replace: searchReplace,
684
+ // The public scroll call holds the match in place, so a late image decode cannot move it.
1114
685
  reveal: (p) => rects.scrollTo(p),
1115
- onClose: searchCaret.restore
686
+ onClose: () => void searchCaret.restore()
1116
687
  });
1117
- // Lives here, not in SearchBar, so the root Ctrl+H and the bar's chevron share one
1118
- // source of truth.
688
+ // Lives here, not in SearchBar, so the root Ctrl+H and the bar's chevron share it.
1119
689
  let replaceExpanded = $state(false);
1120
690
 
1121
- const announceReorder = async (message: string) => {
1122
- // Clear first: Svelte skips the DOM write on a ===-equal assignment, which drops
1123
- // the second of two identical announcements.
1124
- reorderAnnouncement = '';
1125
- await tick();
1126
- reorderAnnouncement = message;
1127
- };
1128
- const reorder = createReorderAction(editorActionsDeps, controller, (to, total) => {
1129
- announceReorder(movedBlockToPosition(to + 1, total));
691
+ const reorder = createReorderAction(editorActionsDeps, controller);
692
+
693
+ // Cleared first like the reorder announcement, so two headings in a row announce twice.
694
+ let kindAnnouncement = $state('');
695
+ const kindCue = createKindCue({
696
+ getDoc,
697
+ getPresentationMode: reading.mode,
698
+ announce: async (label) => {
699
+ kindAnnouncement = '';
700
+ await tick();
701
+ kindAnnouncement = label;
702
+ }
1130
703
  });
1131
704
 
1132
705
  // ── Context provision ───────────────────────────────────────────────
1133
706
 
1134
- // One per instance, shared by every dispatch site's gates: the chord arm, a leaf's rebound
1135
- // chord and the `runCommand` door must reach the same cross-block road.
1136
707
  const crossBlockCommands = createCrossBlockCommands({
1137
708
  selection: selectionState,
1138
709
  getDoc,
1139
- getBlockElByPath,
1140
- revealPath,
1141
710
  controller,
1142
- getPresentationMode: () => effectiveMode,
1143
- grammar: registryView.grammar,
711
+ reading,
1144
712
  getContentVersion: contentVersion.read
1145
713
  });
1146
714
 
1147
- // The action bundles stay per-key so a container re-provides exactly what it
1148
- // overrides; HISTORY is G1.4's single-provider subject. The rest rides three facets.
715
+ // Every chord and `runCommand` dispatches against this one context, so all share one history,
716
+ // mode, overrides, range handling and error channel.
717
+ const commands: CommandDispatchContext = {
718
+ history,
719
+ pluginEditor: pluginEditorLookup,
720
+ activation: activePlugins,
721
+ getPresentationMode: reading.mode,
722
+ isCrossBlockRange: () => selectionState.isCrossBlock,
723
+ crossBlockCommands,
724
+ keybindingOverrides: () => overridesMap,
725
+ onCommandError: commandErrorSink,
726
+ reorder
727
+ };
728
+
729
+ // One context key per bundle so a container re-provides only what it overrides; history
730
+ // has a single provider (G1.4).
1149
731
  setContext(BLOCK_EDIT_KEY, blockEdit);
1150
732
  setContext(FOCUS_KEY, focus);
1151
733
  setContext(HISTORY_KEY, history);
@@ -1155,39 +737,41 @@
1155
737
  events,
1156
738
  decorations: decorationEngine,
1157
739
  selection: selectionState,
740
+ rangeCoverage: () => coverage,
1158
741
  search: searchState,
1159
- stickyColumn,
1160
- edgeAffinity,
1161
- pendingMarks,
1162
- revealAnchor,
742
+ caretMemory,
743
+ autoPairs,
744
+ scrollOwner,
1163
745
  widgetSelection,
746
+ selectedWidget,
1164
747
  linkCard,
748
+ inlineMenuCombobox: inlineMenu.comboboxFor,
1165
749
  controller,
750
+ caretLanding,
1166
751
  pasteCoordinator,
1167
752
  reorder,
1168
- reorderAnnounce: announceReorder,
1169
753
  registryView,
1170
754
  activePlugins,
1171
755
  rects,
1172
- crossBlockCommands
756
+ commands,
757
+ menuPresence,
758
+ kindCue
1173
759
  } satisfies EditorServices);
1174
760
 
1175
761
  setContext(EDITOR_POLICIES_KEY, {
1176
762
  resolveImageUrl: resolveImageUrlImpl,
1177
763
  resolveLinkUrl: resolveLinkUrlImpl,
1178
764
  imageLoadPolicy: () => imageLoadPolicy,
1179
- // Reading mode forces the affordance off through the prop's own funnel.
765
+ // Reading mode turns the drag handles off through the prop's own getter.
1180
766
  blockDragHandles: () => blockDragHandles && effectiveMode !== 'reading',
1181
- presentationMode: () => effectiveMode,
767
+ presentationMode: reading.mode,
1182
768
  theme: () => theme,
1183
769
  keybindingOverrides: () => overridesMap,
1184
- // An accessor, not the `onPasteImage,` shorthand: the shorthand captures the prop
1185
- // and svelte-check reports `state_referenced_locally`, which
1186
- // `svelte/no-unused-svelte-ignore` won't let us suppress.
770
+ // An accessor, not the `onPasteImage,` shorthand, which would capture the prop's value.
1187
771
  get onPasteImage() {
1188
772
  return onPasteImage;
1189
773
  },
1190
- // Accessors for the same reason as onPasteImage above.
774
+ // Accessors, as above.
1191
775
  get onRunCode() {
1192
776
  return onRunCode;
1193
777
  },
@@ -1199,26 +783,30 @@
1199
783
 
1200
784
  // ── Root DOM effects ────────────────────────────────────────────────
1201
785
 
1202
- // The pre half runs while the outgoing mode still owns the DOM, the post half after the
1203
- // mode's render key has rebuilt every block; the factory carries the caret across the gap.
786
+ // `beforeFlip` runs while the outgoing mode still owns the DOM, `afterFlip` once every block
787
+ // has re-rendered; the caret is carried across.
1204
788
  const modeFlip = createModeFlip({
1205
789
  get editorEl() {
1206
790
  return editorEl;
1207
791
  },
1208
792
  get mode() {
1209
- return effectiveMode;
793
+ return reading.mode();
1210
794
  },
1211
795
  selection: selectionState,
1212
796
  getSelection,
797
+ announceSelection: selectionAnnouncer.announce,
1213
798
  getBlockElByPath,
1214
799
  isHostChrome,
1215
- edgeAffinity,
1216
- // Built below; a flip runs post-init, so the getter reads past the TDZ.
1217
- get heightOracle() {
1218
- return heightOracle;
800
+ caretMemory,
801
+ // Built below; a mode switch runs after init, so the getter reads past the TDZ.
802
+ get layout() {
803
+ return layout;
1219
804
  },
1220
805
  events,
1221
- restoreCaret: (path, offset) => restoreThroughRevealRoad(caretAt(path, offset), 'mount')
806
+ restoreCaret: (path, offset) => roundTripRestore(caretAt(path, offset), { reveal: 'mount' }),
807
+ holdOutgoingMode: (mode) => {
808
+ outgoingMode = mode;
809
+ }
1222
810
  });
1223
811
  $effect.pre(() => {
1224
812
  const mode = effectiveMode;
@@ -1228,8 +816,63 @@
1228
816
  modeFlip.afterFlip(effectiveMode);
1229
817
  });
1230
818
 
1231
- // A theme flip invalidates no live edit, so it only has to be announced — for
1232
- // plugins that paint their own colors and can't see the flip through CSS.
819
+ // ── Root gestures ───────────────────────────────────────────────────
820
+
821
+ const rootGestures = createRootGestures({
822
+ getDoc,
823
+ selection: selectionState,
824
+ caretMemory,
825
+ getBlockElByPath,
826
+ getBlockComponent,
827
+ land: caretLanding.land,
828
+ getScrollHost,
829
+ getLifetime: () => lifetimeController.signal,
830
+ isHostChrome,
831
+ activateLink,
832
+ linkCard,
833
+ reading,
834
+ widgetSelection
835
+ });
836
+ $effect(() => {
837
+ if (!editorEl) return;
838
+ const root = editorEl;
839
+ return removeAll(
840
+ rootGestures.install(root),
841
+ installSelectionDrop({
842
+ editorRoot: root,
843
+ getDoc,
844
+ controller,
845
+ coordinator: pasteCoordinator,
846
+ reading,
847
+ activePlugins,
848
+ events,
849
+ setDropCaret: (rect) => (dropCaret = rect),
850
+ isReadOnly: () => effectiveMode === 'reading'
851
+ })
852
+ );
853
+ });
854
+
855
+ const rootMenus = createRootMenus({
856
+ get editorEl() {
857
+ return editorEl;
858
+ },
859
+ get mode() {
860
+ return reading.mode();
861
+ },
862
+ getDoc,
863
+ isHostChrome,
864
+ blockEdit,
865
+ replaceRaw: (index, raw) =>
866
+ replaceBlockRaw({ deps: editorActionsDeps, controller }, [index], raw),
867
+ placeCaretAtPoint,
868
+ insertMarkdown,
869
+ insertCatalogue: getInsertCatalogue,
870
+ activation: activePlugins,
871
+ reading,
872
+ setMenu: (menu) => (blockMenu = menu)
873
+ });
874
+
875
+ // Announced for plugins that paint their own colors and cannot see a theme change in CSS.
1233
876
  // svelte-ignore state_referenced_locally
1234
877
  let lastTheme = theme;
1235
878
  $effect(() => {
@@ -1239,9 +882,8 @@
1239
882
  events.emit('themeChange', next);
1240
883
  });
1241
884
 
1242
- // CSS keys on `data-cross-block` to hide the native caret and selection highlight
1243
- // while the overlay paints the cross-block range. Keyed on the OVERLAY's own predicate:
1244
- // a state it declines to paint must not also lose the caret, or the screen shows neither.
885
+ // Hides the native caret and highlight while the overlay paints; keyed on the overlay's own
886
+ // test, so a range it does not paint keeps the native one.
1245
887
  $effect(() => {
1246
888
  if (!editorEl) return;
1247
889
  if (selectionState.isCustomRendered) {
@@ -1263,6 +905,8 @@
1263
905
  editorRoot: editorEl,
1264
906
  getScrollHost,
1265
907
  moveReorderUnit: reorder.moveReorderUnit,
908
+ getDoc,
909
+ reading,
1266
910
  overlay: {
1267
911
  setGhost: (g) => (reorderGhost = g),
1268
912
  setLine: (l) => (reorderLine = l)
@@ -1277,7 +921,8 @@
1277
921
  return installSelectionChangeBridge({
1278
922
  root: editorEl,
1279
923
  isHostChrome,
1280
- emit: () => events.emit('selectionChange', getSelection())
924
+ announceIfMoved: selectionAnnouncer.announceIfMoved,
925
+ selection: selectionState
1281
926
  });
1282
927
  });
1283
928
 
@@ -1285,72 +930,53 @@
1285
930
  if (!editorEl) return;
1286
931
  return installEditorBlurAnnouncer({
1287
932
  root: editorEl,
1288
- emit: () => events.emit('selectionChange', getSelection())
933
+ announce: selectionAnnouncer.announce
1289
934
  });
1290
935
  });
1291
936
 
1292
937
  // ── Editor-root keydown routing ──────────────────────────────────────
1293
- //
1294
- // When the caret's block windows out, focus drops to <body> and the per-block keydown handlers
1295
- // go silent, so this editor-scope handler reuses the same cross-block composer with the root
1296
- // and the focus path standing in for `getEl` and `getMyPath`.
938
+ // Handles keys for a caret whose block unmounted, when focus has dropped to <body>.
1297
939
  const editorCrossBlock = createCrossBlockHandlers({
1298
940
  getEl: () => editorEl ?? null,
1299
941
  getMyPath: () => selectionState.focus?.path ?? [],
1300
- getIndex: () => selectionState.focus?.path?.[0] ?? 0,
1301
942
  selection: selectionState,
1302
943
  getDoc,
1303
944
  getBlockElByPath,
1304
- revealPath,
945
+ caretLanding,
946
+ revealPath: (path) => caretLanding.mount(path),
1305
947
  getEditorRoot: () => editorEl ?? null,
1306
948
  getScrollHost,
949
+ scrollOwner,
1307
950
  getEditorLifetime: () => lifetimeController.signal,
1308
- stickyColumn,
1309
- edgeAffinity,
951
+ caretMemory,
1310
952
  blockEdit,
1311
953
  controller,
1312
- history,
1313
- pluginEditor: pluginEditorLookup,
1314
- getPresentationMode: () => effectiveMode,
1315
- linkRef: linkRefView,
1316
- onCommandError: commandErrorSink,
1317
- crossBlockCommands,
954
+ reading,
955
+ commands,
1318
956
  pasteCoordinator,
1319
- getKeybindingOverrides: () => overridesMap,
1320
- grammar: registryView.grammar,
1321
957
  activePlugins,
1322
958
  events,
1323
- getCursorOffset: () => selectionState.focus?.offset ?? null,
959
+ selectedWidget,
1324
960
  afterReactivity: () => tick()
1325
961
  });
1326
962
 
1327
- // Document-level chords for the windowed-out caret, plus the search shortcuts. Every
1328
- // arm is contained to THIS instance: the listener sees every editor's keystrokes on
1329
- // the page, so an unguarded handler let one Ctrl+Z revert two editors.
963
+ // Every handler checks for this instance: the document listener sees every editor's keys,
964
+ // and one Ctrl+Z must not revert two editors.
1330
965
  const rootKeydown = createEditorRootKeydown({
1331
966
  get searchBarEnabled() {
1332
967
  return searchBar;
1333
968
  },
1334
- get mode() {
1335
- return effectiveMode;
1336
- },
1337
969
  get canReplace() {
1338
970
  return canReplace;
1339
971
  },
1340
- get keybindingOverrides() {
1341
- return overridesMap;
1342
- },
1343
972
  get isCrossBlock() {
1344
973
  return selectionState.isCrossBlock;
1345
974
  },
1346
975
  search: searchState,
1347
- history,
1348
- pluginEditor: pluginEditorLookup,
1349
- activation: activePlugins,
1350
- onCommandError: commandErrorSink,
976
+ commands,
1351
977
  crossBlock: editorCrossBlock,
1352
978
  isHostChrome,
1353
- saveSearchRange: searchCaret.save,
979
+ saveSearchCaret: searchCaret.saveCurrent,
1354
980
  setReplaceExpanded: (expanded) => (replaceExpanded = expanded)
1355
981
  });
1356
982
 
@@ -1363,21 +989,18 @@
1363
989
  });
1364
990
 
1365
991
  // ── Editor-root clipboard routing ────────────────────────────────────
1366
- //
1367
- // The keydown sibling's counterpart: a Ctrl+C/X/V that Chromium retargeted to <body> because
1368
- // the selection found no text position to park a caret in. Same containment — the arms claim
1369
- // only events landing on THIS root, or on the body with this instance holding the chord claim.
992
+ // Copy, cut and paste that Chromium retargets to <body> when the selection has no text position.
1370
993
  const rootClipboard = createEditorRootClipboard({
1371
994
  selection: selectionState,
1372
995
  getDoc,
1373
996
  crossBlock: editorCrossBlock,
1374
- // A live policy value, like the accessor the blocks' policies context hands out.
997
+ // An accessor, so the handler reads the live prop.
1375
998
  get onPasteImage() {
1376
999
  return onPasteImage;
1377
1000
  },
1378
1001
  events,
1379
1002
  getSelectedWidgetBlock: () => {
1380
- const selected = widgetSelection.getSelected();
1003
+ const selected = selectionState.widget;
1381
1004
  return selected ? getBlockComponent(selected.paragraphPath) : null;
1382
1005
  }
1383
1006
  });
@@ -1393,83 +1016,48 @@
1393
1016
  );
1394
1017
  });
1395
1018
 
1396
- // ── Height oracle ───────────────────────────────────────────────────
1019
+ // ── Height estimates ────────────────────────────────────────────────
1397
1020
 
1398
- // How far the host scaled the type off the size HEIGHT_ESTIMATES were calibrated
1399
- // at. Plain `let`, not `$state`: the oracle reads it inside `estimate()`, the
1400
- // feature's hottest path, and `widthVersion` is already the rebuild signal.
1401
- let typeScale = 1;
1402
-
1403
- // Only the font-relative terms scale: block chrome is absolute padding and an
1404
- // image's height is its own. Getters, so a scale change lands without rebuilding
1405
- // the oracle or dropping its measured heights.
1406
- const heightOracle = createHeightOracle({
1407
- get lineHeight() {
1408
- return HEIGHT_ESTIMATES.proseLineHeight * typeScale;
1409
- },
1410
- get codeLineHeight() {
1411
- return HEIGHT_ESTIMATES.codeLineHeight * typeScale;
1412
- },
1413
- get avgCharWidth() {
1414
- return HEIGHT_ESTIMATES.avgCharWidth * typeScale;
1415
- },
1416
- blockChrome: HEIGHT_ESTIMATES.blockChrome,
1417
- imageBlockMinHeight: HEIGHT_ESTIMATES.imageBlockMinHeight
1418
- });
1021
+ const layout = createLayoutState();
1022
+ const heightOracle = layout.heightOracle;
1419
1023
 
1420
1024
  // ── Resize invalidation ─────────────────────────────────────────────
1421
1025
 
1422
- // A WIDTH change re-wraps prose, staling every cached height, so the scopes rebuild
1423
- // off this counter; a height-only resize spares the measured cache.
1424
- let widthVersion = $state(0);
1026
+ // A zero-tall element, not the root: in host mode the root's height follows the blocks the
1027
+ // rebuild changes, and a watched box resized mid-delivery is a ResizeObserver loop error.
1425
1028
  $effect(() => {
1426
- if (!editorEl) return;
1427
- return installWidthWatcher(editorEl, () => {
1428
- heightOracle.dropMeasured();
1429
- widthVersion++;
1430
- });
1029
+ if (!widthProbeEl) return;
1030
+ return installWidthWatcher(widthProbeEl, layout.rebuildForNewGeometry);
1431
1031
  });
1432
1032
 
1433
- // The slice's own axis, watched on the RESOLVED port: the window's extent comes from the
1434
- // scrollport's height, so a height-only resize otherwise leaves the newly-exposed band as
1435
- // bare spacer until the next scroll. Its own counter, never `widthVersion` — that one
1436
- // drops every measured height, which a resize re-wrapping no prose has not earned.
1437
- let viewportHeightVersion = $state(0);
1033
+ // The scroll container's height sets how many blocks mount.
1438
1034
  $effect(() => {
1439
1035
  if (!editorEl) return;
1440
1036
  const target = getScrollHost();
1441
1037
  if (!target) return;
1442
- return installViewportHeightWatcher(target, () => viewportHeightVersion++);
1038
+ return installViewportHeightWatcher(target, layout.noteViewportHeight);
1443
1039
  });
1444
1040
 
1445
1041
  // ── Type scale ──────────────────────────────────────────────────────
1446
1042
 
1447
- // A font-size move puts the estimates off several-fold, which no other box in the root
1448
- // reports; the probe's scale rides the width signal, since a rebuild is what it needs.
1043
+ // A font-size change throws every height estimate off, and no other box reports it.
1449
1044
  $effect(() => {
1450
1045
  if (!typeScaleProbeEl) return;
1451
1046
  return installTypeScaleProbe(typeScaleProbeEl, {
1452
- getScale: () => typeScale,
1453
- onScale: (next) => {
1454
- typeScale = next;
1455
- heightOracle.dropMeasured();
1456
- widthVersion++;
1457
- }
1047
+ getScale: layout.typeScale,
1048
+ onScale: layout.setTypeScale
1458
1049
  });
1459
1050
  });
1460
1051
 
1461
- // ── Header slot compensation ────────────────────────────────────────
1052
+ // ── Header height compensation ──────────────────────────────────────
1462
1053
 
1463
1054
  $effect(() => {
1464
1055
  const el = headerEl;
1465
- if (!el || !editorEl) return;
1466
- const port = getScrollport();
1467
- if (!port) return;
1056
+ if (!el || !editorEl || !rootScroll) return;
1468
1057
  return installHeaderSlotCompensation({
1469
1058
  el,
1470
- port,
1471
- ownsScrollCorrection,
1472
- revealHoldsScroll: () => topWindowing.revealHoldsScroll()
1059
+ port: scrollOwner.port,
1060
+ compensate: rootScroll.compensate
1473
1061
  });
1474
1062
  });
1475
1063
 
@@ -1477,7 +1065,7 @@
1477
1065
 
1478
1066
  const focusAttribution = createFocusAttribution({
1479
1067
  get mode() {
1480
- return effectiveMode;
1068
+ return reading.mode();
1481
1069
  }
1482
1070
  });
1483
1071
  $effect(() => {
@@ -1491,49 +1079,46 @@
1491
1079
 
1492
1080
  // ── Top-level windowing ─────────────────────────────────────────────
1493
1081
 
1494
- // Assembled here, after the windowing signals it carries exist; the block
1495
- // components and the windowing hook below both read it back through getContext.
1082
+ const listTree = createListTree({
1083
+ getScrollTop: () => scrollOwner.port()?.scrollTop() ?? null,
1084
+ getFocusPath: focusAttribution.getFocusedPath
1085
+ });
1086
+
1087
+ // Set after the windowing signals it holds exist; the windowing hook below reads it back.
1496
1088
  setContext(EDITOR_DOC_KEY, {
1497
1089
  doc: getDoc,
1498
1090
  contentVersion: contentVersion.read,
1499
- linkRef: linkRefView,
1091
+ reading,
1500
1092
  pluginEditor: pluginEditorLookup,
1501
1093
  lifetime: lifetimeController.signal,
1502
1094
  editorRoot: () => editorEl ?? null,
1503
1095
  scrollHost: getScrollHost,
1504
- scrollport: getScrollport,
1096
+ scrollport: scrollOwner.port,
1505
1097
  blockElLookup: getBlockElByPath,
1506
1098
  focusedPath: focusAttribution.getFocusedPath,
1507
1099
  heightOracle,
1508
- correctsScroll: ownsScrollCorrection,
1509
- widthVersion: () => widthVersion,
1510
- viewportHeightVersion: () => viewportHeightVersion
1100
+ listTree,
1101
+ widthVersion: layout.widthVersion,
1102
+ viewportHeightVersion: layout.viewportHeightVersion
1511
1103
  } satisfies EditorDoc);
1512
1104
 
1513
- // getListEl is the inner .block-list wrapper, never editorEl (== scrollEl): it
1514
- // scrolls with content, so its top maps root scrollTop into local coordinates,
1515
- // where editorEl would collapse to 0.
1516
- const topWindowing = useContainerWindowing({
1517
- getIndex: () => 0, // ignored — root has no parent sink (reportSelfHeight is undefined)
1518
- getParentPath: () => [],
1105
+ // The inner `.block-list`, not `editorEl`: it scrolls with content, so its top maps scrollTop
1106
+ // into list coordinates.
1107
+ const { windowing: topWindowing, scroll: rootScroll } = useRootWindowing({
1519
1108
  getChildren: () => doc.children,
1520
1109
  getChildIds: () => blockIds,
1521
- getListEl: () => editorEl?.querySelector(':scope > .block-list') ?? null,
1522
- provideLeafChannel: true
1110
+ getListEl: () => editorEl?.querySelector(':scope > .block-list') ?? null
1523
1111
  });
1524
1112
 
1525
- // Plain `let`, not $state or $derived: the correction path asks this mid-measure, where
1526
- // evaluating the window derived would force the layout read the batched pass exists to
1527
- // avoid (VR-4). Nothing reactive reads it — the root's own attribute reads the derived,
1528
- // so the flag lags the attribute one flush at a watermark crossing (measured harmless).
1113
+ // Plain `let`: the scroll correction reads it mid-measure, where the derived would force a
1114
+ // layout read (VR-4). It lags one flush when windowing toggles.
1529
1115
  let rootWindowingActive = false;
1530
1116
  $effect(() => {
1531
1117
  rootWindowingActive = topWindowing.window.active;
1532
1118
  });
1533
1119
 
1534
- // Who holds the reader's place through a height mutation. Self mode always; host mode only
1535
- // while windowing runs, since below the budget the host's own native anchoring does it and
1536
- // both correcting would double-count. The `overflow-anchor` opt-out keys off the same fact.
1120
+ // Whether the editor corrects scroll on a height change; in host mode without windowing the
1121
+ // browser's scroll anchoring does it instead.
1537
1122
  function ownsScrollCorrection(): boolean {
1538
1123
  return !hostScroll || rootWindowingActive;
1539
1124
  }
@@ -1544,150 +1129,73 @@
1544
1129
  return serialize(doc);
1545
1130
  }
1546
1131
 
1547
- // The kind alone, never the node: a host reads the tree's shape without holding a handle into
1548
- // it. See `editor-props.ts` for the contract.
1132
+ // The kind alone, never the node, so a host holds no handle into the tree.
1549
1133
  export function getBlockKindAt(path: number[]): AnyBlockKind | null {
1550
1134
  return blockNodeAt(doc, path)?.kind ?? null;
1551
1135
  }
1552
1136
 
1553
- /**
1554
- * A frozen snapshot of the current selection, or null when nothing is focused.
1555
- * Path arrays are copies, so mutating the result does not affect internal state.
1556
- */
1137
+ /** A snapshot of the current selection with copied paths, or null when nothing is focused. */
1557
1138
  export function getSelection(): EditorSelection | null {
1558
1139
  return readCurrentSelection(selectionState, blockRefs);
1559
1140
  }
1560
1141
 
1561
- /**
1562
- * The one restore road: resolve + clamp, reveal, place. `reveal` picks which reveal. The two
1563
- * scrolling ones differ in whether the pin outlives the call — a navigation holds, a consumer
1564
- * restore hands the viewport back so a kept pin can't override the host's next scroll — and
1565
- * `mount` is the history swap's bare mount, which promises no block IN VIEW (overscan keeps
1566
- * blocks mounted past the fold) and in exchange writes no scrollport.
1567
- */
1568
- function restoreThroughRevealRoad(
1569
- selection: EditorSelection,
1570
- reveal: 'hold' | 'release' | 'mount'
1571
- ): Promise<SelectionRestoreOutcome> {
1572
- return restoreSelection(selection, {
1573
- getDoc,
1574
- selectionState,
1575
- getBlockElByPath,
1576
- revealTarget: async (path) =>
1577
- reveal === 'mount'
1578
- ? (await revealPath(path)) !== null
1579
- : rects.scrollTo(path, { block: 'nearest', hold: reveal === 'hold' })
1580
- });
1581
- }
1582
-
1583
1142
  function caretAt(path: number[], offset = 0): EditorSelection {
1584
1143
  return { anchor: { path, offset }, focus: { path, offset } };
1585
1144
  }
1586
1145
 
1587
- /** Land the caret at a raw offset through the shared restore road — the link card's return
1588
- * door after a commit, so the next keystroke (Ctrl+Z included) addresses the document. */
1589
- async function landCaretAtOffset(path: number[], offset: number): Promise<boolean> {
1590
- return (await restoreThroughRevealRoad(caretAt(path, offset), 'hold')) === 'applied';
1146
+ // Read off the placed selection, not the requested one: a caret aimed into a hidden body lands
1147
+ // on its title row.
1148
+ function focusInView(): boolean {
1149
+ const focus = getSelection()?.focus;
1150
+ return !!focus && scrollOwner.isInView(selectionState.cellLandingFor(focus).path);
1591
1151
  }
1592
1152
 
1593
- /**
1594
- * Restore a snapshot from {@link getSelection}, sharing the whole restore road with
1595
- * the undo swap and plugin navigation so the three cannot diverge. True iff the
1596
- * selection was placed AND its focus block is in view; a later programmatic reveal
1597
- * mid-settle owns the viewport and makes this false, a user gesture does not.
1598
- */
1599
- export async function setSelection(selection: EditorSelection): Promise<boolean> {
1600
- return (await restoreThroughRevealRoad(selection, 'release')) === 'applied';
1153
+ async function navigateCaret(path: number[], offset: number): Promise<boolean> {
1154
+ const outcome = await caretLanding.navigate({ path: docPathFrom(path), offset });
1155
+ return outcome === 'placed' && focusInView();
1601
1156
  }
1602
1157
 
1603
- // The dead-space click's own landing walk, minus the press/target discrimination a host
1604
- // caller has already done for itself. See `editor-props.ts` for the contract.
1605
- export function placeCaretAtPoint(x: number, y: number): boolean {
1606
- return editorEl ? deadSpaceCaret.placeAtPoint(editorEl, x, y) : false;
1607
- }
1608
-
1609
- /**
1610
- * The block path behind `document.activeElement`, for the public doors that address the
1611
- * focused surface. A gap caret declines: its proxy is not a block, and a NESTED gap's proxy
1612
- * sits inside its container's host, which must not receive what was aimed at the gap.
1613
- */
1614
- function blurEditingSurface(root: HTMLElement): void {
1615
- const active = document.activeElement;
1616
- if (active instanceof HTMLElement && root.contains(active) && active !== root) active.blur();
1617
- }
1618
-
1619
- function focusedSurfacePath(): number[] | null {
1620
- if (selectionState.gapCaret) return null;
1621
- const active = document.activeElement;
1622
- if (!(active instanceof HTMLElement) || !editorEl?.contains(active)) return null;
1623
- return findSurfacePathForElement(active);
1158
+ /** True only if the selection was placed and its focus block is in view; a programmatic
1159
+ * scroll before it finishes makes it false, a user scroll does not. */
1160
+ export async function setSelection(selection: EditorSelection): Promise<boolean> {
1161
+ return (await caretLanding.restore(selection)) === 'applied' && focusInView();
1624
1162
  }
1625
1163
 
1626
- // Routed to the focused SURFACE, the way a paste event is: everything the pipeline owes
1627
- // (transforms, delete-first, one undo entry, focus) lives below that seam, not here. See
1628
- // `editor-props.ts` for the contract.
1629
- export function insertMarkdown(md: string): boolean {
1630
- const path = focusedSurfacePath();
1631
- if (!path) return false;
1632
- return getBlockComponent(path)?.insertMarkdown?.(md) ?? false;
1164
+ // The caret search a click on empty space runs, minus the event checks a host has done.
1165
+ export function placeCaretAtPoint(x: number, y: number): boolean {
1166
+ return editorEl ? rootGestures.placeCaretAtPoint(editorEl, x, y) : false;
1633
1167
  }
1634
1168
 
1635
- const commandDispatchContext: CommandDispatchContext = {
1636
- history,
1637
- pluginEditor: pluginEditorLookup,
1638
- activation: activePlugins,
1639
- getPresentationMode: () => effectiveMode,
1640
- isCrossBlockRange: () => selectionState.isCrossBlock,
1641
- crossBlockCommands: crossBlockCommands
1642
- };
1169
+ // The entry points below only find the focused editable; the rules live in the editable.
1170
+ const focusedSurface = createFocusedSurface({
1171
+ get editorEl() {
1172
+ return editorEl;
1173
+ },
1174
+ selection: selectionState,
1175
+ getDoc,
1176
+ getBlockComponent,
1177
+ isReading: () => effectiveMode === 'reading',
1178
+ insertParagraph: (boundary, text) => blockEdit.insertParagraph(boundary, text),
1179
+ undoStep: (path, offset, run) => controller.undoStep({ path: docPathFrom(path), offset }, run),
1180
+ contentVersion: contentVersion.read
1181
+ });
1643
1182
 
1644
- // A gap caret focuses a proxy, not a block, so no block-local command has a surface to run
1645
- // on; global ones still reach the seam, exactly as the gap caret's own chord proxy does.
1646
- function focusedCommandTarget(): KindCommandTarget | null {
1647
- const path = focusedSurfacePath();
1648
- if (!path) return null;
1649
- const component = getBlockComponent(path);
1650
- const node = blockNodeAt(doc, path);
1651
- if (!component?.runCommand || !node) return null;
1652
- return {
1653
- kind: node.kind,
1654
- runCommand: (id, arg) => component.runCommand!(id, arg),
1655
- isCommandActive: component.isCommandActive
1656
- ? (id) => component.isCommandActive!(id)
1657
- : undefined
1658
- };
1183
+ export function insertMarkdown(md: string, options?: InsertMarkdownOptions): Promise<boolean> {
1184
+ return focusedSurface.insertMarkdown(md, options);
1659
1185
  }
1660
1186
 
1661
- // The door only resolves the focused surface; every rule (the reading gate, the cross-block
1662
- // range decline, the arms themselves) lives below the seam. See `editor-props.ts`.
1663
1187
  export function runCommand(commandId: string, arg?: unknown): boolean {
1664
- return runCommandById(
1665
- commandId as AnyCommandId,
1666
- arg,
1667
- focusedCommandTarget(),
1668
- commandDispatchContext,
1669
- commandErrorSink
1670
- );
1188
+ return runCommandById(commandId as AnyCommandId, arg, focusedSurface.commandTarget(), commands);
1671
1189
  }
1672
1190
 
1673
- // The same seam the door dispatches through, so what a host greys out and what a click declines
1674
- // cannot drift. See `editor-props.ts` for the contract.
1191
+ // Asks through `runCommand`'s own dispatch, so a host greys out exactly what a click refuses.
1675
1192
  export function canRunCommand(commandId: string): boolean {
1676
- return canRunCommandById(
1677
- commandId as AnyCommandId,
1678
- focusedCommandTarget(),
1679
- commandDispatchContext
1680
- );
1193
+ return canRunCommandById(commandId as AnyCommandId, focusedSurface.commandTarget(), commands);
1681
1194
  }
1682
1195
 
1683
- // State, not admissibility: the focused surface reports its own toggle-state, so a toolbar's
1684
- // pressed paint reads the same bytes the toggle would rewrite. See `editor-props.ts`.
1196
+ // The focused editable reports its toggle state from the bytes the toggle would rewrite.
1685
1197
  export function isCommandActive(commandId: string): boolean {
1686
- return isCommandActiveById(
1687
- commandId as AnyCommandId,
1688
- focusedCommandTarget(),
1689
- commandDispatchContext
1690
- );
1198
+ return isCommandActiveById(commandId as AnyCommandId, focusedSurface.commandTarget(), commands);
1691
1199
  }
1692
1200
 
1693
1201
  export function getEvents(): EditorEvents {
@@ -1702,12 +1210,19 @@
1702
1210
  return decorations;
1703
1211
  }
1704
1212
 
1213
+ export function getInlineMenus(): InlineMenuRegistry {
1214
+ return inlineMenus;
1215
+ }
1216
+
1217
+ export function getInsertCatalogue(): readonly InsertEntry[] {
1218
+ return insertCatalogue(activePlugins);
1219
+ }
1220
+
1705
1221
  export function getRects(): EditorRects {
1706
1222
  return rects;
1707
1223
  }
1708
1224
 
1709
- // Recomposed per call rather than derived: the kind and plugin registries are
1710
- // process-global and mutate outside this component's reactive graph.
1225
+ // Recomposed per call: the registries are process-global and not reactive.
1711
1226
  export function reservedChords(): ReadonlySet<string> {
1712
1227
  return collectReservedChords({
1713
1228
  searchBar,
@@ -1720,34 +1235,10 @@
1720
1235
  return chordIsClaimed(event, reservedChords());
1721
1236
  }
1722
1237
 
1723
- // Reads the public snapshot, so it covers single-block carets the cross-block
1724
- // SelectionState never holds.
1725
- function selectionSummary(): string {
1726
- const sel = getSelection();
1727
- if (!sel) return '(no selection)';
1728
- const fmt = (p: { path: number[]; offset: number }) => `[${p.path.join(',')}]@${p.offset}`;
1729
- return `anchor=${fmt(sel.anchor)} focus=${fmt(sel.focus)}`;
1730
- }
1238
+ const diagnostics = createEditorDiagnostics({ getSelection, getSource, operationsLog });
1731
1239
 
1732
1240
  export function getDiagnostics(): EditorDiagnostics {
1733
- return {
1734
- enableTrace: enableInteractionTrace,
1735
- disableTrace: disableInteractionTrace,
1736
- isTraceEnabled: isInteractionTraceEnabled,
1737
- traceSnapshot: interactionTraceSnapshot,
1738
- serializeDiagnostics: (opts) => {
1739
- const includeSource = opts?.includeSource ?? false;
1740
- return buildDiagnosticsReport({
1741
- timestamp: new Date().toISOString(),
1742
- trace: dumpInteractionTrace(interactionTraceSnapshot()),
1743
- opsLog: dumpOperationsLog(operationsLog),
1744
- selection: selectionSummary(),
1745
- // Serialize only when opted in — the report stays document-free by default.
1746
- source: includeSource ? getSource() : '',
1747
- includeSource
1748
- });
1749
- }
1750
- };
1241
+ return diagnostics;
1751
1242
  }
1752
1243
 
1753
1244
  // Compile-time conformance: the published handle can't drift from the exports.
@@ -1764,64 +1255,37 @@
1764
1255
  getEvents,
1765
1256
  getSearch,
1766
1257
  getDecorations,
1258
+ getInlineMenus,
1259
+ getInsertCatalogue,
1767
1260
  getRects,
1768
1261
  getDiagnostics,
1769
1262
  reservedChords,
1770
1263
  claimsChord
1771
1264
  } satisfies EditorInstance);
1772
1265
 
1773
- // ── Test-only surface ───────────────────────────────────────────────
1774
-
1775
- function getOperationsLog() {
1776
- return operationsLog;
1777
- }
1778
-
1779
- function getUndoStack() {
1780
- return undoManager.getStacks();
1781
- }
1266
+ // ── Test-only hooks ─────────────────────────────────────────────────
1782
1267
 
1783
- /** The live CST Document; treat it as read-only, since mutating it bypasses the
1784
- * undo pipeline. */
1785
- function getDocument() {
1786
- return doc;
1787
- }
1788
-
1789
- export const __test = {
1790
- getDocument,
1791
- // The swap door's only oracle: every other door is reachable headlessly, but the
1792
- // `source` prop's reset lives in this component.
1268
+ export const __test: EditorTestSurface = {
1269
+ getDocument: () => doc,
1270
+ getGrammar: () => registryView.grammar,
1793
1271
  getContentVersion: contentVersion.read,
1794
1272
  getBlockComponent,
1795
- getUndoStack,
1796
- getOperationsLog,
1797
- // Specs need the state, not the `data-cross-block` mirror: a deferred $effect
1798
- // writes the attribute, and an intra-table rectangle keeps one path on both
1799
- // endpoints, so neither the DOM nor getSelection() answers this reliably.
1273
+ getUndoStack: () => undoManager.getStacks(),
1274
+ getOperationsLog: () => operationsLog,
1800
1275
  isCrossBlockActive: () => selectionState.isCrossBlock,
1801
- // The gap caret has no public selection shape and no paint yet, so the state is the
1802
- // only place arrival can be observed.
1803
1276
  getGapCaret: () => selectionState.gapCaret,
1804
- // The engine, not the `addSource`-only public registry: the derived per-path
1805
- // buckets are the only oracle for a stale bucket, since jsdom measures every
1806
- // range at zero width and no overlay ever paints there.
1807
1277
  getDecorationEngine: () => decorationEngine,
1808
- // The measured-height cache is root-constructed and handed down through context,
1809
- // so its lifetime against a document swap has no headless seam either.
1810
1278
  getHeightOracle: () => heightOracle,
1811
- // The one signal a windowing scope rebuilds off when no id moved, so it is the
1812
- // oracle for a mode flip having asked for the rebuild it needs.
1813
- getWidthVersion: () => widthVersion,
1814
- // Constructs the detached-slot artifact the windowed each-block's cleanup can
1815
- // leave behind (see `isSlotDetached`); e2e-only.
1279
+ getBlockIds: () => blockIds,
1280
+ getMeasuredIds: () => heightOracle.measuredIds(),
1281
+ getListTree: () => listTree,
1282
+ getWidthVersion: layout.widthVersion,
1816
1283
  setBlockRefSlot: blockRefSlots.set
1817
1284
  };
1818
1285
  </script>
1819
1286
 
1820
- <!-- tabindex="-1": focusable so a windowed-out block hands focus here rather than to
1821
- <body>, but not tab-reachable. Non-editable, so focusing it creates no native
1822
- selection for the selectionchange bridge to collapse. -->
1823
- <!-- data-presentation is ABSENT in source mode on purpose: the default path's DOM
1824
- stays byte-identical, and reading-mode CSS keys on the attribute's presence. -->
1287
+ <!-- tabindex="-1": an unmounted block hands focus here rather than to <body>, and the root
1288
+ stays out of the tab order. -->
1825
1289
  <div
1826
1290
  class="editor"
1827
1291
  data-editor-theme={theme}
@@ -1832,12 +1296,11 @@
1832
1296
  tabindex="-1"
1833
1297
  role="group"
1834
1298
  aria-label={EDITOR_LABEL}
1835
- oncontextmenu={onRootContextMenu}
1299
+ oncontextmenu={rootMenus.onRootContextMenu}
1836
1300
  >
1837
1301
  {#if searchBar}
1838
- <!-- Zero-height sticky anchor, so the bar doesn't scroll away with content. Portaled
1839
- out, it drops that positioning (the consumer's element is the box) and carries the
1840
- editor's own theme scope, since custom properties resolve by DOM ancestry. -->
1302
+ <!-- A zero-height sticky anchor; portaled out, it takes the theme class instead, since
1303
+ custom properties resolve by DOM ancestry. -->
1841
1304
  <div
1842
1305
  class:search-anchor={!searchBarAnchor}
1843
1306
  class:aragonite-editor-theme={!!searchBarAnchor && inThemedScope}
@@ -1850,12 +1313,12 @@
1850
1313
  />
1851
1314
  </div>
1852
1315
  {/if}
1853
- <!-- One `em` tall and out of flow: its box IS the root's computed font size, which
1854
- no other box reports, and windowing's activation decision needs that scale. -->
1316
+ <!-- One `em` tall and out of flow, so its box reports the root's font size. -->
1855
1317
  <div class="type-scale-probe" bind:this={typeScaleProbeEl} aria-hidden="true"></div>
1318
+ <!-- The root's padding box wide and zero tall, so its box reports the root's width only. -->
1319
+ <div class="width-probe" bind:this={widthProbeEl} aria-hidden="true"></div>
1856
1320
  {#if header}
1857
- <!-- A SIBLING of the block list, never a wrapper: the windowing scope resolves
1858
- its list as a direct child of this root. -->
1321
+ <!-- A sibling of the block list: windowing needs the list as a direct child of the root. -->
1859
1322
  <div class="editor-header" bind:this={headerEl}>{@render header()}</div>
1860
1323
  {/if}
1861
1324
  <BlockList
@@ -1866,13 +1329,8 @@
1866
1329
  window={topWindowing.window}
1867
1330
  reorderable={true}
1868
1331
  />
1869
- <!-- A sibling of the list like the header: the windowing scope wants the list bare. -->
1870
- <TailInsert
1871
- {blockEdit}
1872
- childCount={doc.children.length}
1873
- readOnly={effectiveMode === 'reading'}
1874
- onPlus={(button) => void onTailPlus(button)}
1875
- />
1332
+ <!-- A sibling of the block list, like the header. -->
1333
+ <TailInsert {blockEdit} childCount={doc.children.length} readOnly={effectiveMode === 'reading'} />
1876
1334
  {#if blockMenu}
1877
1335
  <BlockMenu
1878
1336
  x={blockMenu.x}
@@ -1882,6 +1340,7 @@
1882
1340
  label={blockMenu.label}
1883
1341
  onPick={blockMenu.pick}
1884
1342
  onClose={() => (blockMenu = null)}
1343
+ {menuPresence}
1885
1344
  />
1886
1345
  {/if}
1887
1346
  {#if selectionToolbar && effectiveMode !== 'reading'}
@@ -1900,37 +1359,51 @@
1900
1359
  {/if}
1901
1360
  <ImageOverlayHost
1902
1361
  {widgetSelection}
1903
- {controller}
1362
+ {inlineRange}
1904
1363
  {events}
1905
1364
  {getDoc}
1365
+ getContentVersion={contentVersion.read}
1906
1366
  getEditorEl={() => editorEl ?? null}
1907
1367
  getSelectionIsCustomRendered={() => selectionState.isCustomRendered}
1908
- getPresentationMode={() => effectiveMode}
1909
- grammar={registryView.grammar}
1910
1368
  lifetime={lifetimeController.signal}
1369
+ {menuPresence}
1911
1370
  />
1912
1371
  <LinkCardHost
1913
1372
  card={linkCard}
1914
- {controller}
1373
+ {inlineRange}
1915
1374
  {events}
1916
1375
  {getDoc}
1917
1376
  getEditorEl={() => editorEl ?? null}
1918
1377
  measureRange={rects.rangeRects}
1919
- landCaret={landCaretAtOffset}
1920
1378
  {activateLink}
1921
1379
  resolveLinkUrl={resolveLinkUrlImpl}
1922
1380
  caretRestore={linkCardCaret}
1923
- linkRef={linkRefView}
1924
- grammar={registryView.grammar}
1381
+ {reading}
1382
+ {menuPresence}
1383
+ {commands}
1384
+ />
1385
+ <InlineMenuHost
1386
+ menu={inlineMenu}
1387
+ {events}
1388
+ {menuPresence}
1389
+ getEditorEl={() => editorEl ?? null}
1390
+ measureRange={rects.rangeRects}
1925
1391
  />
1926
1392
  <div class="editor-sr-live" role="status" aria-live="polite">{selectionDescription}</div>
1927
- <div class="editor-sr-live-reorder" role="status" aria-live="polite">{reorderAnnouncement}</div>
1393
+ <div class="editor-sr-live-reorder" role="status" aria-live="polite">{editAnnouncement}</div>
1394
+ <div class="editor-sr-live-kind" role="status" aria-live="polite">{kindAnnouncement}</div>
1928
1395
  {#if reorderLine}
1929
1396
  <div
1930
1397
  class="reorder-line"
1931
1398
  style="left:{reorderLine.left}px;top:{reorderLine.top}px;width:{reorderLine.width}px"
1932
1399
  ></div>
1933
1400
  {/if}
1401
+ {#if dropCaret}
1402
+ <div
1403
+ class="drop-caret"
1404
+ style="left:{dropCaret.left}px;top:{dropCaret.top}px;height:{dropCaret.height}px"
1405
+ ></div>
1406
+ {/if}
1934
1407
  {#if reorderGhost}
1935
1408
  <div class="reorder-ghost" style="left:{reorderGhost.clientX}px;top:{reorderGhost.clientY}px">
1936
1409
  {reorderGhost.label}
@@ -1942,35 +1415,27 @@
1942
1415
  .editor {
1943
1416
  width: 100%;
1944
1417
  flex: 1;
1945
- /* Wider on the left: the drag grip lives in that gutter, 1.25rem out from the content,
1946
- and the rest of this padding is what keeps it off the scroll container's border. */
1418
+ /* Wider on the left to hold the drag handle's gutter. */
1947
1419
  padding: 1rem 1rem 1rem 1.5rem;
1948
1420
  font-family: var(--font-editor, ui-monospace, monospace);
1949
- /* The type-scale root: every construct sizes in `em` off this, so one
1950
- declaration scales the whole surface. */
1421
+ /* Every construct sizes in `em` off this. */
1951
1422
  font-size: var(--editor-font-size, 1rem);
1952
1423
  line-height: 1.6;
1953
1424
  /* Inherit rather than assume a dark host: an unthemed page keeps its own text color. */
1954
1425
  color: var(--color-text-primary, currentColor);
1955
1426
  min-height: 200px;
1956
1427
  overflow-y: auto;
1957
- /* The editor corrects the scroll anchor by hand (list-windowing's
1958
- correctAnchor); native anchoring independently rewrites scrollTop and
1959
- double-corrects. Do NOT restore `overflow-anchor` (VR-2). */
1428
+ /* The editor corrects scroll by hand; browser anchoring would correct twice (VR-2). */
1960
1429
  overflow-anchor: none;
1961
1430
  scrollbar-width: thin;
1962
1431
  scrollbar-color: var(--color-border, #3e3e3b) transparent;
1963
- /* No box of its own. A document is the page's content, not a widget sitting on it, and
1964
- an outline around the whole editor reads as a form field the moment the host gives it
1965
- a column. A host that wants the frame draws it on its own container. */
1966
- /* Containing block for the image overlay portal. */
1432
+ /* Containing block for the image overlay portal, and for `.width-probe`, whose width is the
1433
+ root's only while this holds. */
1967
1434
  position: relative;
1968
1435
  }
1969
1436
 
1970
- /* Embedded flow mode: an ancestor owns the scroll, so the root drops its scrollport and the
1971
- standalone-widget chrome that would box every entry of a journal. Below the windowing
1972
- budget nothing corrects by hand, so native anchoring is restored — `none` would strip the
1973
- subtree from the HOST's anchor candidates and hold nothing in its place. */
1437
+ /* Host scroll: an ancestor scrolls, and without windowing the browser's anchoring holds the
1438
+ position, so it is restored. */
1974
1439
  .editor[data-scroll-mode='host'] {
1975
1440
  overflow-y: visible;
1976
1441
  overflow-anchor: auto;
@@ -1980,27 +1445,32 @@
1980
1445
  padding: 0;
1981
1446
  }
1982
1447
 
1983
- /* The stated trade of windowing under host scroll: native anchoring and the manual
1984
- correction cannot coexist, so an active editor withdraws its own subtree from the host's
1985
- anchor candidates and holds the line itself (VR-2). The host's scroller is untouched. */
1448
+ /* With windowing on, the editor corrects scroll itself, so its subtree leaves the host's
1449
+ anchor candidates (VR-2). */
1986
1450
  .editor[data-scroll-mode='host'][data-windowing='active'] {
1987
1451
  overflow-anchor: none;
1988
1452
  }
1989
1453
 
1990
- /* Absolute and zero-width so it takes no part in layout; never `display: none`,
1991
- which stops a ResizeObserver reporting. */
1992
- .type-scale-probe {
1454
+ /* Out of layout, but never `display: none`, which stops a ResizeObserver reporting. */
1455
+ .type-scale-probe,
1456
+ .width-probe {
1993
1457
  position: absolute;
1994
1458
  top: 0;
1995
1459
  left: 0;
1996
- width: 0;
1997
- height: 1em;
1998
1460
  visibility: hidden;
1999
1461
  pointer-events: none;
2000
1462
  }
2001
1463
 
2002
- /* Sticks to the scrollport top reserving no space; the bar positions absolutely
2003
- against it and stays put as the editor scrolls. */
1464
+ .type-scale-probe {
1465
+ width: 0;
1466
+ height: 1em;
1467
+ }
1468
+
1469
+ .width-probe {
1470
+ right: 0;
1471
+ height: 0;
1472
+ }
1473
+
2004
1474
  .search-anchor {
2005
1475
  position: sticky;
2006
1476
  top: 0;
@@ -2008,8 +1478,7 @@
2008
1478
  z-index: 5;
2009
1479
  }
2010
1480
 
2011
- /* Sticky resolves against the nearest SCROLLPORT, which in flow mode is the host's,
2012
- floating the bar over unrelated page content; absolute re-homes it to the root. */
1481
+ /* Sticky would resolve against the host's scroller and float over unrelated page content. */
2013
1482
  .editor[data-scroll-mode='host'] .search-anchor {
2014
1483
  position: absolute;
2015
1484
  top: 0;
@@ -2035,7 +1504,8 @@
2035
1504
  }
2036
1505
 
2037
1506
  .editor-sr-live,
2038
- .editor-sr-live-reorder {
1507
+ .editor-sr-live-reorder,
1508
+ .editor-sr-live-kind {
2039
1509
  position: absolute;
2040
1510
  width: 1px;
2041
1511
  height: 1px;
@@ -2047,17 +1517,15 @@
2047
1517
  border: 0;
2048
1518
  }
2049
1519
 
2050
- /* A nested drag reorders only within its container, so the container takes a faint
2051
- transient wash — deliberately not an outline (a document should feel like a
2052
- document, not a pile of blocks). Applied by editor-actions/reorder-drag.ts. */
1520
+ /* A nested drag stays inside its container, so the container takes a faint wash, not an
1521
+ outline. */
2053
1522
  :global(.reorder-scope) {
2054
1523
  background: var(--reorder-scope-bg, rgba(100, 150, 255, 0.14));
2055
1524
  border-radius: 4px;
2056
1525
  transition: background-color 0.12s ease;
2057
1526
  }
2058
1527
 
2059
- /* Viewport-fixed, since the rects come from client coords; pointer-events:none so
2060
- they never intercept the drag's own pointer stream. */
1528
+ /* Fixed, since the rects are client coordinates. */
2061
1529
  .reorder-line {
2062
1530
  position: fixed;
2063
1531
  height: 2px;
@@ -2067,6 +1535,15 @@
2067
1535
  z-index: 20;
2068
1536
  }
2069
1537
 
1538
+ /* Replaces the browser's drop caret, which goes with the drop the editor cancels. */
1539
+ .drop-caret {
1540
+ position: fixed;
1541
+ width: 1.5px;
1542
+ background: var(--color-text-primary, currentColor);
1543
+ pointer-events: none;
1544
+ z-index: 20;
1545
+ }
1546
+
2070
1547
  .reorder-ghost {
2071
1548
  position: fixed;
2072
1549
  transform: translate(0.75rem, 0.5rem);