@voithos-labs/aragonite 0.10.2 → 0.10.4

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 (300) hide show
  1. package/README.md +6 -21
  2. package/THIRD-PARTY-NOTICES.md +25 -0
  3. package/dist/a11y-strings.d.ts +19 -0
  4. package/dist/a11y-strings.js +19 -0
  5. package/dist/action-contracts.d.ts +7 -1
  6. package/dist/ambient/ambient-dom.js +5 -1
  7. package/dist/block-component.d.ts +14 -0
  8. package/dist/components/BlockDragHandle.svelte +37 -24
  9. package/dist/components/BlockHost.svelte +18 -11
  10. package/dist/components/Editor.svelte +434 -198
  11. package/dist/components/Editor.svelte.d.ts +1 -1
  12. package/dist/components/SelectionOverlay.svelte +25 -17
  13. package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
  14. package/dist/components/TailInsert.svelte +107 -0
  15. package/dist/components/TailInsert.svelte.d.ts +17 -0
  16. package/dist/components/block-content-selector.d.ts +6 -2
  17. package/dist/components/block-content-selector.js +6 -2
  18. package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
  19. package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
  20. package/dist/components/blocks/code/CodeBlock.svelte +211 -30
  21. package/dist/components/blocks/code/CodeBlockRail.svelte +704 -0
  22. package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
  23. package/dist/components/blocks/code/code-bootstrap.js +16 -0
  24. package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
  25. package/dist/components/blocks/code/code-context-actions.js +24 -0
  26. package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
  27. package/dist/components/blocks/code/code-fence-exit.js +26 -0
  28. package/dist/components/blocks/code/code-languages.d.ts +6 -0
  29. package/dist/components/blocks/code/code-languages.js +22 -3
  30. package/dist/components/blocks/code/code-renderer.js +11 -0
  31. package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
  32. package/dist/components/blocks/editable-leaf.d.ts +37 -6
  33. package/dist/components/blocks/editable-leaf.js +248 -30
  34. package/dist/components/blocks/editable-surface.d.ts +9 -0
  35. package/dist/components/blocks/editable-surface.js +22 -3
  36. package/dist/components/blocks/list/ListBlock.svelte +1 -0
  37. package/dist/components/blocks/list/ListItemBlock.svelte +14 -5
  38. package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
  39. package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
  40. package/dist/components/blocks/list/task-checkbox.js +11 -2
  41. package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
  42. package/dist/components/blocks/surface-wiring.svelte.js +8 -1
  43. package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
  44. package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
  45. package/dist/components/blocks/table/TableBlock.svelte +205 -186
  46. package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
  47. package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
  48. package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
  49. package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
  50. package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
  51. package/dist/components/blocks/table/cell-clipboard.js +34 -1
  52. package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
  53. package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
  54. package/dist/components/blocks/table/table-cell-paste.js +2 -1
  55. package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
  56. package/dist/components/blocks/table/table-menu-model.js +36 -17
  57. package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
  58. package/dist/components/blocks/text/click-snap-guard.d.ts +3 -0
  59. package/dist/components/blocks/text/click-snap-guard.js +8 -0
  60. package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
  61. package/dist/components/blocks/text/delimiter-autopair.js +216 -0
  62. package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
  63. package/dist/components/blocks/text/edge-policy-dispatch.js +89 -20
  64. package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
  65. package/dist/components/blocks/text/live-selection-edit.js +37 -7
  66. package/dist/components/blocks/text/text-clipboard.js +2 -2
  67. package/dist/components/blocks/text/text-keydown.d.ts +7 -1
  68. package/dist/components/blocks/text/text-keydown.js +11 -1
  69. package/dist/components/blocks/text/text-render.d.ts +1 -1
  70. package/dist/components/blocks/text/text-render.js +3 -1
  71. package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
  72. package/dist/components/blocks/text/widget-interaction.js +181 -37
  73. package/dist/components/drag-handle.d.ts +41 -0
  74. package/dist/components/drag-handle.js +134 -0
  75. package/dist/components/editor-root-focus.d.ts +19 -0
  76. package/dist/components/editor-root-focus.js +67 -0
  77. package/dist/components/editor-root-geometry.d.ts +39 -0
  78. package/dist/components/editor-root-geometry.js +91 -0
  79. package/dist/components/editor-root-keydown.d.ts +1 -1
  80. package/dist/components/editor-root-keydown.js +12 -3
  81. package/dist/components/editor-root-listeners.d.ts +3 -6
  82. package/dist/components/editor-root-listeners.js +3 -24
  83. package/dist/components/editor-root-mode-flip.d.ts +36 -0
  84. package/dist/components/editor-root-mode-flip.js +92 -0
  85. package/dist/components/image/ImageOverlayHost.svelte +14 -6
  86. package/dist/components/image/ImageProperties.svelte +512 -70
  87. package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
  88. package/dist/components/image/ImageResizeHandles.svelte +67 -34
  89. package/dist/components/image/image-crop.d.ts +39 -0
  90. package/dist/components/image/image-crop.js +74 -0
  91. package/dist/components/image/image-edit-commit.d.ts +1 -0
  92. package/dist/components/image/image-edit-commit.js +21 -5
  93. package/dist/components/image/image-source-bytes.js +10 -3
  94. package/dist/components/image/image-widget-editing.js +1 -0
  95. package/dist/components/image/widget-dom.js +5 -1
  96. package/dist/components/link-card/link-card-commit.js +1 -1
  97. package/dist/components/lrd-map-gate.js +1 -1
  98. package/dist/components/menu/BlockMenu.svelte +315 -0
  99. package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
  100. package/dist/components/menu/MenuIcon.svelte +153 -0
  101. package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
  102. package/dist/components/menu/SelectionToolbar.svelte +385 -0
  103. package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -0
  104. package/dist/components/menu/clipboard-actions.d.ts +12 -0
  105. package/dist/components/menu/clipboard-actions.js +42 -0
  106. package/dist/components/menu/default-context-actions.d.ts +15 -0
  107. package/dist/components/menu/default-context-actions.js +76 -0
  108. package/dist/components/menu/flyout-placement.d.ts +6 -0
  109. package/dist/components/menu/flyout-placement.js +25 -0
  110. package/dist/core/inline/format-toggle.d.ts +12 -4
  111. package/dist/core/inline/format-toggle.js +94 -40
  112. package/dist/core/inline/image-dimensions.d.ts +3 -0
  113. package/dist/core/inline/image-dimensions.js +46 -10
  114. package/dist/core/inline/inline-widgets.d.ts +19 -0
  115. package/dist/core/inline/inline-widgets.js +5 -0
  116. package/dist/core/inline/scan/brackets.js +1 -0
  117. package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
  118. package/dist/core/inline/scan/plugin-syntax.js +15 -1
  119. package/dist/core/inline/transparency.js +3 -3
  120. package/dist/core/inline-render.d.ts +6 -0
  121. package/dist/core/inline-render.js +32 -0
  122. package/dist/core/nodes.d.ts +14 -0
  123. package/dist/cursor/edge-affinity.js +2 -1
  124. package/dist/cursor/height-oracle.d.ts +2 -3
  125. package/dist/cursor/height-oracle.js +0 -1
  126. package/dist/cursor/overlay-remeasure.js +8 -0
  127. package/dist/cursor/reveal-source.js +7 -2
  128. package/dist/cursor/scroll-hold.d.ts +10 -0
  129. package/dist/cursor/scroll-hold.js +22 -0
  130. package/dist/cursor/scrollport.d.ts +9 -0
  131. package/dist/cursor/scrollport.js +30 -1
  132. package/dist/cursor/visual-lines.d.ts +5 -4
  133. package/dist/cursor/visual-lines.js +56 -11
  134. package/dist/cursor/widget-edge-snap.d.ts +28 -0
  135. package/dist/cursor/widget-edge-snap.js +38 -0
  136. package/dist/cursor/widget-offset.d.ts +9 -0
  137. package/dist/cursor/widget-offset.js +71 -4
  138. package/dist/debug/interaction-trace.d.ts +4 -0
  139. package/dist/debug/interaction-trace.js +15 -0
  140. package/dist/decorations/decoration-state.svelte.js +1 -1
  141. package/dist/decorations/reserved-attrs.js +1 -0
  142. package/dist/editor-actions/ancestry-folds.d.ts +2 -2
  143. package/dist/editor-actions/ancestry-folds.js +1 -1
  144. package/dist/editor-actions/block-edit-scope.js +1 -1
  145. package/dist/editor-actions/commit/text-batch.d.ts +3 -2
  146. package/dist/editor-actions/commit/text-batch.js +1 -1
  147. package/dist/editor-actions/commit/undo-controller.js +4 -2
  148. package/dist/editor-actions/container-edit.js +2 -1
  149. package/dist/editor-actions/enter-completion.d.ts +2 -0
  150. package/dist/editor-actions/enter-completion.js +23 -2
  151. package/dist/editor-actions/focus/focus-dispatch.js +7 -4
  152. package/dist/editor-actions/focus/focus-landing.d.ts +10 -1
  153. package/dist/editor-actions/focus/focus-landing.js +20 -6
  154. package/dist/editor-actions/inline-range-commit.js +1 -1
  155. package/dist/editor-actions/plugin/container.d.ts +7 -0
  156. package/dist/editor-actions/plugin/container.js +3 -1
  157. package/dist/editor-actions/reorder-action.js +19 -10
  158. package/dist/editor-actions/reorder-drag.js +29 -1
  159. package/dist/editor-actions/replacement-focus.d.ts +1 -1
  160. package/dist/editor-actions/replacement-focus.js +1 -1
  161. package/dist/editor-actions/search-replace.js +1 -1
  162. package/dist/editor-actions/table-context.d.ts +4 -1
  163. package/dist/editor-actions/table-context.js +57 -1
  164. package/dist/editor-events.d.ts +3 -0
  165. package/dist/editor-keys.d.ts +33 -0
  166. package/dist/editor-props.d.ts +24 -12
  167. package/dist/index.d.ts +1 -1
  168. package/dist/plugin.d.ts +7 -0
  169. package/dist/plugin.js +16 -0
  170. package/dist/plugins/latex/BlockMath.svelte +264 -29
  171. package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
  172. package/dist/plugins/latex/index.d.ts +2 -1
  173. package/dist/plugins/latex/latex-kind.js +60 -10
  174. package/dist/plugins/latex/math-completion.js +4 -1
  175. package/dist/plugins/latex/math-layout.d.ts +15 -0
  176. package/dist/plugins/latex/math-layout.js +13 -0
  177. package/dist/plugins/latex/math-source.d.ts +19 -0
  178. package/dist/plugins/latex/math-source.js +103 -0
  179. package/dist/plugins/latex/register.d.ts +11 -2
  180. package/dist/plugins/latex/register.js +3 -1
  181. package/dist/plugins/latex/renderer.d.ts +3 -3
  182. package/dist/plugins/latex/renderer.js +13 -6
  183. package/dist/plugins/mermaid/MermaidBlock.svelte +19 -9
  184. package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
  185. package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
  186. package/dist/reactivity/list-windowing.svelte.js +75 -30
  187. package/dist/schema/block-completions.d.ts +8 -0
  188. package/dist/schema/block-completions.js +11 -0
  189. package/dist/schema/commands.d.ts +9 -5
  190. package/dist/schema/commands.js +12 -7
  191. package/dist/schema/context-actions.d.ts +31 -0
  192. package/dist/schema/context-actions.js +23 -0
  193. package/dist/schema/fenced-code-raw.js +31 -1
  194. package/dist/schema/operations.d.ts +8 -1
  195. package/dist/schema/reserved-chords.js +37 -4
  196. package/dist/schema/table-cell-raw.d.ts +1 -1
  197. package/dist/schema/table-cell-raw.js +1 -1
  198. package/dist/selection/block-hit-test.js +3 -2
  199. package/dist/selection/char-endpoint-snap.js +1 -1
  200. package/dist/selection/clipboard-text.js +6 -1
  201. package/dist/selection/covered-block.d.ts +10 -0
  202. package/dist/selection/covered-block.js +24 -0
  203. package/dist/selection/cross-block/dispatch.d.ts +3 -0
  204. package/dist/selection/cross-block/dispatch.js +13 -1
  205. package/dist/selection/cross-block/format-range.d.ts +1 -1
  206. package/dist/selection/cross-block/format-range.js +4 -15
  207. package/dist/selection/cross-block/format-toggle.js +1 -1
  208. package/dist/selection/cross-block/keydown.js +3 -29
  209. package/dist/selection/cross-block/ops.js +1 -1
  210. package/dist/selection/cross-block/paste.js +31 -35
  211. package/dist/selection/cross-block/type-replace.d.ts +3 -2
  212. package/dist/selection/cross-block/type-replace.js +52 -13
  213. package/dist/selection/dead-space-caret.d.ts +13 -0
  214. package/dist/selection/dead-space-caret.js +57 -1
  215. package/dist/selection/drag-pointer.d.ts +26 -2
  216. package/dist/selection/drag-pointer.js +103 -2
  217. package/dist/selection/gap-caret.js +1 -1
  218. package/dist/selection/keyboard-extend.d.ts +3 -2
  219. package/dist/selection/keyboard-extend.js +4 -3
  220. package/dist/selection/multi-click.d.ts +42 -0
  221. package/dist/selection/multi-click.js +203 -0
  222. package/dist/selection/native-bridge.d.ts +3 -0
  223. package/dist/selection/native-bridge.js +31 -2
  224. package/dist/selection/path-lookup.d.ts +10 -2
  225. package/dist/selection/path-lookup.js +11 -4
  226. package/dist/selection/pointer-gesture.d.ts +9 -0
  227. package/dist/selection/pointer-gesture.js +11 -0
  228. package/dist/selection/primitives.d.ts +7 -0
  229. package/dist/selection/primitives.js +19 -1
  230. package/dist/selection/range-delete-ceremony.js +4 -2
  231. package/dist/selection/range-delete-chrome.js +2 -1
  232. package/dist/selection/range-delete-table-coverage.js +2 -1
  233. package/dist/selection/range-delete-table.js +3 -2
  234. package/dist/selection/range-delete.d.ts +4 -0
  235. package/dist/selection/range-delete.js +31 -3
  236. package/dist/selection/selection-drop.d.ts +34 -0
  237. package/dist/selection/selection-drop.js +253 -0
  238. package/dist/selection/selection-restore.js +1 -1
  239. package/dist/selection/selection-state.svelte.d.ts +6 -0
  240. package/dist/selection/selection-state.svelte.js +36 -1
  241. package/dist/selection/table-endpoint-snap.js +1 -1
  242. package/dist/selection/table-rect-extend.js +1 -1
  243. package/dist/styles/editor-theme.css +75 -31
  244. package/dist/styles/editor.css +268 -18
  245. package/dist/testing/container-conformance.js +2 -2
  246. package/dist/testing/inline-conformance.js +2 -1
  247. package/dist/tree-operations/blockquote.js +1 -1
  248. package/dist/tree-operations/chain-rebuild.d.ts +63 -0
  249. package/dist/tree-operations/chain-rebuild.js +142 -0
  250. package/dist/tree-operations/children.d.ts +1 -1
  251. package/dist/tree-operations/children.js +1 -1
  252. package/dist/tree-operations/cleanup.js +1 -1
  253. package/dist/tree-operations/content-write.d.ts +50 -0
  254. package/dist/tree-operations/content-write.js +263 -0
  255. package/dist/tree-operations/index.d.ts +8 -3
  256. package/dist/tree-operations/index.js +6 -2
  257. package/dist/tree-operations/list/exit-replacement.js +1 -1
  258. package/dist/tree-operations/list/unwrap-merge.js +3 -3
  259. package/dist/tree-operations/node-ops.d.ts +17 -234
  260. package/dist/tree-operations/node-ops.js +47 -1113
  261. package/dist/tree-operations/node-primitives.d.ts +75 -0
  262. package/dist/tree-operations/node-primitives.js +117 -0
  263. package/dist/tree-operations/paste/apply.js +1 -1
  264. package/dist/tree-operations/paste/body-write.d.ts +1 -1
  265. package/dist/tree-operations/paste/body-write.js +2 -2
  266. package/dist/tree-operations/paste/container-match.js +4 -2
  267. package/dist/tree-operations/paste/dispatch.js +2 -1
  268. package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
  269. package/dist/tree-operations/paste/focus-target.d.ts +1 -1
  270. package/dist/tree-operations/paste/list-absorb.js +1 -1
  271. package/dist/tree-operations/paste/list-break-out.js +1 -1
  272. package/dist/tree-operations/paste/parent-scope.js +1 -1
  273. package/dist/tree-operations/paste/paste-replacement.js +1 -1
  274. package/dist/tree-operations/paste/replace-block-at-parent.d.ts +3 -1
  275. package/dist/tree-operations/paste/replace-block-at-parent.js +5 -2
  276. package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
  277. package/dist/tree-operations/paste/replacement-parse.js +22 -0
  278. package/dist/tree-operations/path-mutate.d.ts +1 -1
  279. package/dist/tree-operations/path-mutate.js +2 -1
  280. package/dist/tree-operations/reorder-unit.js +1 -1
  281. package/dist/tree-operations/reorder.d.ts +5 -2
  282. package/dist/tree-operations/reorder.js +55 -2
  283. package/dist/tree-operations/settle.d.ts +105 -0
  284. package/dist/tree-operations/settle.js +660 -0
  285. package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
  286. package/dist/tree-operations/table-grid-clipboard.js +90 -0
  287. package/dist/tree-operations/unshare.d.ts +15 -77
  288. package/dist/tree-operations/unshare.js +15 -166
  289. package/docs/guide/consumer-guide.md +136 -104
  290. package/docs/guide/plugin-api.md +73 -29
  291. package/docs/guide/plugin-guide.md +55 -23
  292. package/package.json +9 -7
  293. package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
  294. package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
  295. package/dist/components/blocks/table/TableGrip.svelte +0 -91
  296. package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
  297. package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
  298. package/dist/components/blocks/table/table-drop-target.js +0 -16
  299. package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
  300. package/dist/components/blocks/table/table-reorder-drag.js +0 -97
