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