@@ -39,7 +39,7 @@ export interface OperationDetailMap {
39
39
  parentItemIdx?: number;
40
40
  nestedItemIdx?: number;
41
41
  } | {
42
- source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-paste-whole-table';
42
+ source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-covered-block' | 'selection-drop';
43
43
  };
44
44
  paste: {
45
45
  count: number;
@@ -70,6 +70,13 @@ export interface OperationDetailMap {
70
70
  colIdx: number;
71
71
  side: 'left' | 'right';
72
72
  };
73
+ /** A grid pasted from `rowIdx`/`colIdx`, the table grown to hold it. */
74
+ tablePasteGrid: {
75
+ rowIdx: number;
76
+ colIdx: number;
77
+ rows: number;
78
+ cols: number;
79
+ };
73
80
  tableDeleteColumn: {
74
81
  colIdx: number;
75
82
  crossBlock?: true;
@@ -15,12 +15,30 @@ export const HARDCODED_CHORD_SITES = [
15
15
  keys: [],
16
16
  note: 'Mod-click link activation — no keystroke is consumed.'
17
17
  },
18
+ {
19
+ file: 'components/editor-root-keydown.ts',
20
+ chords: [],
21
+ keys: ['Escape'],
22
+ note: 'Escape closes the find bar (a reserved UI chord, enumerated). The modifier read is a REFUSAL: an unchorded character over a live range goes to the range’s text door, a chorded one to the arms below it.'
23
+ },
18
24
  {
19
25
  file: 'components/editor-root-listeners.ts',
20
26
  chords: [],
21
27
  keys: [],
22
28
  note: 'The Mod-held `data-mod-active` affordance tracks flag state only — no keystroke is consumed.'
23
29
  },
30
+ {
31
+ file: 'selection/selection-drop.ts',
32
+ chords: [],
33
+ keys: [],
34
+ note: 'Ctrl/Alt at the DROP is the platform copy modifier, read off a DragEvent — no keystroke is consumed.'
35
+ },
36
+ {
37
+ file: 'selection/multi-click.ts',
38
+ chords: [],
39
+ keys: [],
40
+ note: 'A modified multi-click is left to the browser (Shift extends, Mod activates) — no keystroke is consumed.'
41
+ },
24
42
  {
25
43
  file: 'components/GapCaret.svelte',
26
44
  chords: [],
@@ -45,8 +63,8 @@ export const HARDCODED_CHORD_SITES = [
45
63
  {
46
64
  file: 'components/blocks/editable-leaf.ts',
47
65
  chords: [],
48
- keys: ['Enter'],
49
- note: 'Shift-click gate on the rendered surface — a pointer read.'
66
+ keys: ['Backspace', 'Enter'],
67
+ note: "Shift-click gate on the rendered surface — a pointer read; plain Backspace (no modifier) at the top of an empty painted source deletes the block, as the code block does. The reveal's own undo resolves its chord through the keymap, so it claims none here."
50
68
  },
51
69
  {
52
70
  file: 'components/link-card/LinkCard.svelte',
@@ -54,6 +72,12 @@ export const HARDCODED_CHORD_SITES = [
54
72
  keys: ['Enter', 'K', 'Tab', 'k'],
55
73
  note: "Backwards step of the open card's focus trap, plus the entry chord swallowed as a no-op where the focus already is — the kind keymaps claim it everywhere else. Escape lives on the host, which must also close a card the document still holds the caret for."
56
74
  },
75
+ {
76
+ file: 'components/menu/BlockMenu.svelte',
77
+ chords: [],
78
+ keys: ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Enter', 'Escape'],
79
+ note: 'The open block menu steps and picks; the modifier read is the guard that leaves every chord to the editor.'
80
+ },
57
81
  {
58
82
  file: 'components/blocks/table/TableActionMenu.svelte',
59
83
  chords: ['Shift+Tab'],
@@ -115,8 +139,17 @@ export const HARDCODED_CHORD_SITES = [
115
139
  {
116
140
  file: 'components/blocks/text/widget-interaction.ts',
117
141
  chords: ['Shift+ArrowLeft', 'Shift+ArrowRight'],
118
- keys: ['Arrow*', 'ArrowLeft', 'ArrowRight', 'Backspace', 'Delete', 'Escape'],
119
- note: 'Extends a selection into a widget. A modified arrow is also swallowed while a widget is selected, but that is a caret-coherence guard in a transient state, not a binding.'
142
+ keys: [
143
+ 'Arrow*',
144
+ 'ArrowDown',
145
+ 'ArrowLeft',
146
+ 'ArrowRight',
147
+ 'ArrowUp',
148
+ 'Backspace',
149
+ 'Delete',
150
+ 'Escape'
151
+ ],
152
+ note: 'Extends a selection into a widget. A modified arrow is also swallowed while a widget is selected, but that is a caret-coherence guard in a transient state, not a binding; a plain vertical arrow seats a caret and declines.'
120
153
  },
121
154
  {
122
155
  file: 'components/image/ImageResizeHandles.svelte',
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
3
- * write sink (`tree-operations/node-ops.updateNodeContent`). A cell's bytes are joined verbatim
3
+ * write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
4
4
  * into its row, and the parser truncates a row reparsing wider than the delimiter's column
5
5
  * count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
6
6
  * Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
3
- * write sink (`tree-operations/node-ops.updateNodeContent`). A cell's bytes are joined verbatim
3
+ * write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
4
4
  * into its row, and the parser truncates a row reparsing wider than the delimiter's column
5
5
  * count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
6
6
  * Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
@@ -26,10 +26,11 @@ export function blockAtPoint(editorRoot, clientX, clientY) {
26
26
  path,
27
27
  // A cell-addressed kind's first contenteditable is one of its CELLS, so declaring
28
28
  // the drag hook withdraws the block-level surface rather than offering that. The
29
- // whole-block editing host is chrome, not characters, so it is excluded too.
29
+ // whole-block editing host is chrome, not characters, so it is excluded too, as is
30
+ // the selection overlay (`contenteditable="false"`, mounted while a range is live).
30
31
  charSurface: dragHitTest
31
32
  ? null
32
- : wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}])`),
33
+ : wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}]):not(.selection-overlay)`),
33
34
  foreignDragHitTest: dragHitTest && ((cx, cy) => dragHitTest(wrapper, cx, cy)),
34
35
  caretTargetAtPoint: caretTarget && ((cx, cy) => caretTarget(wrapper, cx, cy))
35
36
  };
@@ -6,7 +6,7 @@
6
6
  * order against the other endpoint, never the anchor/focus role, which `normalize` reorders later.
7
7
  */
8
8
  import { displayLength, snapToScalarBoundary } from '../core/lines';
9
- import { isBlockNode, nodeAt } from '../tree-operations/node-ops';
9
+ import { isBlockNode, nodeAt } from '../tree-operations/node-primitives';
10
10
  import { isWholeBlockUnit } from '../schema/whole-block-unit';
11
11
  import { comparePaths } from './path-math';
12
12
  import { isWholeBlockEndpoint } from './primitives';
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { makeBlockNode, metadataOf } from '../core/nodes';
5
5
  import { cloneMetadata } from '../tree-operations/clone';
6
- import { isBlockNode, nodeAt } from '../tree-operations/node-ops';
6
+ import { isBlockNode, nodeAt } from '../tree-operations/node-primitives';
7
7
  import { walkBetween, normalize, charOffsetOf, cellIndexOf } from './primitives';
8
8
  import { snapCrossBlockTableEndpoints } from './table-endpoint-snap';
9
9
  import { isStrictAncestorOf, pathHasPrefix, pathsEqual, sharedPrefixLength } from './path-math';
@@ -39,6 +39,11 @@ export function collectCrossBlockText(doc, anchor, focus) {
39
39
  }
40
40
  const startRaw = isBlockNode(startNode) ? startNode.raw : '';
41
41
  const endRaw = isBlockNode(endNode) ? endNode.raw : '';
42
+ // One leaf taken whole (`SelectionState.wholeUnitPath`): the only same-path prose pair the
43
+ // state stores, and the head/tail split below would emit its bytes twice.
44
+ if (pathsEqual(start.path, end.path) && !start.cellCoordinate) {
45
+ return startRaw.slice(start.offset, end.offset);
46
+ }
42
47
  let effectiveStartPath = start.path;
43
48
  let chromeStart = null;
44
49
  let startTail = '';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The block a range holds whole, for the gestures that insert over it. Such a range has no seam
3
+ * inside it to merge at: the block leaves the tree entirely, so bytes that follow must land in
4
+ * its SLOT rather than at a caret in whatever took the slot.
5
+ */
6
+ import type { DocumentView } from '../core/node-views';
7
+ import type { SelectionPoint } from './primitives';
8
+ /** The block both endpoints address and jointly span, or null. Cell coordinates index a grid
9
+ * rather than characters: the table's own coverage rule is `cross-block/paste.ts`'s. */
10
+ export declare function blockCoveredWhole(doc: DocumentView, anchor: SelectionPoint | null, focus: SelectionPoint | null): number[] | null;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The block a range holds whole, for the gestures that insert over it. Such a range has no seam
3
+ * inside it to merge at: the block leaves the tree entirely, so bytes that follow must land in
4
+ * its SLOT rather than at a caret in whatever took the slot.
5
+ */
6
+ import { displayLength } from '../core/lines';
7
+ import { isBlockNode, nodeAt } from '../tree-operations/node-primitives';
8
+ import { pathsEqual } from './path-math';
9
+ /** The block both endpoints address and jointly span, or null. Cell coordinates index a grid
10
+ * rather than characters: the table's own coverage rule is `cross-block/paste.ts`'s. */
11
+ export function blockCoveredWhole(doc, anchor, focus) {
12
+ if (!anchor || !focus)
13
+ return null;
14
+ if (!pathsEqual(anchor.path, focus.path))
15
+ return null;
16
+ if (anchor.cellCoordinate || focus.cellCoordinate)
17
+ return null;
18
+ const node = nodeAt(doc, anchor.path);
19
+ if (!node || !isBlockNode(node))
20
+ return null;
21
+ const low = Math.min(anchor.offset, focus.offset);
22
+ const high = Math.max(anchor.offset, focus.offset);
23
+ return low === 0 && high >= displayLength(node.raw) ? anchor.path.slice() : null;
24
+ }
@@ -71,6 +71,9 @@ export interface CrossBlockHandlers {
71
71
  * programmatic insertion: no gesture to consume, so `replacement` carries the payload. */
72
72
  handlePaste(e: ClipboardEvent | null, replacement?: string): Promise<boolean>;
73
73
  handleBeforeInput(e: InputEvent): Promise<boolean>;
74
+ /** Type-replace from a door that carries no InputEvent: the editor root, where a range over a
75
+ * block with no character position leaves no editing surface for `beforeinput` to fire on. */
76
+ insertText(text: string): Promise<boolean>;
74
77
  handleCompositionStart(): boolean;
75
78
  /** Cross-block range delete for Cut handlers, after they synchronously wrote the clipboard. */
76
79
  performCrossBlockDeleteFromEvent(): Promise<void>;
@@ -28,6 +28,14 @@ export function createCrossBlockHandlers(ctx) {
28
28
  // (each editable surface, the editor root) inherits them. Keydown gates its own destructive
29
29
  // branches, since it also carries navigation, which stays live.
30
30
  const reading = () => isReadingMode(ctx.getPresentationMode);
31
+ const insertText = async (text) => {
32
+ if (reading())
33
+ return true;
34
+ if (!ctx.selection.isCrossBlock)
35
+ return false;
36
+ await handleCrossBlockTypeReplace(ctx, mutationCtx, text);
37
+ return true;
38
+ };
31
39
  return {
32
40
  handleKeyDown: keydown.handleKeyDown,
33
41
  handleCompositionStart: keydown.handleCompositionStart,
@@ -44,8 +52,12 @@ export function createCrossBlockHandlers(ctx) {
44
52
  e.preventDefault();
45
53
  return true;
46
54
  }
47
- return handleCrossBlockTypeReplace(ctx, mutationCtx, e);
55
+ if (!ctx.selection.isCrossBlock || e.inputType !== 'insertText')
56
+ return false;
57
+ e.preventDefault();
58
+ return insertText(e.data ?? '');
48
59
  },
60
+ insertText,
49
61
  performCrossBlockDeleteFromEvent: async () => {
50
62
  // Reached from cut handlers after the clipboard write; declining the delete
51
63
  // degrades a reading-mode cut to a copy.
@@ -10,7 +10,7 @@ import type { DocumentView } from '../../core/node-views';
10
10
  import type { InlineMarkKind } from '../../schema/inline-construct-policy';
11
11
  import type { PresentationMode } from '../../presentation-mode';
12
12
  import type { GrammarView } from '../../schema/block-openers';
13
- import { type NodeParent } from '../../tree-operations/node-ops';
13
+ import { type NodeParent } from '../../tree-operations/node-primitives';
14
14
  import type { SharingState } from '../../tree-operations/sharing';
15
15
  import { type SelectionPoint } from '../primitives';
16
16
  export interface CrossBlockFormatWrite {
@@ -6,12 +6,13 @@
6
6
  * through the single-block seam, so its arms, candidates and mode verification are the same ones.
7
7
  * A grid joins by its cells, which the range covers whole.
8
8
  */
9
- import { activeInlineFormats, inlineFormatsCovering, isInlineFormatActive, isInlineFormatActiveAfter, toggleInlineFormat } from '../../core/inline/format-toggle';
9
+ import { activeInlineFormats, inlineFormatsCovering, isInlineFormatActive, isInlineFormatActiveAfter, toggleInlineFormat, withoutBoundaryWhitespace } from '../../core/inline/format-toggle';
10
10
  import { getContentRange } from '../../core/inline';
11
11
  import { ownTrailingLineEnding, trimTrailingLineEnding } from '../../core/lines';
12
12
  import { tryGetBlockKindDescriptor } from '../../schema/block-kind-descriptor';
13
- import { normalizeBodyWrite, writeOwnRaw } from '../../tree-operations/node-ops';
14
- import { ensureUnsharedPath, rebuildUnsharedChain } from '../../tree-operations/unshare';
13
+ import { normalizeBodyWrite, writeOwnRaw } from '../../tree-operations/node-primitives';
14
+ import { ensureUnsharedPath } from '../../tree-operations/unshare';
15
+ import { rebuildUnsharedChain } from '../../tree-operations/chain-rebuild';
15
16
  import { comparePaths } from '../path-math';
16
17
  import { charOffsetOf } from '../primitives';
17
18
  import { coveredGridCells, gridEndpointCellIndex } from '../table-endpoint-snap';
@@ -170,18 +171,6 @@ function contentSpan(node, path, from, to) {
170
171
  const selection = withoutBoundaryWhitespace(display, clampToContent(from ?? content.start, content), clampToContent(to ?? content.end, content));
171
172
  return selection && { path, edit: { display, content, selection } };
172
173
  }
173
- /** The span minus its boundary whitespace, null once nothing is left. Markdown opens and closes a
174
- * run against a word, never a space, so an untrimmed edge yields delimiters that form no
175
- * construct — which the modes that paint them write anyway (`wrapCandidates` is unverified there). */
176
- function withoutBoundaryWhitespace(display, from, to) {
177
- let start = from;
178
- let end = to;
179
- while (start < end && /\s/.test(display[start]))
180
- start++;
181
- while (end > start && /\s/.test(display[end - 1]))
182
- end--;
183
- return start === end ? null : { start, end };
184
- }
185
174
  const clampToContent = (offset, content) => Math.min(Math.max(offset, content.start), content.end);
186
175
  /**
187
176
  * Whether the span actually changed sides. The single-block seam decides its own arm from the
@@ -7,7 +7,7 @@
7
7
  import { ownTrailingLineEnding } from '../../core/lines';
8
8
  import { docPathFrom } from '../../cursor/coordinate-spaces';
9
9
  import { inlineMarkForCommand } from '../../schema/inline-construct-policy';
10
- import { blockNodeAt } from '../../tree-operations/node-ops';
10
+ import { blockNodeAt } from '../../tree-operations/node-primitives';
11
11
  import { comparePaths } from '../path-math';
12
12
  import { restoreSelection } from '../selection-restore';
13
13
  import { applyCrossBlockFormat, crossBlockActiveFormats, planCrossBlockFormat } from './format-range';
@@ -4,16 +4,14 @@
4
4
  */
5
5
  import { CURSOR_START } from '../../block-component';
6
6
  import { performCrossBlockDelete, performCrossBlockDeleteSync } from './ops';
7
- import { isBlockNode } from '../../tree-operations/node-ops';
7
+ import { isBlockNode } from '../../tree-operations/node-primitives';
8
8
  import { isReadingMode } from '../../presentation-mode';
9
9
  import { eventToChord } from '../../schema/keybindings';
10
10
  import { dispatchKeyCommand } from '../../schema/block-commands';
11
11
  import { collapseCrossBlock, extendFocusToNextBlock, extendFocusToPreviousBlock, extendFocusToDocEdge, selectWholeDocument, scrollFocusBlockIntoView } from '../keyboard-extend';
12
12
  import { pathsEqual } from '../path-math';
13
13
  import { intraTableRectExtension } from '../table-rect-extend';
14
- import { ambientSpanOf, placeCaretAfterAmbientSpan } from '../../ambient/ambient-dom';
15
- import { asDomTextOffset } from '../../cursor/coordinate-spaces';
16
- import { createRangeFromOffsets } from '../../cursor/content-offsets';
14
+ import { applySurfaceContentRange } from '../native-bridge';
17
15
  export function createCrossBlockKeydown(ctx, mutCtx) {
18
16
  return {
19
17
  handleKeyDown: (e) => handleKeyDown(ctx, mutCtx, e),
@@ -168,7 +166,7 @@ async function handleCrossBlockEntry(ctx, e) {
168
166
  e.preventDefault();
169
167
  selection.incrementSelectAllCount();
170
168
  if (selection.selectAllCount === 1) {
171
- selectFirstPressContent(el);
169
+ applySurfaceContentRange(el);
172
170
  return true;
173
171
  }
174
172
  selectWholeDocument(selection, getDoc(), ctx.getBlockElByPath);
@@ -261,30 +259,6 @@ function kindOfPath(path, doc) {
261
259
  // The root's 'document' kind is outside AnyBlockKind; dispatch treats it as an unknown kind.
262
260
  return isBlockNode(node) ? node.kind : node.kind;
263
261
  }
264
- /**
265
- * Select the block's content for the first Ctrl+A press. With an ambient marker (a list item's
266
- * `- `), anchor after it so type-replace doesn't corrupt the contenteditable="false" island.
267
- */
268
- function selectFirstPressContent(el) {
269
- const ambient = ambientSpanOf(el);
270
- const ambientLen = ambient?.textContent?.length ?? 0;
271
- const textLen = el.textContent?.length ?? 0;
272
- if (ambient && textLen > ambientLen) {
273
- if (!placeCaretAfterAmbientSpan(el))
274
- return;
275
- // textLen counts the full textContent (marker included) — a DomTextOffset by construction.
276
- const endRange = createRangeFromOffsets(el, asDomTextOffset(textLen), asDomTextOffset(textLen));
277
- if (endRange) {
278
- window.getSelection()?.extend(endRange.endContainer, endRange.endOffset);
279
- }
280
- return;
281
- }
282
- const range = document.createRange();
283
- range.selectNodeContents(el);
284
- const sel = window.getSelection();
285
- sel?.removeAllRanges();
286
- sel?.addRange(range);
287
- }
288
262
  /**
289
263
  * Bring the active (focus-side) endpoint into view after an extend. A cell-coordinate focus
290
264
  * addresses the table block by cell index, so reveal the deep cell path to mount the off-window
@@ -9,7 +9,7 @@ import { deleteSnapshot } from '../primitives';
9
9
  import { focusCollapsedCaret } from '../native-bridge';
10
10
  import { rangeDelete } from '../range-delete';
11
11
  import { trackChildIds } from '../../tree-operations/structural-change';
12
- import { isBlockNode, nodeAt } from '../../tree-operations/node-ops';
12
+ import { isBlockNode, nodeAt } from '../../tree-operations/node-primitives';
13
13
  import { pathsEqual } from '../path-math';
14
14
  import { docPathFrom } from '../../cursor/coordinate-spaces';
15
15
  import { getStateForNode } from '../../reactivity/state-registry';
@@ -9,13 +9,13 @@ import { normalizeLineEndings } from '../../core/lines';
9
9
  import { performCrossBlockDelete } from './ops';
10
10
  import { charOffsetOf } from '../primitives';
11
11
  import { focusCollapsedCaret } from '../native-bridge';
12
+ import { blockCoveredWhole } from '../covered-block';
12
13
  import { pasteDispatch } from '../../tree-operations/paste/dispatch';
13
14
  import { applyPasteTransforms } from '../../tree-operations/paste/paste-transforms';
14
- import { parse } from '../../core/parser';
15
- import { blockNodeAt, isBlockNode, nodeAt } from '../../tree-operations/node-ops';
15
+ import { blockNodeAt, isBlockNode, nodeAt } from '../../tree-operations/node-primitives';
16
16
  import { pathsEqual } from '../path-math';
17
17
  import { replaceBlockAtParent } from '../../tree-operations/paste/replace-block-at-parent';
18
- import { ensureEditableContainers, normalizeReplacementTrivia } from '../../tree-operations';
18
+ import { parseReplacement } from '../../tree-operations/paste/replacement-parse';
19
19
  import { emitClipboardError } from '../../editor-events';
20
20
  export async function handleCrossBlockPaste(ctx, mutCtx, e, replacement) {
21
21
  if (!ctx.selection.isCrossBlock)
@@ -32,10 +32,12 @@ export async function handleCrossBlockPaste(ctx, mutCtx, e, replacement) {
32
32
  if (!pasted)
33
33
  return true;
34
34
  const doc = ctx.getDoc();
35
- // Whole-table selection (Ctrl+A 2nd press inside a cell): replace the table block at the
36
- // parent position, single undo. The sub-rectangle path only clears cells, leaving the table.
37
- if (isWholeTableSelection(ctx.selection, doc)) {
38
- await replaceTableWithPaste(ctx, mutCtx, pasted);
35
+ // The range holds one block whole, in either addressing: replace the block at its parent
36
+ // position, single undo. A sub-rectangle inside a table only clears cells, leaving the table.
37
+ const covered = wholeTablePath(ctx.selection, doc) ??
38
+ blockCoveredWhole(doc, ctx.selection.anchor, ctx.selection.focus);
39
+ if (covered) {
40
+ await replaceCoveredBlockWithPaste(ctx, mutCtx, pasted, covered);
39
41
  return true;
40
42
  }
41
43
  // Read before the delete collapses the selection: the only coordinate an error report on the
@@ -96,60 +98,54 @@ async function landCaretAfterPaste(ctx, caretPath, inlineCaretOffset) {
96
98
  ctx.getBlockElByPath(caretPath)?.focus();
97
99
  }
98
100
  }
99
- // ── Whole-table paste ──────────────────────────────────────────────────────
100
- function isWholeTableSelection(selection, doc) {
101
+ // ── Covered-block paste ────────────────────────────────────────────────────
102
+ /** The table a cell rectangle covers whole (Ctrl+A's 2nd press inside a cell), or null. */
103
+ function wholeTablePath(selection, doc) {
101
104
  const anchor = selection.anchor;
102
105
  const focus = selection.focus;
103
106
  if (!anchor || !focus)
104
- return false;
107
+ return null;
105
108
  if (!pathsEqual(anchor.path, focus.path))
106
- return false;
109
+ return null;
107
110
  const node = nodeAt(doc, anchor.path);
108
111
  if (!node || !isBlockNode(node) || node.kind !== 'table')
109
- return false;
112
+ return null;
110
113
  const cellCount = tableCellCount(node);
111
114
  if (cellCount === 0)
112
- return false;
115
+ return null;
113
116
  // Same-path intra-table selection: cell offsets are context-established, so read directly.
114
117
  const lo = Math.min(anchor.offset, focus.offset);
115
118
  const hi = Math.max(anchor.offset, focus.offset);
116
- return lo === 0 && hi === cellCount - 1;
119
+ return lo === 0 && hi === cellCount - 1 ? anchor.path.slice() : null;
117
120
  }
118
121
  /**
119
- * Replace the selected table block with the pasted content at the table's parent position.
120
- * Routes through replaceBlockAtParent so the splice lands at the doc/enclosing-container scope
121
- * rather than the row-level blockEdit TableRowBlock propagates. One snapshot covers the replace.
122
+ * Replace the covered block with the pasted content at its parent position. Routes through
123
+ * replaceBlockAtParent so the splice lands at the doc/enclosing-container scope rather than the
124
+ * row-level blockEdit TableRowBlock propagates. One snapshot covers the replace.
122
125
  */
123
- async function replaceTableWithPaste(ctx, mutCtx, pasted) {
124
- const tablePath = ctx.selection.anchor.path;
126
+ async function replaceCoveredBlockWithPaste(ctx, mutCtx, pasted, blockPath) {
125
127
  const doc = ctx.getDoc();
128
+ const covered = blockNodeAt(doc, blockPath);
129
+ if (!covered)
130
+ return;
126
131
  // This route never reaches pasteDispatch, so the paste transforms and the instance grammar
127
132
  // ride here too; both rules live in the helper, applied at both sites.
128
- const parsed = parse(applyPasteTransforms(pasted, ctx.activePlugins), {
129
- grammar: ctx.grammar,
130
- scope: 'fragment'
131
- });
132
- if (parsed.children.length === 0)
133
- return;
134
- const tableNode = blockNodeAt(doc, tablePath);
135
- if (!tableNode)
133
+ const parsed = parseReplacement(covered, applyPasteTransforms(pasted, ctx.activePlugins), ctx.grammar);
134
+ if (!parsed)
136
135
  return;
137
- const replacement = normalizeReplacementTrivia(tableNode, parsed.children);
138
- for (const node of replacement)
139
- ensureEditableContainers(node);
140
136
  mutCtx.pushUndoSnapshot();
141
137
  ctx.selection.collapse();
142
138
  await replaceBlockAtParent({
143
139
  doc,
144
- blockPath: tablePath,
145
- replacement,
140
+ blockPath,
141
+ replacement: parsed.replacement,
146
142
  controller: ctx.pasteCoordinator,
147
143
  undoEntry: 'join',
148
- focusReplacementIndex: replacement.length - 1,
144
+ focusReplacementIndex: parsed.replacement.length - 1,
149
145
  focusOffset: CURSOR_END,
150
- source: 'cross-block-paste-whole-table',
146
+ source: 'cross-block-covered-block',
151
147
  ...(ctx.grammar ? { grammar: ctx.grammar } : {}),
152
- // Nothing is reattached behind the clipboard here — the table's whole slot is the target —
148
+ // Nothing is reattached behind the clipboard here — the block's whole slot is the target —
153
149
  // so the trailing blank rides in unfiltered (`paste/dispatch.ts` states the rule).
154
150
  trailingSeparator: parsed.suffix
155
151
  });
@@ -3,8 +3,9 @@
3
3
  * Delete the range, splice the character into the surviving leaf's raw, re-parse so a marker at
4
4
  * offset 0 re-derives the kind (parity with the single-block type path). Routed through
5
5
  * commitMultiScope so a kind change mints a fresh node, ids/refs stay synced, and the op is
6
- * `updateContent`.
6
+ * `updateContent`. A range holding its block whole has no surviving leaf and takes the
7
+ * replace arm at the foot of the file instead.
7
8
  */
8
9
  import type { CrossBlockDispatchContext } from './dispatch';
9
10
  import type { CrossBlockMutationContext } from './ops';
10
- export declare function handleCrossBlockTypeReplace(ctx: CrossBlockDispatchContext, mutCtx: CrossBlockMutationContext, e: InputEvent): Promise<boolean>;
11
+ export declare function handleCrossBlockTypeReplace(ctx: CrossBlockDispatchContext, mutCtx: CrossBlockMutationContext, typed: string): Promise<void>;
@@ -3,37 +3,49 @@
3
3
  * Delete the range, splice the character into the surviving leaf's raw, re-parse so a marker at
4
4
  * offset 0 re-derives the kind (parity with the single-block type path). Routed through
5
5
  * commitMultiScope so a kind change mints a fresh node, ids/refs stay synced, and the op is
6
- * `updateContent`.
6
+ * `updateContent`. A range holding its block whole has no surviving leaf and takes the
7
+ * replace arm at the foot of the file instead.
7
8
  */
8
9
  import { performCrossBlockDelete } from './ops';
9
10
  import { charOffsetOf } from '../primitives';
10
- import { blockNodeAt, normalizeBodyWrite, updateNodeContent, writeOwnRaw, settledCaretTarget } from '../../tree-operations/node-ops';
11
+ import { blockCoveredWhole } from '../covered-block';
12
+ import { CURSOR_END } from '../../block-component';
13
+ import { replaceBlockAtParent } from '../../tree-operations/paste/replace-block-at-parent';
14
+ import { parseReplacement } from '../../tree-operations/paste/replacement-parse';
15
+ import { blockNodeAt, normalizeBodyWrite, writeOwnRaw } from '../../tree-operations/node-primitives';
16
+ import { updateNodeContent, settledCaretTarget } from '../../tree-operations/content-write';
11
17
  import { focusCollapsedCaret } from '../native-bridge';
12
- import { ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedPath, rebuildUnsharedChain } from '../../tree-operations/unshare';
18
+ import { ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedPath } from '../../tree-operations/unshare';
19
+ import { rebuildUnsharedChain } from '../../tree-operations/chain-rebuild';
13
20
  import { stampStructuralChange } from '../../tree-operations/structural-change';
14
21
  import { getStateForNode } from '../../reactivity/state-registry';
15
22
  import { docPathFrom } from '../../cursor/coordinate-spaces';
16
23
  import { devWarn } from '../../dev-warn';
17
- export async function handleCrossBlockTypeReplace(ctx, mutCtx, e) {
18
- if (!ctx.selection.isCrossBlock || e.inputType !== 'insertText')
19
- return false;
20
- e.preventDefault();
21
- const typed = e.data ?? '';
24
+ export async function handleCrossBlockTypeReplace(ctx, mutCtx, typed) {
25
+ // A range holding its block whole leaves no leaf to splice into, so the character replaces the
26
+ // block in its own slot. An empty insertion has nothing to stand there and takes the delete.
27
+ const covered = typed
28
+ ? blockCoveredWhole(ctx.getDoc(), ctx.selection.anchor, ctx.selection.focus)
29
+ : null;
30
+ if (covered) {
31
+ await replaceCoveredBlockWithText(ctx, mutCtx, typed, covered);
32
+ return;
33
+ }
22
34
  const caret = await performCrossBlockDelete(mutCtx, { skipCaretRestore: true });
23
35
  if (!caret)
24
- return true;
36
+ return;
25
37
  // All caret placements below target caret.path's top-level block; mount it once here so each
26
38
  // (the post-tick landing included) finds a live element.
27
39
  await ctx.revealPath(caret.path);
28
40
  if (!typed) {
29
41
  focusCollapsedCaret(ctx.getBlockElByPath, caret);
30
- return true;
42
+ return;
31
43
  }
32
44
  const doc = ctx.getDoc();
33
45
  const targetNode = blockNodeAt(doc, caret.path);
34
46
  if (!targetNode) {
35
47
  focusCollapsedCaret(ctx.getBlockElByPath, caret);
36
- return true;
48
+ return;
37
49
  }
38
50
  // `updateContent`, not `input`: symmetric with the single-block path's KIND-CHANGING branch
39
51
  // (block-edit.ts updateBlockContent). Only that path's kind-stable branch emits the debounced
@@ -42,7 +54,7 @@ export async function handleCrossBlockTypeReplace(ctx, mutCtx, e) {
42
54
  const scope = resolveTypedCharScope(ctx, caret.path);
43
55
  if (!scope) {
44
56
  focusCollapsedCaret(ctx.getBlockElByPath, caret);
45
- return true;
57
+ return;
46
58
  }
47
59
  // resolveTypedCharScope returns the leaf's IMMEDIATE parent: every mounted container registers
48
60
  // a BlockListState, and the document root stands in for a top-level leaf. The guard below is
@@ -97,7 +109,34 @@ export async function handleCrossBlockTypeReplace(ctx, mutCtx, e) {
97
109
  focusCollapsedCaret(ctx.getBlockElByPath, { path, offset: target.offset });
98
110
  }
99
111
  });
100
- return true;
112
+ }
113
+ /**
114
+ * Replace the covered block with the parse of the typed character, at the block's parent
115
+ * position: the same door the covered-block paste takes, so both gestures splice at one scope
116
+ * and land one undo entry. Re-parsed rather than spliced, so a marker typed over the block
117
+ * derives its kind exactly as the single-block type path does.
118
+ */
119
+ async function replaceCoveredBlockWithText(ctx, mutCtx, typed, blockPath) {
120
+ const doc = ctx.getDoc();
121
+ const covered = blockNodeAt(doc, blockPath);
122
+ if (!covered)
123
+ return;
124
+ const parsed = parseReplacement(covered, typed, ctx.grammar);
125
+ if (!parsed)
126
+ return;
127
+ mutCtx.pushUndoSnapshot();
128
+ ctx.selection.collapse();
129
+ await replaceBlockAtParent({
130
+ doc,
131
+ blockPath,
132
+ replacement: parsed.replacement,
133
+ controller: ctx.pasteCoordinator,
134
+ undoEntry: 'join',
135
+ focusReplacementIndex: parsed.replacement.length - 1,
136
+ focusOffset: CURSOR_END,
137
+ source: 'cross-block-covered-block',
138
+ ...(ctx.grammar ? { grammar: ctx.grammar } : {})
139
+ });
101
140
  }
102
141
  /** The scope's children, re-read after the commit: the ceremony replaces the node it published. */
103
142
  function scopeChildrenOf(ctx, scopePath) {
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { type BlockComponent } from '../block-component';
9
9
  import { type GapStopScope } from './gap-caret';
10
+ import type { SelectionEndpoint } from './primitives';
10
11
  export interface DeadSpaceCaretDeps {
11
12
  getBlockComponent(path: number[]): BlockComponent | null;
12
13
  /**
@@ -35,5 +36,17 @@ export interface DeadSpaceCaret {
35
36
  * was claimed; a point past a windowed-out tail claims it and lands after the reveal.
36
37
  */
37
38
  placeAtPoint(root: HTMLElement, x: number, y: number): boolean;
39
+ /** Whether a press target is the editor's dead space (the root, or a block list inside it). */
40
+ isDeadSpaceTarget(root: HTMLElement, target: EventTarget | null): boolean;
41
+ /**
42
+ * The endpoint a drag STARTING in dead space (or on a block's rendered face) anchors at: the
43
+ * landing a click there would take, without taking it. A character surface anchors at that
44
+ * offset; a rendered leaf (an equation) anchors as a WHOLE block, so the range takes it entire
45
+ * whichever way the drag goes. A coordinate-addressed kind (a table) anchors nothing.
46
+ */
47
+ anchorAtPoint(root: HTMLElement, x: number, y: number): SelectionEndpoint | null;
48
+ /** The path of the block a press names, the point clamped into the nearest band: what the
49
+ * click ladder selects in for a press on the margin or a block's own box. */
50
+ blockPathNearPoint(root: HTMLElement, x: number, y: number): number[] | null;
38
51
  }
39
52
  export declare function createDeadSpaceCaret(deps: DeadSpaceCaretDeps): DeadSpaceCaret;